Compare commits

...

5 Commits

Author SHA1 Message Date
github-actions[bot] 9793bba77a
Fix stocktake bug for counting serialized items (#12280) (#12282)
* Fix stocktake bug for counting serialized items

* Add unit test

(cherry picked from commit 414aac0224)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2026-06-30 17:09:23 +10:00
github-actions[bot] ffc60cb189
[UI] Stock column fix (#12268) (#12269)
* [UI] Fix StockColumn component

* stock table rendering tweaks

(cherry picked from commit 1da71ca3b9)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2026-06-27 12:25:10 +10:00
github-actions[bot] 2ac8f608d8
Bug fix for exception handler (#12257) (#12261)
(cherry picked from commit e847d96a88)

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
2026-06-26 09:55:52 +10:00
Oliver 9ea33df847
Bump version to 1.4.1 (#12242) 2026-06-24 15:47:31 +10:00
Oliver 0a9a8b1c54
Add release entry for 1.4.0 (#12237)
* Add release entry for 1.4.0

* Mark version as 1.4.0
2026-06-24 13:53:55 +10:00
8 changed files with 50 additions and 22 deletions

View File

@ -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/), 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). 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 ### Breaking Changes

View File

@ -15,7 +15,7 @@ from datetime import timedelta as td
from .api_version import INVENTREE_API_TEXT, INVENTREE_API_VERSION from .api_version import INVENTREE_API_TEXT, INVENTREE_API_VERSION
# InvenTree software version # InvenTree software version
INVENTREE_SW_VERSION = '1.4.0 dev' INVENTREE_SW_VERSION = '1.4.1'
# Minimum supported Python version # Minimum supported Python version
MIN_PYTHON_VERSION = (3, 11) MIN_PYTHON_VERSION = (3, 11)

View File

@ -6,6 +6,7 @@ from datetime import datetime
from typing import Optional from typing import Optional
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.exceptions import FieldDoesNotExist
from django.core.exceptions import ValidationError as DjangoValidationError from django.core.exceptions import ValidationError as DjangoValidationError
from django.core.validators import FileExtensionValidator from django.core.validators import FileExtensionValidator
from django.db import models from django.db import models
@ -200,7 +201,7 @@ class DataImportSession(models.Model):
related_field = model_class._meta.get_field(field_name) related_field = model_class._meta.get_field(field_name)
model = related_field.remote_field.model model = related_field.remote_field.model
return model return model
except (AttributeError, models.FieldDoesNotExist): except (AttributeError, FieldDoesNotExist):
return None return None
def extract_columns(self) -> None: def extract_columns(self) -> None:

View File

@ -2651,8 +2651,8 @@ class StockItem(
) )
tracking_info['old_status_logical'] = old_status_logical tracking_info['old_status_logical'] = old_status_logical
if self.updateQuantity(count): if self.serialized or self.updateQuantity(count):
tracking_info['quantity'] = float(count) tracking_info['quantity'] = 1 if self.serialized else float(count)
self.stocktake_date = InvenTree.helpers.current_date() self.stocktake_date = InvenTree.helpers.current_date()
self.stocktake_user = user self.stocktake_user = user
@ -2672,12 +2672,12 @@ class StockItem(
deltas=tracking_info, deltas=tracking_info,
) )
trigger_event( trigger_event(
StockEvents.ITEM_COUNTED, StockEvents.ITEM_COUNTED,
'stockitem.counted', 'stockitem.counted',
id=self.id, id=self.id,
quantity=float(self.quantity), quantity=1 if self.serialized else float(self.quantity),
) )
return True return True

View File

@ -2289,6 +2289,32 @@ class StocktakeTest(StockAPITestCase):
self.assertEqual(response.data['items'][0]['pk'], 1234) self.assertEqual(response.data['items'][0]['pk'], 1234)
self.assertEqual(response.data['items'][0]['quantity'], target[endpoint]) 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): def test_transfer(self):
"""Test stock transfers.""" """Test stock transfers."""
stock_item = StockItem.objects.get(pk=1234) stock_item = StockItem.objects.get(pk=1234)

View File

@ -156,7 +156,7 @@ export function StockColumn(props: StockColumnProps): TableColumn {
text = `# ${stock_item.serial}`; text = `# ${stock_item.serial}`;
} }
if (record.is_building) { if (stock_item.is_building) {
color = 'blue'; color = 'blue';
extra.push( extra.push(
<Text <Text
@ -164,35 +164,35 @@ export function StockColumn(props: StockColumnProps): TableColumn {
size='sm' size='sm'
>{t`This stock item is in production`}</Text> >{t`This stock item is in production`}</Text>
); );
} else if (record.sales_order) { } else if (stock_item.sales_order) {
extra.push( extra.push(
<Text <Text
key='sales-order' key='sales-order'
size='sm' size='sm'
>{t`This stock item has been assigned to a sales order`}</Text> >{t`This stock item has been assigned to a sales order`}</Text>
); );
} else if (record.customer) { } else if (stock_item.customer) {
extra.push( extra.push(
<Text <Text
key='customer' key='customer'
size='sm' size='sm'
>{t`This stock item has been assigned to a customer`}</Text> >{t`This stock item has been assigned to a customer`}</Text>
); );
} else if (record.belongs_to) { } else if (stock_item.belongs_to) {
extra.push( extra.push(
<Text <Text
key='belongs-to' key='belongs-to'
size='sm' size='sm'
>{t`This stock item is installed in another stock item`}</Text> >{t`This stock item is installed in another stock item`}</Text>
); );
} else if (record.consumed_by) { } else if (stock_item.consumed_by) {
extra.push( extra.push(
<Text <Text
key='consumed-by' key='consumed-by'
size='sm' size='sm'
>{t`This stock item has been consumed by a build order`}</Text> >{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( extra.push(
<Text <Text
key='unavailable' key='unavailable'
@ -201,17 +201,17 @@ export function StockColumn(props: StockColumnProps): TableColumn {
); );
} }
if (record.expired) { if (stock_item.expired) {
extra.push( extra.push(
<Text key='expired' size='sm'>{t`This stock item has expired`}</Text> <Text key='expired' size='sm'>{t`This stock item has expired`}</Text>
); );
} else if (record.stale) { } else if (stock_item.stale) {
extra.push( extra.push(
<Text key='stale' size='sm'>{t`This stock item is stale`}</Text> <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 > 0) {
if (allocated > quantity) { if (allocated > quantity) {
color = 'red'; color = 'red';
@ -267,7 +267,7 @@ export function StockColumn(props: StockColumnProps): TableColumn {
} }
} }
if (!record.in_stock) { if (!stock_item.in_stock) {
color = 'red'; color = 'red';
} }

View File

@ -68,7 +68,7 @@ export function TableHoverCard({
zIndex={zIndex} zIndex={zIndex}
> >
<HoverCard.Target> <HoverCard.Target>
<Group gap='xs' justify='space-between'> <Group gap='xs' justify='space-between' wrap='nowrap'>
{value} {value}
<InvenTreeIcon <InvenTreeIcon
icon={icon ?? 'info'} icon={icon ?? 'info'}

View File

@ -86,6 +86,7 @@ function stockItemTableColumns({
'allocated', 'allocated',
'consumed', 'consumed',
'installed', 'installed',
'in_stock',
'sent_to_customer' 'sent_to_customer'
] ]
}), }),