Compare commits
5 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
9793bba77a | |
|
|
ffc60cb189 | |
|
|
2ac8f608d8 | |
|
|
9ea33df847 | |
|
|
0a9a8b1c54 |
|
|
@ -5,7 +5,7 @@ All major notable changes to this project will be documented in this file (start
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## Unreleased - YYYY-MM-DD
|
||||
## 1.4.0 - 2026-06-24
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ from datetime import timedelta as td
|
|||
from .api_version import INVENTREE_API_TEXT, INVENTREE_API_VERSION
|
||||
|
||||
# InvenTree software version
|
||||
INVENTREE_SW_VERSION = '1.4.0 dev'
|
||||
INVENTREE_SW_VERSION = '1.4.1'
|
||||
|
||||
# Minimum supported Python version
|
||||
MIN_PYTHON_VERSION = (3, 11)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ from datetime import datetime
|
|||
from typing import Optional
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import FieldDoesNotExist
|
||||
from django.core.exceptions import ValidationError as DjangoValidationError
|
||||
from django.core.validators import FileExtensionValidator
|
||||
from django.db import models
|
||||
|
|
@ -200,7 +201,7 @@ class DataImportSession(models.Model):
|
|||
related_field = model_class._meta.get_field(field_name)
|
||||
model = related_field.remote_field.model
|
||||
return model
|
||||
except (AttributeError, models.FieldDoesNotExist):
|
||||
except (AttributeError, FieldDoesNotExist):
|
||||
return None
|
||||
|
||||
def extract_columns(self) -> None:
|
||||
|
|
|
|||
|
|
@ -2651,8 +2651,8 @@ class StockItem(
|
|||
)
|
||||
tracking_info['old_status_logical'] = old_status_logical
|
||||
|
||||
if self.updateQuantity(count):
|
||||
tracking_info['quantity'] = float(count)
|
||||
if self.serialized or self.updateQuantity(count):
|
||||
tracking_info['quantity'] = 1 if self.serialized else float(count)
|
||||
|
||||
self.stocktake_date = InvenTree.helpers.current_date()
|
||||
self.stocktake_user = user
|
||||
|
|
@ -2672,12 +2672,12 @@ class StockItem(
|
|||
deltas=tracking_info,
|
||||
)
|
||||
|
||||
trigger_event(
|
||||
StockEvents.ITEM_COUNTED,
|
||||
'stockitem.counted',
|
||||
id=self.id,
|
||||
quantity=float(self.quantity),
|
||||
)
|
||||
trigger_event(
|
||||
StockEvents.ITEM_COUNTED,
|
||||
'stockitem.counted',
|
||||
id=self.id,
|
||||
quantity=1 if self.serialized else float(self.quantity),
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
|
|
|||
|
|
@ -2289,6 +2289,32 @@ class StocktakeTest(StockAPITestCase):
|
|||
self.assertEqual(response.data['items'][0]['pk'], 1234)
|
||||
self.assertEqual(response.data['items'][0]['quantity'], target[endpoint])
|
||||
|
||||
def test_count_serialized(self):
|
||||
"""Test that counting a serialized stock item correctly updates stocktake_date."""
|
||||
import datetime
|
||||
|
||||
# Fixture item pk=501 is a serialized item (serial=1)
|
||||
item = StockItem.objects.get(pk=501)
|
||||
self.assertTrue(item.serialized)
|
||||
self.assertEqual(item.quantity, 1)
|
||||
|
||||
# Clear any existing stocktake date so we can verify it gets set
|
||||
item.stocktake_date = None
|
||||
item.save()
|
||||
|
||||
url = reverse('api-stock-count')
|
||||
|
||||
# Count the serialized item — quantity must be 1
|
||||
data = {'items': [{'pk': item.pk, 'quantity': 1}]}
|
||||
response = self.post(url, data, expected_code=201)
|
||||
|
||||
self.assertEqual(response.data['items'][0]['pk'], item.pk)
|
||||
self.assertEqual(response.data['items'][0]['quantity'], '1.00000')
|
||||
|
||||
# stocktake_date must have been set to today
|
||||
item.refresh_from_db()
|
||||
self.assertEqual(item.stocktake_date, datetime.date.today())
|
||||
|
||||
def test_transfer(self):
|
||||
"""Test stock transfers."""
|
||||
stock_item = StockItem.objects.get(pk=1234)
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ export function StockColumn(props: StockColumnProps): TableColumn {
|
|||
text = `# ${stock_item.serial}`;
|
||||
}
|
||||
|
||||
if (record.is_building) {
|
||||
if (stock_item.is_building) {
|
||||
color = 'blue';
|
||||
extra.push(
|
||||
<Text
|
||||
|
|
@ -164,35 +164,35 @@ export function StockColumn(props: StockColumnProps): TableColumn {
|
|||
size='sm'
|
||||
>{t`This stock item is in production`}</Text>
|
||||
);
|
||||
} else if (record.sales_order) {
|
||||
} else if (stock_item.sales_order) {
|
||||
extra.push(
|
||||
<Text
|
||||
key='sales-order'
|
||||
size='sm'
|
||||
>{t`This stock item has been assigned to a sales order`}</Text>
|
||||
);
|
||||
} else if (record.customer) {
|
||||
} else if (stock_item.customer) {
|
||||
extra.push(
|
||||
<Text
|
||||
key='customer'
|
||||
size='sm'
|
||||
>{t`This stock item has been assigned to a customer`}</Text>
|
||||
);
|
||||
} else if (record.belongs_to) {
|
||||
} else if (stock_item.belongs_to) {
|
||||
extra.push(
|
||||
<Text
|
||||
key='belongs-to'
|
||||
size='sm'
|
||||
>{t`This stock item is installed in another stock item`}</Text>
|
||||
);
|
||||
} else if (record.consumed_by) {
|
||||
} else if (stock_item.consumed_by) {
|
||||
extra.push(
|
||||
<Text
|
||||
key='consumed-by'
|
||||
size='sm'
|
||||
>{t`This stock item has been consumed by a build order`}</Text>
|
||||
);
|
||||
} else if (!record.in_stock) {
|
||||
} else if (!stock_item.in_stock) {
|
||||
extra.push(
|
||||
<Text
|
||||
key='unavailable'
|
||||
|
|
@ -201,17 +201,17 @@ export function StockColumn(props: StockColumnProps): TableColumn {
|
|||
);
|
||||
}
|
||||
|
||||
if (record.expired) {
|
||||
if (stock_item.expired) {
|
||||
extra.push(
|
||||
<Text key='expired' size='sm'>{t`This stock item has expired`}</Text>
|
||||
);
|
||||
} else if (record.stale) {
|
||||
} else if (stock_item.stale) {
|
||||
extra.push(
|
||||
<Text key='stale' size='sm'>{t`This stock item is stale`}</Text>
|
||||
);
|
||||
}
|
||||
|
||||
if (record.in_stock) {
|
||||
if (stock_item.in_stock) {
|
||||
if (allocated > 0) {
|
||||
if (allocated > quantity) {
|
||||
color = 'red';
|
||||
|
|
@ -267,7 +267,7 @@ export function StockColumn(props: StockColumnProps): TableColumn {
|
|||
}
|
||||
}
|
||||
|
||||
if (!record.in_stock) {
|
||||
if (!stock_item.in_stock) {
|
||||
color = 'red';
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ export function TableHoverCard({
|
|||
zIndex={zIndex}
|
||||
>
|
||||
<HoverCard.Target>
|
||||
<Group gap='xs' justify='space-between'>
|
||||
<Group gap='xs' justify='space-between' wrap='nowrap'>
|
||||
{value}
|
||||
<InvenTreeIcon
|
||||
icon={icon ?? 'info'}
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ function stockItemTableColumns({
|
|||
'allocated',
|
||||
'consumed',
|
||||
'installed',
|
||||
'in_stock',
|
||||
'sent_to_customer'
|
||||
]
|
||||
}),
|
||||
|
|
|
|||
Loading…
Reference in New Issue