details: https://code.tryton.org/tryton/commit/cf2851caba68
branch: default
user: Cédric Krier <[email protected]>
date: Wed Nov 05 17:18:15 2025 +0100
description:
Generate a default shipping description for the shipment
Also for UPS, it fallbacks to "General Merchandise" if the shipping
description
is empty.
Closes #14351
diffstat:
modules/stock_package_shipping/CHANGELOG | 1 +
modules/stock_package_shipping/stock.py | 33 ++++++++++++++-
modules/stock_package_shipping/tests/test_module.py | 48 +++++++++++++++++++++
modules/stock_package_shipping/tryton.cfg | 2 +
modules/stock_package_shipping_ups/CHANGELOG | 1 +
modules/stock_package_shipping_ups/message.xml | 4 +-
modules/stock_package_shipping_ups/stock.py | 13 +---
7 files changed, 90 insertions(+), 12 deletions(-)
diffs (196 lines):
diff -r cd3931840db9 -r cf2851caba68 modules/stock_package_shipping/CHANGELOG
--- a/modules/stock_package_shipping/CHANGELOG Thu Nov 20 17:03:46 2025 +0100
+++ b/modules/stock_package_shipping/CHANGELOG Wed Nov 05 17:18:15 2025 +0100
@@ -1,3 +1,4 @@
+* Generate a default shipping description based on custom categories
Version 7.6.0 - 2025-04-28
--------------------------
diff -r cd3931840db9 -r cf2851caba68 modules/stock_package_shipping/stock.py
--- a/modules/stock_package_shipping/stock.py Thu Nov 20 17:03:46 2025 +0100
+++ b/modules/stock_package_shipping/stock.py Wed Nov 05 17:18:15 2025 +0100
@@ -96,6 +96,18 @@
pass
+def lowest_common_root(paths):
+ min_length = min((len(p) for p in paths), default=0)
+ common = None
+ for i in range(min_length):
+ level_values = {p[i] for p in paths}
+ if len(level_values) == 1:
+ common = level_values.pop()
+ else:
+ break
+ return common
+
+
class ShippingMixin:
__slots__ = ()
@@ -107,7 +119,8 @@
shipping_description = fields.Char('Shipping Description',
states={
'readonly': Eval('state').in_(['done', 'packed'])
- })
+ },
+ help="Leave empty to use the generated description.")
has_shipping_service = fields.Function(
fields.Boolean("Has Shipping Service"),
'on_change_with_has_shipping_service')
@@ -148,6 +161,24 @@
[table.shipping_reference],
[table.reference]))
+ @property
+ def shipping_description_used(self):
+ pool = Pool()
+ Product = pool.get('product.product')
+ description = self.shipping_description
+ if not description and hasattr(Product, 'customs_category'):
+ def parents(category):
+ if not category:
+ return
+ yield from parents(category.parent)
+ yield category
+ products = {
+ m.product for m in self.moves if m.product.customs_category}
+ paths = [list(parents(p.customs_category)) for p in products]
+ if category := lowest_common_root(paths):
+ description = category.name
+ return description
+
@fields.depends('carrier')
def on_change_with_has_shipping_service(self, name=None):
return bool(self.carrier and self.carrier.shipping_service)
diff -r cd3931840db9 -r cf2851caba68
modules/stock_package_shipping/tests/test_module.py
--- a/modules/stock_package_shipping/tests/test_module.py Thu Nov 20
17:03:46 2025 +0100
+++ b/modules/stock_package_shipping/tests/test_module.py Wed Nov 05
17:18:15 2025 +0100
@@ -1,6 +1,9 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
+import unittest
+
+from trytond.modules.stock_package_shipping.stock import lowest_common_root
from trytond.tests.test_tryton import ModuleTestCase
@@ -9,4 +12,49 @@
module = 'stock_package_shipping'
+class testLowestCommonRoot(unittest.TestCase):
+
+ def test_simple_common_root(self):
+ "Test simple common root"
+ paths = [
+ ["Apparel", "Men", "Shirts"],
+ ["Apparel", "Men", "Pants"]
+ ]
+ self.assertEqual(lowest_common_root(paths), "Men")
+
+ def test_root_only_common(self):
+ "Test no common"
+ paths = [
+ ["Root", "Apparel", "Men", "Shirts"],
+ ["Root", "Electronics", "Mobile"]
+ ]
+ self.assertEqual(lowest_common_root(paths), "Root")
+
+ def test_identical_paths(self):
+ "Test identical paths"
+ paths = [
+ ["Apparel", "Men", "Shirts"],
+ ["Apparel", "Men", "Shirts"]
+ ]
+ self.assertEqual(lowest_common_root(paths), "Shirts")
+
+ def test_single_path(self):
+ "Test single path"
+ paths = [["Apparel", "Men", "Shirts"]]
+ self.assertEqual(lowest_common_root(paths), "Shirts")
+
+ def test_empty_paths_list(self):
+ "Test empty paths list"
+ paths = []
+ self.assertIsNone(lowest_common_root(paths))
+
+ def test_no_common_root(self):
+ "Test no common root"
+ paths = [
+ ["Apparel", "Men"],
+ ["Electronics", "Mobile"]
+ ]
+ self.assertIsNone(lowest_common_root(paths))
+
+
del ModuleTestCase
diff -r cd3931840db9 -r cf2851caba68 modules/stock_package_shipping/tryton.cfg
--- a/modules/stock_package_shipping/tryton.cfg Thu Nov 20 17:03:46 2025 +0100
+++ b/modules/stock_package_shipping/tryton.cfg Wed Nov 05 17:18:15 2025 +0100
@@ -10,6 +10,8 @@
stock_shipment_measurements
stock_shipment_cost
product_measurements
+extras_depend:
+ customs
xml:
carrier.xml
stock.xml
diff -r cd3931840db9 -r cf2851caba68
modules/stock_package_shipping_ups/CHANGELOG
--- a/modules/stock_package_shipping_ups/CHANGELOG Thu Nov 20 17:03:46
2025 +0100
+++ b/modules/stock_package_shipping_ups/CHANGELOG Wed Nov 05 17:18:15
2025 +0100
@@ -1,3 +1,4 @@
+* Use "General Merchandise" as fallback shipping description
Version 7.6.0 - 2025-04-28
--------------------------
diff -r cd3931840db9 -r cf2851caba68
modules/stock_package_shipping_ups/message.xml
--- a/modules/stock_package_shipping_ups/message.xml Thu Nov 20 17:03:46
2025 +0100
+++ b/modules/stock_package_shipping_ups/message.xml Wed Nov 05 17:18:15
2025 +0100
@@ -16,8 +16,8 @@
<record model="ir.message" id="msg_phone_required">
<field name="text">To validate shipment "%(shipment)s" you must
add phone number for address "%(address)s".</field>
</record>
- <record model="ir.message" id="msg_shipping_description_required">
- <field name="text">To validate shipment "%(shipment)s" you must
fill its shipping description.</field>
+ <record model="ir.message" id="msg_general_merchandise">
+ <field name="text">General Merchandise</field>
</record>
<record model="ir.message" id="msg_shipment_has_reference_number">
<field name="text">You cannot create shipping for shipment
"%(shipment)s" because it has already a reference number.</field>
diff -r cd3931840db9 -r cf2851caba68 modules/stock_package_shipping_ups/stock.py
--- a/modules/stock_package_shipping_ups/stock.py Thu Nov 20 17:03:46
2025 +0100
+++ b/modules/stock_package_shipping_ups/stock.py Wed Nov 05 17:18:15
2025 +0100
@@ -105,14 +105,6 @@
'.msg_phone_required',
shipment=self.rec_name,
address=address.rec_name))
- if not self.shipping_description:
- if (any(p.type.ups_code != '01' for p in self.root_packages)
- and self.carrier.ups_service_type != '11'):
- # TODO Should also test if a country is not in the EU
- raise PackingValidationError(
- gettext('stock_package_shipping_ups'
- '.msg_shipping_description_required',
- shipment=self.rec_name))
class CreateShipping(metaclass=PoolMeta):
@@ -375,11 +367,14 @@
# despite what UPS documentation says
for pkg in packages:
pkg['ShipmentServiceOptions'] = options
+ description = (
+ shipment.shipping_description_used
+ or gettext('stock_package_shipping_ups.msg_general_merchandise'))
return {
'ShipmentRequest': {
'Request': self.get_request_container(shipment),
'Shipment': {
- 'Description': (shipment.shipping_description or '')[:50],
+ 'Description': description[:50],
'Shipper': shipper,
'ShipTo': self.get_shipping_party(
shipment.shipping_to, shipment.shipping_to_address),