From 899ecc39e905f9975077f151fcfae9c9cab47141 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Wed, 24 Jun 2026 11:01:19 +0200 Subject: [PATCH] Update DPD API to Shipment Service 4.5 --- modules/stock_package_shipping_dpd/CHANGELOG | 1 + .../configuration.py | 2 +- .../stock_package_shipping_dpd/doc/index.rst | 2 +- modules/stock_package_shipping_dpd/stock.py | 92 +++++++++++++------ 4 files changed, 69 insertions(+), 28 deletions(-) diff --git a/modules/stock_package_shipping_dpd/CHANGELOG b/modules/stock_package_shipping_dpd/CHANGELOG index 76dff022df..cd8cc1433f 100644 --- a/modules/stock_package_shipping_dpd/CHANGELOG +++ b/modules/stock_package_shipping_dpd/CHANGELOG @@ -1,3 +1,4 @@ +* Update to Shipment Service 4.5 Version 8.0.0 - 2026-04-20 -------------------------- diff --git a/modules/stock_package_shipping_dpd/configuration.py b/modules/stock_package_shipping_dpd/configuration.py index 12e693a903..6db8ce20b0 100644 --- a/modules/stock_package_shipping_dpd/configuration.py +++ b/modules/stock_package_shipping_dpd/configuration.py @@ -13,7 +13,7 @@ SERVER_URLS = { } LOGIN_SERVICE = 'LoginService/V2_0/?wsdl' -SHIPMENT_SERVICE = 'ShipmentService/V4_4/?wsdl' +SHIPMENT_SERVICE = 'ShipmentService/V4_5/?wsdl' def get_client(server, service): diff --git a/modules/stock_package_shipping_dpd/doc/index.rst b/modules/stock_package_shipping_dpd/doc/index.rst index a0f0d63e55..dff8c5ff4f 100644 --- a/modules/stock_package_shipping_dpd/doc/index.rst +++ b/modules/stock_package_shipping_dpd/doc/index.rst @@ -6,7 +6,7 @@ using the DPD webservices. DPD has many different web services, the module supports: - `LoginService 2.0 `_ -- `ShipmentService 4.4 `_ +- `ShipmentService 4.5 `_ Carrier Credential ****************** diff --git a/modules/stock_package_shipping_dpd/stock.py b/modules/stock_package_shipping_dpd/stock.py index 952f02632d..2678662ed6 100644 --- a/modules/stock_package_shipping_dpd/stock.py +++ b/modules/stock_package_shipping_dpd/stock.py @@ -234,6 +234,9 @@ class CreateDPDShipping(Wizard): 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 @@ class CreateDPDShipping_Customs(metaclass=PoolMeta): '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 @@ class CreateDPDShipping_Customs(metaclass=PoolMeta): 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 @@ class CreateDPDShipping_Customs(metaclass=PoolMeta): 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, }