From 3d9104e9a5ac2cef817a4fb6635c298bc60bc95d Mon Sep 17 00:00:00 2001 From: Jack <129231166+jmerro-c3d@users.noreply.github.com> Date: Sat, 28 Feb 2026 09:07:02 +1100 Subject: [PATCH] Add "updated_at" field to Orders model (#11374) * add "updated_at" field to PurchaseOrder model * change to use abstract po instead * add api filters * add show, order and filter by po updated_at date to frontend * add tests and increment api_version * change updated_at to null by default * never trust github conflict resolution * bump docker image to python 3.14 (#11414) * chore(deps): bump the dependencies group across 1 directory with 4 updates (#11416) Bumps the dependencies group with 4 updates in the / directory: [depot/setup-action](https://github.com/depot/setup-action), [depot/build-push-action](https://github.com/depot/build-push-action), [anchore/sbom-action](https://github.com/anchore/sbom-action) and [actions/stale](https://github.com/actions/stale). Updates `depot/setup-action` from 1.6.0 to 1.7.1 - [Release notes](https://github.com/depot/setup-action/releases) - [Commits](https://github.com/depot/setup-action/compare/b0b1ea4f69e92ebf5dea3f8713a1b0c37b2126a5...15c09a5f77a0840ad4bce955686522a257853461) Updates `depot/build-push-action` from 1.16.2 to 1.17.0 - [Release notes](https://github.com/depot/build-push-action/releases) - [Commits](https://github.com/depot/build-push-action/compare/9785b135c3c76c33db102e45be96a25ab55cd507...5f3b3c2e5a00f0093de47f657aeaefcedff27d18) Updates `anchore/sbom-action` from 0.21.1 to 0.22.2 - [Release notes](https://github.com/anchore/sbom-action/releases) - [Changelog](https://github.com/anchore/sbom-action/blob/main/RELEASE.md) - [Commits](https://github.com/anchore/sbom-action/compare/0b82b0b1a22399a1c542d4d656f70cd903571b5c...28d71544de8eaf1b958d335707167c5f783590ad) Updates `actions/stale` from 10.1.1 to 10.2.0 - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/997185467fa4f803885201cee163a9f38240193d...b5d41d4e1d5dceea10e7104786b73624c18a190f) --- updated-dependencies: - dependency-name: depot/setup-action dependency-version: 1.7.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: depot/build-push-action dependency-version: 1.17.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: anchore/sbom-action dependency-version: 0.22.2 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies - dependency-name: actions/stale dependency-version: 10.2.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * [UI] Copy cells expansion (#11410) * Prevent copy button if copy value is null * Add "link" columns to order tables * Support copy for default column types * Tweak padding to avoid flickering issues * Refactor IPNColumn * Adjust visual styling * Copy for SKU and MPN columns * Add more copy columns * More tweaks * Tweak playwright testing * Further cleanup * More copy cols * Fix auto pricing overwriting manual purchase price #10846 (#11411) * Fix auto pricing overwriting manual purchase price #10846 * Added entry to api_version.py --------- Co-authored-by: Oliver * [UI] Default locale (#11412) * [UI] Support default server language * Handle faulty theme * Add option for default language * Improve language selection * Brief docs entry * Fix typo * Fix yarn build * Remove debug msg * Fix calendar locale * feat(backend): ensure restore of backups only works in correct enviroments (#11372) * [FR] ensure restore of backups only works in correct enviroments Fixes #11214 * update PR nbr * fix wrong ty detection * fix link * ensure tracing does not enagage while running backup ops * fix import * remove debugging string * add error codes * add tests for backup and restore * complete test for restore * we do not need e2e on every matrix entry there is no realy db dep here * fix changelog format * add flag to allow bypass * update CHANGELOG.md --------- Signed-off-by: dependabot[bot] Co-authored-by: Oliver Co-authored-by: Matthias Mair Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: JustusRijke <53965859+JustusRijke@users.noreply.github.com> --- CHANGELOG.md | 1 + .../InvenTree/InvenTree/api_version.py | 7 +- src/backend/InvenTree/order/api.py | 11 ++ ...ated_at_returnorder_updated_at_and_more.py | 43 +++++ src/backend/InvenTree/order/models.py | 51 +++++- src/backend/InvenTree/order/serializers.py | 14 +- src/backend/InvenTree/order/tests.py | 163 +++++++++++++++++- .../src/components/details/Details.tsx | 22 ++- .../pages/purchasing/PurchaseOrderDetail.tsx | 9 + .../src/pages/sales/ReturnOrderDetail.tsx | 9 + .../src/pages/sales/SalesOrderDetail.tsx | 9 + src/frontend/src/tables/ColumnRenderers.tsx | 10 ++ src/frontend/src/tables/Filter.tsx | 18 ++ .../tables/purchasing/PurchaseOrderTable.tsx | 12 +- .../src/tables/sales/ReturnOrderTable.tsx | 12 +- .../src/tables/sales/SalesOrderTable.tsx | 12 +- 16 files changed, 385 insertions(+), 18 deletions(-) create mode 100644 src/backend/InvenTree/order/migrations/0115_purchaseorder_updated_at_returnorder_updated_at_and_more.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 2981106a76..a7970539d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#11383](https://github.com/inventree/InvenTree/pull/11383) adds "exists_for_model_id", "exists_for_related_model", and "exists_for_related_model_id" filters to the ParameterTemplate API endpoint. These filters allow users to check for the existence of parameters associated with specific models or related models, improving the flexibility and usability of the API. - [#10887](https://github.com/inventree/InvenTree/pull/10887) adds the ability to auto-allocate tracked items against specific build outputs. Currently, this will only allocate items where the serial number of the tracked item matches the serial number of the build output, but in future this may be extended to allow for more flexible allocation rules. - [#11372](https://github.com/inventree/InvenTree/pull/11372) adds backup metadata setter and restore metadata validator functions to ensure common footguns are harder to trigger when using the backup and restore functionality. +- [#11374](https://github.com/inventree/InvenTree/pull/11374) adds `updated_at` field on purchase, sales and return orders. ### Changed diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py index 4d4b72abd2..a41015e398 100644 --- a/src/backend/InvenTree/InvenTree/api_version.py +++ b/src/backend/InvenTree/InvenTree/api_version.py @@ -1,11 +1,16 @@ """InvenTree API version information.""" # InvenTree API version -INVENTREE_API_VERSION = 459 +INVENTREE_API_VERSION = 460 """Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" INVENTREE_API_TEXT = """ +v460 -> 2026-02-25 : https://github.com/inventree/InvenTree/pull/11374 + - Adds "updated_at" field to PurchaseOrder, SalesOrder and ReturnOrder API endpoints + - Adds "updated_before" and "updated_after" date filters to all three order list endpoints + - Adds "updated_at" ordering option to all three order list endpoints + v459 -> 2026-02-23 : https://github.com/inventree/InvenTree/pull/11411 - Changed PurchaseOrderLine "auto_pricing" default value from true to false diff --git a/src/backend/InvenTree/order/api.py b/src/backend/InvenTree/order/api.py index 096e8d1dfb..a7bdee7568 100644 --- a/src/backend/InvenTree/order/api.py +++ b/src/backend/InvenTree/order/api.py @@ -228,6 +228,14 @@ class OrderFilter(FilterSet): label=_('Target Date After'), field_name='target_date', lookup_expr='gt' ) + updated_before = InvenTreeDateFilter( + label=_('Updated Before'), field_name='updated_at', lookup_expr='lt' + ) + + updated_after = InvenTreeDateFilter( + label=_('Updated After'), field_name='updated_at', lookup_expr='gt' + ) + min_date = InvenTreeDateFilter(label=_('Min Date'), method='filter_min_date') def filter_min_date(self, queryset, name, value): @@ -420,6 +428,7 @@ class PurchaseOrderList( 'responsible', 'total_price', 'project_code', + 'updated_at', ] ordering = '-reference' @@ -882,6 +891,7 @@ class SalesOrderList( 'shipment_date', 'total_price', 'project_code', + 'updated_at', ] search_fields = [ @@ -1549,6 +1559,7 @@ class ReturnOrderList( 'target_date', 'complete_date', 'project_code', + 'updated_at', ] search_fields = [ diff --git a/src/backend/InvenTree/order/migrations/0115_purchaseorder_updated_at_returnorder_updated_at_and_more.py b/src/backend/InvenTree/order/migrations/0115_purchaseorder_updated_at_returnorder_updated_at_and_more.py new file mode 100644 index 0000000000..837e2a35e4 --- /dev/null +++ b/src/backend/InvenTree/order/migrations/0115_purchaseorder_updated_at_returnorder_updated_at_and_more.py @@ -0,0 +1,43 @@ +# Generated by Django 5.2.11 on 2026-02-19 22:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("order", "0114_purchaseorderextraline_project_code_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="purchaseorder", + name="updated_at", + field=models.DateTimeField( + blank=True, + null=True, + help_text="Timestamp of last update", + verbose_name="Updated At", + ), + ), + migrations.AddField( + model_name="returnorder", + name="updated_at", + field=models.DateTimeField( + blank=True, + null=True, + help_text="Timestamp of last update", + verbose_name="Updated At", + ), + ), + migrations.AddField( + model_name="salesorder", + name="updated_at", + field=models.DateTimeField( + blank=True, + null=True, + help_text="Timestamp of last update", + verbose_name="Updated At", + ), + ), + ] diff --git a/src/backend/InvenTree/order/models.py b/src/backend/InvenTree/order/models.py index f34c0cb6dd..fdba3e9323 100644 --- a/src/backend/InvenTree/order/models.py +++ b/src/backend/InvenTree/order/models.py @@ -9,7 +9,7 @@ from django.core.validators import MinValueValidator from django.db import models, transaction from django.db.models import F, Q, QuerySet, Sum from django.db.models.functions import Coalesce -from django.db.models.signals import post_save +from django.db.models.signals import post_delete, post_save from django.dispatch.dispatcher import receiver from django.urls import reverse from django.utils.translation import gettext_lazy as _ @@ -331,6 +331,8 @@ class Order( if not self.creation_date: self.creation_date = InvenTree.helpers.current_date() + self.updated_at = InvenTree.helpers.current_time() + super().save(*args, **kwargs) def check_locked(self, db: bool = False) -> bool: @@ -498,6 +500,13 @@ class Order( help_text=_('Date order was issued'), ) + updated_at = models.DateTimeField( + null=True, + blank=True, + verbose_name=_('Updated At'), + help_text=_('Timestamp of last update'), + ) + responsible = models.ForeignKey( UserModels.Owner, on_delete=models.SET_NULL, @@ -3072,3 +3081,43 @@ class ReturnOrderExtraLine(OrderExtraLine): verbose_name=_('Order'), help_text=_('Return Order'), ) + + +def _touch_order_updated_at(instance): + """Bump updated_at on the parent order without triggering a full save.""" + if not InvenTree.ready.canAppAccessDatabase(allow_test=True): + return + instance.order.__class__.objects.filter(pk=instance.order_id).update( + updated_at=InvenTree.helpers.current_time() + ) + + +@receiver(post_save, sender=PurchaseOrderLineItem, dispatch_uid='po_lineitem_post_save') +@receiver( + post_delete, sender=PurchaseOrderLineItem, dispatch_uid='po_lineitem_post_delete' +) +@receiver( + post_save, sender=PurchaseOrderExtraLine, dispatch_uid='po_extraline_post_save' +) +@receiver( + post_delete, sender=PurchaseOrderExtraLine, dispatch_uid='po_extraline_post_delete' +) +@receiver(post_save, sender=SalesOrderLineItem, dispatch_uid='so_lineitem_post_save') +@receiver( + post_delete, sender=SalesOrderLineItem, dispatch_uid='so_lineitem_post_delete' +) +@receiver(post_save, sender=SalesOrderExtraLine, dispatch_uid='so_extraline_post_save') +@receiver( + post_delete, sender=SalesOrderExtraLine, dispatch_uid='so_extraline_post_delete' +) +@receiver(post_save, sender=ReturnOrderLineItem, dispatch_uid='ro_lineitem_post_save') +@receiver( + post_delete, sender=ReturnOrderLineItem, dispatch_uid='ro_lineitem_post_delete' +) +@receiver(post_save, sender=ReturnOrderExtraLine, dispatch_uid='ro_extraline_post_save') +@receiver( + post_delete, sender=ReturnOrderExtraLine, dispatch_uid='ro_extraline_post_delete' +) +def update_order_on_lineitem_change(sender, instance, **kwargs): + """Update parent order updated_at when any line item is saved or deleted.""" + _touch_order_updated_at(instance) diff --git a/src/backend/InvenTree/order/serializers.py b/src/backend/InvenTree/order/serializers.py index 5d0fd37d5b..7613323271 100644 --- a/src/backend/InvenTree/order/serializers.py +++ b/src/backend/InvenTree/order/serializers.py @@ -373,8 +373,14 @@ class PurchaseOrderSerializer( 'total_price', 'order_currency', 'destination', + 'updated_at', ]) - read_only_fields = ['issue_date', 'complete_date', 'creation_date'] + read_only_fields = [ + 'issue_date', + 'complete_date', + 'creation_date', + 'updated_at', + ] extra_kwargs = { 'supplier': {'required': True}, 'order_currency': {'required': False}, @@ -1026,8 +1032,9 @@ class SalesOrderSerializer( 'shipments_count', 'completed_shipments_count', 'allocated_lines', + 'updated_at', ]) - read_only_fields = ['status', 'creation_date', 'shipment_date'] + read_only_fields = ['status', 'creation_date', 'shipment_date', 'updated_at'] extra_kwargs = {'order_currency': {'required': False}} def skip_create_fields(self): @@ -1918,8 +1925,9 @@ class ReturnOrderSerializer( 'customer_reference', 'order_currency', 'total_price', + 'updated_at', ]) - read_only_fields = ['creation_date'] + read_only_fields = ['creation_date', 'updated_at'] def skip_create_fields(self): """Skip these fields when instantiating a new object.""" diff --git a/src/backend/InvenTree/order/tests.py b/src/backend/InvenTree/order/tests.py index 1712f68759..e4e04c82ee 100644 --- a/src/backend/InvenTree/order/tests.py +++ b/src/backend/InvenTree/order/tests.py @@ -25,7 +25,17 @@ from part.models import Part from stock.models import StockItem, StockLocation from users.models import Owner -from .models import PurchaseOrder, PurchaseOrderExtraLine, PurchaseOrderLineItem +from .models import ( + PurchaseOrder, + PurchaseOrderExtraLine, + PurchaseOrderLineItem, + ReturnOrder, + ReturnOrderExtraLine, + ReturnOrderLineItem, + SalesOrder, + SalesOrderExtraLine, + SalesOrderLineItem, +) class OrderTest(ExchangeRateMixin, PluginRegistryMixin, TestCase): @@ -369,7 +379,8 @@ class OrderTest(ExchangeRateMixin, PluginRegistryMixin, TestCase): order=po, part=sp_1, quantity=3, - purchase_price=Money(1000, 'USD'), # "Unit price" should be $100USD + # "Unit price" should be $100USD + purchase_price=Money(1000, 'USD'), ) # 13 x 0.1 = 1.3 @@ -569,3 +580,151 @@ class OrderTest(ExchangeRateMixin, PluginRegistryMixin, TestCase): p.set_metadata(k, k) self.assertEqual(len(p.metadata.keys()), 4) + + +class OrderUpdatedAtTest(TestCase): + """Tests to verify that the updated_at field is correctly maintained on all order types.""" + + def setUp(self): + """Set up objects for all three order types.""" + self.supplier = Company.objects.filter(is_supplier=True).first() + self.customer = Company.objects.filter(is_customer=True).first() + + self.po = PurchaseOrder.objects.create( + reference='PO-TEST-001', supplier=self.supplier + ) + self.so = SalesOrder.objects.create( + reference='SO-TEST-001', customer=self.customer + ) + self.ro = ReturnOrder.objects.create( + reference='RO-TEST-001', customer=self.customer + ) + + self.part = Part.objects.create(name='Test Part', description='Test Part') + self.stock_item = StockItem.objects.create(part=self.part, quantity=10) + + def _refresh(self, instance): + """Return a fresh copy of the instance from the database.""" + return instance.__class__.objects.get(pk=instance.pk) + + def test_updated_at_set_on_save(self): + """updated_at should be populated after the order is saved.""" + for instance in [self.po, self.so, self.ro]: + self.assertIsNotNone(self._refresh(instance).updated_at) + + def test_updated_at_changes_on_save(self): + """updated_at should advance when the order is saved again.""" + for instance in [self.po, self.so, self.ro]: + original = self._refresh(instance).updated_at + + instance.description = 'Updated description' + instance.save() + + refreshed = self._refresh(instance) + self.assertGreaterEqual(refreshed.updated_at, original) + + def test_updated_at_on_extra_line_add(self): + """updated_at should advance on the parent order when an extra line is added.""" + for instance, ExtraLine in [ + (self.po, PurchaseOrderExtraLine), + (self.so, SalesOrderExtraLine), + (self.ro, ReturnOrderExtraLine), + ]: + before = self._refresh(instance).updated_at + + ExtraLine.objects.create(order=instance, quantity=1) + + after = self._refresh(instance).updated_at + self.assertGreaterEqual(after, before) + + def test_updated_at_on_extra_line_update(self): + """updated_at should advance on the parent order when an extra line is updated.""" + for instance, ExtraLine in [ + (self.po, PurchaseOrderExtraLine), + (self.so, SalesOrderExtraLine), + (self.ro, ReturnOrderExtraLine), + ]: + line = ExtraLine.objects.create(order=instance, quantity=1) + + before = self._refresh(instance).updated_at + + line.quantity = 5 + line.save() + + after = self._refresh(instance).updated_at + self.assertGreaterEqual(after, before) + + def test_updated_at_on_extra_line_delete(self): + """updated_at should advance on the parent order when an extra line is deleted.""" + for instance, ExtraLine in [ + (self.po, PurchaseOrderExtraLine), + (self.so, SalesOrderExtraLine), + (self.ro, ReturnOrderExtraLine), + ]: + line = ExtraLine.objects.create(order=instance, quantity=1) + + before = self._refresh(instance).updated_at + + line.delete() + + after = self._refresh(instance).updated_at + self.assertGreaterEqual(after, before) + + def test_updated_at_on_line_item_add(self): + """updated_at should advance on the parent order when a regular line item is added.""" + before_po = self._refresh(self.po).updated_at + PurchaseOrderLineItem.objects.create(order=self.po, part=None, quantity=1) + self.assertGreaterEqual(self._refresh(self.po).updated_at, before_po) + + before_so = self._refresh(self.so).updated_at + SalesOrderLineItem.objects.create(order=self.so, part=None, quantity=1) + self.assertGreaterEqual(self._refresh(self.so).updated_at, before_so) + + before_ro = self._refresh(self.ro).updated_at + ReturnOrderLineItem.objects.create( + order=self.ro, item=self.stock_item, quantity=1 + ) + self.assertGreaterEqual(self._refresh(self.ro).updated_at, before_ro) + + def test_updated_at_on_line_item_update(self): + """updated_at should advance on the parent order when a regular line item is updated.""" + po_line = PurchaseOrderLineItem.objects.create( + order=self.po, part=None, quantity=1 + ) + so_line = SalesOrderLineItem.objects.create( + order=self.so, part=None, quantity=1 + ) + ro_line = ReturnOrderLineItem.objects.create( + order=self.ro, item=self.stock_item, quantity=1 + ) + + for instance, line in [ + (self.po, po_line), + (self.so, so_line), + (self.ro, ro_line), + ]: + before = self._refresh(instance).updated_at + line.quantity = 5 + line.save() + self.assertGreaterEqual(self._refresh(instance).updated_at, before) + + def test_updated_at_on_line_item_delete(self): + """updated_at should advance on the parent order when a regular line item is deleted.""" + po_line = PurchaseOrderLineItem.objects.create( + order=self.po, part=None, quantity=1 + ) + so_line = SalesOrderLineItem.objects.create( + order=self.so, part=None, quantity=1 + ) + ro_line = ReturnOrderLineItem.objects.create( + order=self.ro, item=self.stock_item, quantity=1 + ) + + for instance, line in [ + (self.po, po_line), + (self.so, so_line), + (self.ro, ro_line), + ]: + before = self._refresh(instance).updated_at + line.delete() + self.assertGreaterEqual(self._refresh(instance).updated_at, before) diff --git a/src/frontend/src/components/details/Details.tsx b/src/frontend/src/components/details/Details.tsx index 655af719c6..2e61e7377a 100644 --- a/src/frontend/src/components/details/Details.tsx +++ b/src/frontend/src/components/details/Details.tsx @@ -54,10 +54,16 @@ export type DetailsField = { type BadgeType = 'owner' | 'user' | 'group'; type ValueFormatterReturn = string | number | null | React.ReactNode; -type StringDetailField = { - type: 'string' | 'text' | 'date'; - unit?: boolean; -}; +type StringDetailField = + | { + type: 'string' | 'text'; + unit?: boolean; + } + | { + type: 'date'; + unit?: boolean; + showTime?: boolean; + }; type NumberDetailField = { type: 'number'; @@ -260,7 +266,13 @@ function NameBadge({ } function DateValue(props: Readonly) { - return {formatDate(props.field_value?.toString())}; + return ( + + {formatDate(props.field_value?.toString(), { + showTime: props.field_data?.showTime + })} + + ); } // Return a formatted "number" value, with optional unit diff --git a/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx b/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx index d14d298d8f..f8295065c9 100644 --- a/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx +++ b/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx @@ -304,6 +304,15 @@ export default function PurchaseOrderDetail() { label: t`Completion Date`, copy: true, hidden: !order.complete_date + }, + { + type: 'date', + name: 'updated_at', + label: t`Last Updated`, + icon: 'calendar', + copy: true, + showTime: true, + hidden: !order.updated_at } ]; diff --git a/src/frontend/src/pages/sales/ReturnOrderDetail.tsx b/src/frontend/src/pages/sales/ReturnOrderDetail.tsx index fd64e7689b..7fccf41690 100644 --- a/src/frontend/src/pages/sales/ReturnOrderDetail.tsx +++ b/src/frontend/src/pages/sales/ReturnOrderDetail.tsx @@ -282,6 +282,15 @@ export default function ReturnOrderDetail() { label: t`Completion Date`, copy: true, hidden: !order.complete_date + }, + { + type: 'date', + name: 'updated_at', + label: t`Last Updated`, + icon: 'calendar', + copy: true, + showTime: true, + hidden: !order.updated_at } ]; diff --git a/src/frontend/src/pages/sales/SalesOrderDetail.tsx b/src/frontend/src/pages/sales/SalesOrderDetail.tsx index acd7976457..9fc6317929 100644 --- a/src/frontend/src/pages/sales/SalesOrderDetail.tsx +++ b/src/frontend/src/pages/sales/SalesOrderDetail.tsx @@ -273,6 +273,15 @@ export default function SalesOrderDetail() { label: t`Completion Date`, hidden: !order.shipment_date, copy: true + }, + { + type: 'date', + name: 'updated_at', + label: t`Last Updated`, + icon: 'calendar', + copy: true, + showTime: true, + hidden: !order.updated_at } ]; diff --git a/src/frontend/src/tables/ColumnRenderers.tsx b/src/frontend/src/tables/ColumnRenderers.tsx index 6247094b67..c31b1684ab 100644 --- a/src/frontend/src/tables/ColumnRenderers.tsx +++ b/src/frontend/src/tables/ColumnRenderers.tsx @@ -725,6 +725,16 @@ export function ShipmentDateColumn(props: TableColumnProps): TableColumn { }); } +export function UpdatedAtColumn(props: TableColumnProps): TableColumn { + return DateColumn({ + accessor: 'updated_at', + title: t`Updated`, + defaultVisible: false, + extra: { showTime: true }, + ...props + }); +} + export function CurrencyColumn({ accessor, title, diff --git a/src/frontend/src/tables/Filter.tsx b/src/frontend/src/tables/Filter.tsx index f093cb94f2..b020f0ed81 100644 --- a/src/frontend/src/tables/Filter.tsx +++ b/src/frontend/src/tables/Filter.tsx @@ -286,6 +286,24 @@ export function CompletedAfterFilter(): TableFilter { }; } +export function UpdatedAfterFilter(): TableFilter { + return { + name: 'updated_after', + label: t`Updated After`, + description: t`Show orders updated after this date`, + type: 'date' + }; +} + +export function UpdatedBeforeFilter(): TableFilter { + return { + name: 'updated_before', + label: t`Updated Before`, + description: t`Show orders updated before this date`, + type: 'date' + }; +} + export function HasProjectCodeFilter(): TableFilter { const globalSettings = useGlobalSettingsState.getState(); const enabled = globalSettings.isSet('PROJECT_CODES_ENABLED', true); diff --git a/src/frontend/src/tables/purchasing/PurchaseOrderTable.tsx b/src/frontend/src/tables/purchasing/PurchaseOrderTable.tsx index 18977c9103..e450ede8e7 100644 --- a/src/frontend/src/tables/purchasing/PurchaseOrderTable.tsx +++ b/src/frontend/src/tables/purchasing/PurchaseOrderTable.tsx @@ -25,7 +25,8 @@ import { ResponsibleColumn, StartDateColumn, StatusColumn, - TargetDateColumn + TargetDateColumn, + UpdatedAtColumn } from '../ColumnRenderers'; import { AssignedToMeFilter, @@ -45,7 +46,9 @@ import { StartDateAfterFilter, StartDateBeforeFilter, TargetDateAfterFilter, - TargetDateBeforeFilter + TargetDateBeforeFilter, + UpdatedAfterFilter, + UpdatedBeforeFilter } from '../Filter'; import { InvenTreeTable } from '../InvenTreeTable'; @@ -99,6 +102,8 @@ export function PurchaseOrderTable({ }, CompletedBeforeFilter(), CompletedAfterFilter(), + UpdatedBeforeFilter(), + UpdatedAfterFilter(), ProjectCodeFilter(), HasProjectCodeFilter(), ResponsibleFilter(), @@ -142,6 +147,9 @@ export function PurchaseOrderTable({ CompletionDateColumn({ accessor: 'complete_date' }), + UpdatedAtColumn({ + defaultVisible: false + }), { accessor: 'total_price', title: t`Total Price`, diff --git a/src/frontend/src/tables/sales/ReturnOrderTable.tsx b/src/frontend/src/tables/sales/ReturnOrderTable.tsx index 498e0d2163..22ad44d9ce 100644 --- a/src/frontend/src/tables/sales/ReturnOrderTable.tsx +++ b/src/frontend/src/tables/sales/ReturnOrderTable.tsx @@ -25,7 +25,8 @@ import { ResponsibleColumn, StartDateColumn, StatusColumn, - TargetDateColumn + TargetDateColumn, + UpdatedAtColumn } from '../ColumnRenderers'; import { AssignedToMeFilter, @@ -46,7 +47,9 @@ import { StartDateAfterFilter, StartDateBeforeFilter, TargetDateAfterFilter, - TargetDateBeforeFilter + TargetDateBeforeFilter, + UpdatedAfterFilter, + UpdatedBeforeFilter } from '../Filter'; import { InvenTreeTable } from '../InvenTreeTable'; @@ -99,6 +102,8 @@ export function ReturnOrderTable({ }, CompletedBeforeFilter(), CompletedAfterFilter(), + UpdatedBeforeFilter(), + UpdatedAfterFilter(), HasProjectCodeFilter(), ProjectCodeFilter(), ResponsibleFilter(), @@ -146,6 +151,9 @@ export function ReturnOrderTable({ CompletionDateColumn({ accessor: 'complete_date' }), + UpdatedAtColumn({ + defaultVisible: false + }), ResponsibleColumn({}), { accessor: 'total_price', diff --git a/src/frontend/src/tables/sales/SalesOrderTable.tsx b/src/frontend/src/tables/sales/SalesOrderTable.tsx index 6276a1a7d4..3d97ed09b2 100644 --- a/src/frontend/src/tables/sales/SalesOrderTable.tsx +++ b/src/frontend/src/tables/sales/SalesOrderTable.tsx @@ -27,7 +27,8 @@ import { ShipmentDateColumn, StartDateColumn, StatusColumn, - TargetDateColumn + TargetDateColumn, + UpdatedAtColumn } from '../ColumnRenderers'; import { AssignedToMeFilter, @@ -48,7 +49,9 @@ import { StartDateAfterFilter, StartDateBeforeFilter, TargetDateAfterFilter, - TargetDateBeforeFilter + TargetDateBeforeFilter, + UpdatedAfterFilter, + UpdatedBeforeFilter } from '../Filter'; import { InvenTreeTable } from '../InvenTreeTable'; @@ -97,6 +100,8 @@ export function SalesOrderTable({ }, CompletedBeforeFilter(), CompletedAfterFilter(), + UpdatedBeforeFilter(), + UpdatedAfterFilter(), HasProjectCodeFilter(), ProjectCodeFilter(), ResponsibleFilter(), @@ -182,6 +187,9 @@ export function SalesOrderTable({ }), TargetDateColumn({}), ShipmentDateColumn({}), + UpdatedAtColumn({ + defaultVisible: false + }), ResponsibleColumn({}), { accessor: 'total_price',