details:   https://code.tryton.org/tryton/commit/57e59f5abf45
branch:    default
user:      Cédric Krier <[email protected]>
date:      Wed Jun 24 11:01:19 2026 +0200
description:
        Update DPD API to Shipment Service 4.5
diffstat:

 modules/stock_package_shipping_dpd/CHANGELOG        |   1 +
 modules/stock_package_shipping_dpd/configuration.py |   2 +-
 modules/stock_package_shipping_dpd/doc/index.rst    |   2 +-
 modules/stock_package_shipping_dpd/stock.py         |  92 +++++++++++++++-----
 4 files changed, 69 insertions(+), 28 deletions(-)

diffs (176 lines):

diff -r 936431c951bb -r 57e59f5abf45 
modules/stock_package_shipping_dpd/CHANGELOG
--- a/modules/stock_package_shipping_dpd/CHANGELOG      Mon Jul 06 09:07:08 
2026 +0200
+++ b/modules/stock_package_shipping_dpd/CHANGELOG      Wed Jun 24 11:01:19 
2026 +0200
@@ -1,3 +1,4 @@
+* Update to Shipment Service 4.5
 
 Version 8.0.0 - 2026-04-20
 --------------------------
diff -r 936431c951bb -r 57e59f5abf45 
modules/stock_package_shipping_dpd/configuration.py
--- a/modules/stock_package_shipping_dpd/configuration.py       Mon Jul 06 
09:07:08 2026 +0200
+++ b/modules/stock_package_shipping_dpd/configuration.py       Wed Jun 24 
11:01:19 2026 +0200
@@ -13,7 +13,7 @@
     }
 
 LOGIN_SERVICE = 'LoginService/V2_0/?wsdl'
-SHIPMENT_SERVICE = 'ShipmentService/V4_4/?wsdl'
+SHIPMENT_SERVICE = 'ShipmentService/V4_5/?wsdl'
 
 
 def get_client(server, service):
diff -r 936431c951bb -r 57e59f5abf45 
modules/stock_package_shipping_dpd/doc/index.rst
--- a/modules/stock_package_shipping_dpd/doc/index.rst  Mon Jul 06 09:07:08 
2026 +0200
+++ b/modules/stock_package_shipping_dpd/doc/index.rst  Wed Jun 24 11:01:19 
2026 +0200
@@ -6,7 +6,7 @@
 DPD has many different web services, the module supports:
 
 - `LoginService 2.0 
<https://esolutions.dpd.com/dokumente/LoginService_V2_0.pdf>`_
-- `ShipmentService 4.4 
<https://esolutions.dpd.com/dokumente/ShipmentService_V4_4.pdf>`_
+- `ShipmentService 4.5 
<https://esolutions.dpd.com/dokumente/ShipmentService_V4_5.pdf>`_
 
 Carrier Credential
 ******************
diff -r 936431c951bb -r 57e59f5abf45 modules/stock_package_shipping_dpd/stock.py
--- a/modules/stock_package_shipping_dpd/stock.py       Mon Jul 06 09:07:08 
2026 +0200
+++ b/modules/stock_package_shipping_dpd/stock.py       Wed Jun 24 11:01:19 
2026 +0200
@@ -234,6 +234,9 @@
         mobile = address.contact_mechanism_get('mobile', usage=usage)
         if mobile and len(mobile.value) <= 30:
             shipping_party['mobile'] = mobile.value
+        fax = address.contact_mechanism_get('fax', usage=usage)
+        if fax and len(fax.value) <= 30:
+            shipping_party['fax'] = fax.value
         email = address.contact_mechanism_get('email', usage=usage)
         if email and 5 <= len(email.value) <= 100:
             shipping_party['email'] = email.value
@@ -376,25 +379,30 @@
                 'customsAmount': int(
                     amount.quantize(Decimal('.01')) * Decimal(100)),
                 'customsCurrency': currency.code,
-                'customsTerms': self.get_customs_terms(shipment),
                 'customsPaper': 'A',
                 'customsInvoice': shipment.number[:20],
                 'customsInvoiceDate': invoice_date.strftime('%Y%m%d'),
+                'reasonForExport': self.get_international_form_invoice_reason(
+                    shipment),
                 }
             if customs_agent := shipment.customs_agent:
