Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
7 changes: 6 additions & 1 deletion src/backend/InvenTree/InvenTree/api_version.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down
11 changes: 11 additions & 0 deletions src/backend/InvenTree/order/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,14 @@ def filter_has_target_date(self, queryset, name, value):
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):
Expand Down Expand Up @@ -420,6 +428,7 @@ class PurchaseOrderList(
'responsible',
'total_price',
'project_code',
'updated_at',
]

ordering = '-reference'
Expand Down Expand Up @@ -882,6 +891,7 @@ class SalesOrderList(
'shipment_date',
'total_price',
'project_code',
'updated_at',
]

search_fields = [
Expand Down Expand Up @@ -1549,6 +1559,7 @@ class ReturnOrderList(
'target_date',
'complete_date',
'project_code',
'updated_at',
]

search_fields = [
Expand Down
Original file line number Diff line number Diff line change
@@ -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",
),
),
]
51 changes: 50 additions & 1 deletion src/backend/InvenTree/order/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
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 _
Expand Down Expand Up @@ -331,6 +331,8 @@ def save(self, *args, **kwargs):
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:
Expand Down Expand Up @@ -498,6 +500,13 @@ def is_overdue(self):
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,
Expand Down Expand Up @@ -3072,3 +3081,43 @@ def get_api_url():
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)
14 changes: 11 additions & 3 deletions src/backend/InvenTree/order/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,8 +373,14 @@ class Meta:
'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},
Expand Down Expand Up @@ -1026,8 +1032,9 @@ class Meta:
'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):
Expand Down Expand Up @@ -1918,8 +1925,9 @@ class Meta:
'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."""
Expand Down
Loading
Loading