SalesOrderShipment address (#10650)

* Adds "shipment_address" attribute to the SalesOrderShipment model:

- Allows different addresses for each shipment
- Defaults to the order shipment address (if not specified)

* Add unit testing for field validation

* Update SalesOrderShipment serializer

* Edit shipment address in UI

* Render date on shipment page

* Improve address rendering

* Update docs

* Bump API version

* Update CHANGELOG.md

* Fix API version
This commit is contained in:
Oliver
2025-10-23 16:37:43 +11:00
committed by GitHub
parent 754b2f2d66
commit ec33c57e85
13 changed files with 252 additions and 39 deletions
+32
View File
@@ -2137,6 +2137,7 @@ class SalesOrderShipmentReportContext(report.mixins.BaseReportContext):
Attributes:
allocations: QuerySet of SalesOrderAllocation objects
address: The shipping address for this shipment (or order)
order: The associated SalesOrder object
reference: Shipment reference string
shipment: The SalesOrderShipment object itself
@@ -2147,6 +2148,7 @@ class SalesOrderShipmentReportContext(report.mixins.BaseReportContext):
allocations: report.mixins.QuerySet['SalesOrderAllocation']
order: 'SalesOrder'
reference: str
address: 'Address'
shipment: 'SalesOrderShipment'
tracking_number: str
title: str
@@ -2168,6 +2170,7 @@ class SalesOrderShipment(
Attributes:
order: SalesOrder reference
shipment_address: Shipping address for this shipment (optional)
shipment_date: Date this shipment was "shipped" (or null)
checked_by: User reference field indicating who checked this order
reference: Custom reference text for this shipment (e.g. consignment number?)
@@ -2186,6 +2189,16 @@ class SalesOrderShipment(
unique_together = ['order', 'reference']
verbose_name = _('Sales Order Shipment')
def clean(self):
"""Custom clean method for the SalesOrderShipment class."""
super().clean()
if self.order and self.shipment_address:
if self.shipment_address.company != self.order.customer:
raise ValidationError({
'shipment_address': _('Shipment address must match the customer')
})
@staticmethod
def get_api_url():
"""Return the API URL associated with the SalesOrderShipment model."""
@@ -2196,6 +2209,7 @@ class SalesOrderShipment(
return {
'allocations': self.allocations,
'order': self.order,
'address': self.address,
'reference': self.reference,
'shipment': self,
'tracking_number': self.tracking_number,
@@ -2212,6 +2226,16 @@ class SalesOrderShipment(
help_text=_('Sales Order'),
)
shipment_address = models.ForeignKey(
Address,
on_delete=models.SET_NULL,
blank=True,
null=True,
verbose_name=_('Address'),
help_text=_('Shipping address for this shipment'),
related_name='+',
)
shipment_date = models.DateField(
null=True,
blank=True,
@@ -2267,6 +2291,14 @@ class SalesOrderShipment(
max_length=2000,
)
@property
def address(self) -> Address:
"""Return the shipping address for this shipment.
If no specific shipment address is assigned, return the address from the order.
"""
return self.shipment_address or self.order.address
def is_complete(self):
"""Return True if this shipment has already been completed."""
return self.shipment_date is not None