-                international.update({
-                        'commercialInvoiceConsigneeVatNumber': (
-                            customs_agent.tax_identifier.code_compact)[:20],
-                        'commercialInvoiceConsignee': self.shipping_party(
-                            customs_agent.party,
-                            customs_agent.address,
-                            with_contact=True),
-                        })
-            if shipment.tax_identifier:
-                international['commercialInvoiceConsignorVatNumber'] = (
-                    shipment.tax_identifier.code_compact[:17])
-            international['commercialInvoiceConsignor'] = self.shipping_party(
-                shipment.company.party, shipment.customs_from_address)
+                consignee = self.shipping_party(
+                    customs_agent.party,
+                    customs_agent.address,
+                    with_contact=True)
+                consignee['vatNumber'] = (
+                    customs_agent.tax_identifier.code_compact[:20])
+                international['commercialInvoiceConsignee'] = consignee
+            if company_party := shipment.company_party:
+                consignor = self.shipping_party(
+                    company_party,
+                    shipment.customs_from_address,
+                    with_contact=True)
+                if (shipment.tax_identifier
+                        and shipment.tax_identifier.type == 'eu_vat'):
+                    consignor['eori'] = (
+                        shipment.tax_identifier.code_compact[:20])
+                international['commercialInvoiceConsignor'] = consignor
             international['additionalInvoiceLines'] = [
                 {'customsInvoicePosition': i,
                     **self.get_international_invoice_line(shipment, *k, **v)}
@@ -402,26 +410,47 @@
                     shipment.customs_products.items(), 1)]
             international['numberOfArticle'] = len(
                 international['additionalInvoiceLines'])
+            international['clearanceCleared'] = 'F'
+            if shipment.incoterm:
+                international['incoterm'] = shipment.incoterm.code
+            if shipment.incoterm_location:
+                if shipment.incoterm_location.city:
+                    international['incotermLocationTown'] = (
+                        shipment.incoterm_location.city)
+                if shipment.incoterm_location.country:
+                    international['incotermLocationCountryCode'] = (
+                        shipment.incoterm_location.country.code)
+            international['paymentTerm'] = self.get_payment_terms(shipment)
             product_and_service['international'] = international
         return product_and_service
 
-    def get_customs_terms(self, shipment):
+    def get_payment_terms(self, shipment):
         if shipment and shipment.incoterm:
-            if shipment.incoterm.code == 'DAP':
-                return '01'
-            elif shipment.incoterm.code == 'DDP':
-                return '03'
-            elif shipment.incoterm.code == 'EXW':
-                return '05'
+            if shipment.incoterm.import_duties == 'buyer':
+                return 'A'
+            elif shipment.incoterm.import_duties == 'seller':
+                return 'B'
+
+    def get_international_form_invoice_reason(self, shipment):
+        return {
+            'stock.shipment.out': '01',
+            'stock.shipment.in.return': '02',
+            }.get(shipment.__class__.__name__)
 
     def get_international_invoice_line(
             self, shipment, product, price, currency, unit, quantity, weight):
-        tariff_code = product.get_tariff_code({
+        recipient_tarrif_code = product.get_tariff_code({
                 'date': shipment.effective_date or shipment.planned_date,
                 'country': (
                     shipment.customs_to_country.id
                     if shipment.customs_to_country else None),
                 })
+        sender_tarrif_code = product.get_tariff_code({
+                'date': shipment.effective_date or shipment.planned_date,
+                'country': (
+                    shipment.customs_from_country.id
+                    if shipment.customs_from_country else None),
+                })
 
         weight = round(weight, 2)
 
@@ -429,14 +458,25 @@
         if not quantity.is_integer():
             quantity = 1
 
+        quantity = int(quantity)
+        amount = int(
+            value.quantize(Decimal('.01')) * Decimal(100))
+        unit_price = amount // quantity
+
         return {
-            'quantityItems': int(quantity),
+            'quantityItems': quantity,
             'customsContent': product.name[:200],
-            'customsTarif': tariff_code.code if tariff_code else None,
-            'customsAmountLine': int(
-                value.quantize(Decimal('.01')) * Decimal(100)),
+            'customsAmountLine': amount,
             'customsOrigin': (
                 product.country_of_origin.code_numeric
                 if product.country_of_origin else None),
+            'customsOriginAlphanumeric': (
+                product.country_of_origin.code
+                if product.country_of_origin else None),
             'customsGrossWeight': int(weight * 100),
+            'recipientCountryTarif': (
+                recipient_tarrif_code.code if recipient_tarrif_code else None),
+            'senderCountryTarif': (
+                sender_tarrif_code.code if sender_tarrif_code else None),
+            'unitPrice': unit_price,
             }

Reply via email to