From c173c4e0515d1dd7ec9ccb36e4cb7338fd8791ab Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 16 Mar 2026 00:12:38 +1100 Subject: [PATCH] Fix stuck-in-past expiry date filter (#11518) (#11523) * fix stuck-in-past expiry date filter * also fix OVERDUE_FILTER date problem Co-authored-by: Jacob Felknor --- src/backend/InvenTree/build/api.py | 4 ++-- src/backend/InvenTree/build/models.py | 15 +++++++----- src/backend/InvenTree/build/serializers.py | 3 ++- src/backend/InvenTree/order/models.py | 28 ++++++++++++---------- src/backend/InvenTree/order/serializers.py | 4 ++-- src/backend/InvenTree/stock/api.py | 4 ++-- src/backend/InvenTree/stock/models.py | 14 ++++++----- src/backend/InvenTree/stock/serializers.py | 2 +- 8 files changed, 42 insertions(+), 32 deletions(-) diff --git a/src/backend/InvenTree/build/api.py b/src/backend/InvenTree/build/api.py index e94590e67f..f509b980b4 100644 --- a/src/backend/InvenTree/build/api.py +++ b/src/backend/InvenTree/build/api.py @@ -145,8 +145,8 @@ class BuildFilter(FilterSet): def filter_overdue(self, queryset, name, value): """Filter the queryset to either include or exclude orders which are overdue.""" if str2bool(value): - return queryset.filter(Build.OVERDUE_FILTER) - return queryset.exclude(Build.OVERDUE_FILTER) + return queryset.filter(Build.get_overdue_filter()) + return queryset.exclude(Build.get_overdue_filter()) assigned_to_me = rest_filters.BooleanFilter( label=_('Assigned to me'), method='filter_assigned_to_me' diff --git a/src/backend/InvenTree/build/models.py b/src/backend/InvenTree/build/models.py index 497aeeece6..1d819e7508 100644 --- a/src/backend/InvenTree/build/models.py +++ b/src/backend/InvenTree/build/models.py @@ -123,11 +123,14 @@ class Build( order_insertion_by = ['reference'] - OVERDUE_FILTER = ( - Q(status__in=BuildStatusGroups.ACTIVE_CODES) - & ~Q(target_date=None) - & Q(target_date__lte=InvenTree.helpers.current_date()) - ) + @classmethod + def get_overdue_filter(cls): + """Filter for determining if a build order is overdue.""" + return ( + Q(status__in=BuildStatusGroups.ACTIVE_CODES) + & ~Q(target_date=None) + & Q(target_date__lte=InvenTree.helpers.current_date()) + ) # Global setting for specifying reference pattern REFERENCE_PATTERN_SETTING = 'BUILDORDER_REFERENCE_PATTERN' @@ -456,7 +459,7 @@ class Build( bool: Is the build overdue """ query = Build.objects.filter(pk=self.pk) - query = query.filter(Build.OVERDUE_FILTER) + query = query.filter(Build.get_overdue_filter()) return query.exists() diff --git a/src/backend/InvenTree/build/serializers.py b/src/backend/InvenTree/build/serializers.py index 48f290f2d3..ad9c677a6f 100644 --- a/src/backend/InvenTree/build/serializers.py +++ b/src/backend/InvenTree/build/serializers.py @@ -168,7 +168,8 @@ class BuildSerializer( queryset = queryset.annotate( overdue=Case( When( - Build.OVERDUE_FILTER, then=Value(True, output_field=BooleanField()) + Build.get_overdue_filter(), + then=Value(True, output_field=BooleanField()), ), default=Value(False, output_field=BooleanField()), ) diff --git a/src/backend/InvenTree/order/models.py b/src/backend/InvenTree/order/models.py index 2d06ca09ef..38d4ea32e6 100644 --- a/src/backend/InvenTree/order/models.py +++ b/src/backend/InvenTree/order/models.py @@ -1863,12 +1863,14 @@ class PurchaseOrderLineItem(OrderLineItem): verbose_name = _('Purchase Order Line Item') - # Filter for determining if a particular PurchaseOrderLineItem is overdue - OVERDUE_FILTER = ( - Q(received__lt=F('quantity')) - & ~Q(target_date=None) - & Q(target_date__lt=InvenTree.helpers.current_date()) - ) + @classmethod + def get_overdue_filter(cls): + """Filter for determining if a particular PurchaseOrderLineItem is overdue.""" + return ( + Q(received__lt=F('quantity')) + & ~Q(target_date=None) + & Q(target_date__lt=InvenTree.helpers.current_date()) + ) @staticmethod def get_api_url() -> str: @@ -2067,12 +2069,14 @@ class SalesOrderLineItem(OrderLineItem): verbose_name = _('Sales Order Line Item') - # Filter for determining if a particular SalesOrderLineItem is overdue - OVERDUE_FILTER = ( - Q(shipped__lt=F('quantity')) - & ~Q(target_date=None) - & Q(target_date__lt=InvenTree.helpers.current_date()) - ) + @classmethod + def get_overdue_filter(cls): + """Filter for determining if a particular SalesOrderLineItem is overdue.""" + return ( + Q(shipped__lt=F('quantity')) + & ~Q(target_date=None) + & Q(target_date__lt=InvenTree.helpers.current_date()) + ) @staticmethod def get_api_url(): diff --git a/src/backend/InvenTree/order/serializers.py b/src/backend/InvenTree/order/serializers.py index 85b4b91d5f..236b262410 100644 --- a/src/backend/InvenTree/order/serializers.py +++ b/src/backend/InvenTree/order/serializers.py @@ -577,7 +577,7 @@ class PurchaseOrderLineItemSerializer( queryset = queryset.annotate( overdue=Case( When( - order.models.PurchaseOrderLineItem.OVERDUE_FILTER, + order.models.PurchaseOrderLineItem.get_overdue_filter(), then=Value(True, output_field=BooleanField()), ), default=Value(False, output_field=BooleanField()), @@ -1154,7 +1154,7 @@ class SalesOrderLineItemSerializer( overdue=Case( When( Q(order__status__in=SalesOrderStatusGroups.OPEN) - & order.models.SalesOrderLineItem.OVERDUE_FILTER, + & order.models.SalesOrderLineItem.get_overdue_filter(), then=Value(True, output_field=BooleanField()), ), default=Value(False, output_field=BooleanField()), diff --git a/src/backend/InvenTree/stock/api.py b/src/backend/InvenTree/stock/api.py index 95257265d4..0ae1a154a8 100644 --- a/src/backend/InvenTree/stock/api.py +++ b/src/backend/InvenTree/stock/api.py @@ -685,8 +685,8 @@ class StockFilter(FilterSet): return queryset if str2bool(value): - return queryset.filter(StockItem.EXPIRED_FILTER) - return queryset.exclude(StockItem.EXPIRED_FILTER) + return queryset.filter(StockItem.get_expired_filter()) + return queryset.exclude(StockItem.get_expired_filter()) external = rest_filters.BooleanFilter( label=_('External Location'), method='filter_external' diff --git a/src/backend/InvenTree/stock/models.py b/src/backend/InvenTree/stock/models.py index 68c5b53416..8ff556c2a3 100644 --- a/src/backend/InvenTree/stock/models.py +++ b/src/backend/InvenTree/stock/models.py @@ -518,12 +518,14 @@ class StockItem( status__in=StockStatusGroups.AVAILABLE_CODES, ) - # A query filter which can be used to filter StockItem objects which have expired - EXPIRED_FILTER = ( - IN_STOCK_FILTER - & ~Q(expiry_date=None) - & Q(expiry_date__lt=InvenTree.helpers.current_date()) - ) + @classmethod + def get_expired_filter(cls): + """A query filter which can be used to filter StockItem objects which have expired.""" + return ( + cls.IN_STOCK_FILTER + & ~Q(expiry_date=None) + & Q(expiry_date__lt=InvenTree.helpers.current_date()) + ) @classmethod def _create_serial_numbers(cls, serials: list, **kwargs) -> QuerySet: diff --git a/src/backend/InvenTree/stock/serializers.py b/src/backend/InvenTree/stock/serializers.py index 11ac344e1e..49913ecb75 100644 --- a/src/backend/InvenTree/stock/serializers.py +++ b/src/backend/InvenTree/stock/serializers.py @@ -516,7 +516,7 @@ class StockItemSerializer( queryset = queryset.annotate( expired=Case( When( - StockItem.EXPIRED_FILTER, + StockItem.get_expired_filter(), then=Value(True, output_field=BooleanField()), ), default=Value(False, output_field=BooleanField()),