From 469502860dd84bdbbf0074042474f4ec9315b733 Mon Sep 17 00:00:00 2001 From: Jan Suhr Date: Mon, 17 Feb 2025 09:21:12 +0100 Subject: [PATCH] [MIG] delivery_ups_oca: Migration to 15.0 --- delivery_ups_oca/README.rst | 137 +++++ delivery_ups_oca/__init__.py | 3 + delivery_ups_oca/__manifest__.py | 25 + .../data/product_packaging_data.xml | 120 +++++ delivery_ups_oca/i18n/delivery_ups_oca.pot | 470 +++++++++++++++++ delivery_ups_oca/i18n/it.po | 471 +++++++++++++++++ delivery_ups_oca/models/__init__.py | 5 + delivery_ups_oca/models/delivery_carrier.py | 217 ++++++++ delivery_ups_oca/models/product_packaging.py | 14 + delivery_ups_oca/models/stock_picking.py | 14 + delivery_ups_oca/models/ups_request.py | 418 +++++++++++++++ delivery_ups_oca/readme/CONFIGURE.rst | 16 + delivery_ups_oca/readme/CONTRIBUTORS.rst | 13 + delivery_ups_oca/readme/DESCRIPTION.rst | 9 + delivery_ups_oca/readme/ROADMAP.rst | 2 + delivery_ups_oca/readme/USAGE.rst | 9 + delivery_ups_oca/static/description/icon.png | Bin 0 -> 38594 bytes .../static/description/index.html | 483 ++++++++++++++++++ delivery_ups_oca/tests/__init__.py | 3 + delivery_ups_oca/tests/test_delivery_ups.py | 166 ++++++ .../views/delivery_carrier_view.xml | 83 +++ delivery_ups_oca/views/stock_picking_view.xml | 27 + .../odoo/addons/delivery_ups_oca | 1 + setup/delivery_ups_oca/setup.py | 6 + 24 files changed, 2712 insertions(+) create mode 100644 delivery_ups_oca/README.rst create mode 100644 delivery_ups_oca/__init__.py create mode 100644 delivery_ups_oca/__manifest__.py create mode 100644 delivery_ups_oca/data/product_packaging_data.xml create mode 100644 delivery_ups_oca/i18n/delivery_ups_oca.pot create mode 100644 delivery_ups_oca/i18n/it.po create mode 100644 delivery_ups_oca/models/__init__.py create mode 100644 delivery_ups_oca/models/delivery_carrier.py create mode 100644 delivery_ups_oca/models/product_packaging.py create mode 100644 delivery_ups_oca/models/stock_picking.py create mode 100644 delivery_ups_oca/models/ups_request.py create mode 100644 delivery_ups_oca/readme/CONFIGURE.rst create mode 100644 delivery_ups_oca/readme/CONTRIBUTORS.rst create mode 100644 delivery_ups_oca/readme/DESCRIPTION.rst create mode 100644 delivery_ups_oca/readme/ROADMAP.rst create mode 100644 delivery_ups_oca/readme/USAGE.rst create mode 100644 delivery_ups_oca/static/description/icon.png create mode 100644 delivery_ups_oca/static/description/index.html create mode 100644 delivery_ups_oca/tests/__init__.py create mode 100644 delivery_ups_oca/tests/test_delivery_ups.py create mode 100644 delivery_ups_oca/views/delivery_carrier_view.xml create mode 100644 delivery_ups_oca/views/stock_picking_view.xml create mode 120000 setup/delivery_ups_oca/odoo/addons/delivery_ups_oca create mode 100644 setup/delivery_ups_oca/setup.py diff --git a/delivery_ups_oca/README.rst b/delivery_ups_oca/README.rst new file mode 100644 index 0000000000..e32231c206 --- /dev/null +++ b/delivery_ups_oca/README.rst @@ -0,0 +1,137 @@ +================ +Delivery UPS OCA +================ + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:a89efe46304fac9cd47e1f827c23f8d351066a71d4ca44f577e20d37862377a8 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fdelivery--carrier-lightgray.png?logo=github + :target: https://github.com/OCA/delivery-carrier/tree/14.0/delivery_ups_oca + :alt: OCA/delivery-carrier +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/delivery-carrier-14-0/delivery-carrier-14-0-delivery_ups_oca + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/delivery-carrier&target_branch=14.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module adds `UPS `_ to the available carriers. + +It allows you to register shippings, generate labels, get rates from order, read +shipping states and cancel shipments using UPS webservice, so no need of exchanging +any kind of file. + +When a sales order is created in Odoo and the UPS carrier is assigned, the shipping +price that will be obtained will be the price that the UPS webservice estimates +according to the order information (address and products). + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +To configure this module, you need to: + +#. Add a carrier account with delivery type ``ups`` and fill in your credentials (UPS + Client and UPS Client Secret) +#. Configure in Odoo all required fields of the UPS tab with your account data + https://wwwapps.ups.com/ppc/ppc.html (Shipper number, Default Packaging, Package + Dimension Code, Package Weight Code and File Format). +#. If yo have "Tracking state update sync" checked all delivery orders state check will + be done querying UPS services. +#. It is possible to create a UPS carrier for cash on delivery parcels. Select the + ``ups`` delivery type and check the "Cash on Delivery" checkbox under the "UPS" tab. + It is required to select the "UPS COD Funds Code" when the "Cash on Delivery" option + is selected. + +**NOTE** You need to add an APP from https://developer.ups.com/ for using the +webservice. + +Usage +===== + +You have to set the created shipping method in the delivery order to ship: + +* When the picking is 'Transferred', a *Create Shipping Label* button appears. Just + click on it, and if all went well, the label will be 'attached'. +* If the shipment creation process fails, a validation error will appear displaying UPS + error. +* When the delivery order is cancelled, it's automatically cancelled too in UPS. +* If you have "Tracking state update sync" checked in the shipping method, a periodical + state check will be done querying UPS services. + +Known issues / Roadmap +====================== + +* Support international forms +* Support package service options + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Hunki Enterprises BV +* Tecnativa +* ForgeFlow + +Contributors +~~~~~~~~~~~~ + +* Holger Brunn (https://hunki-enterprises.nl) +* `Tecnativa `_: + + * Víctor Martínez + * Pedro M. Baeza + +* `ForgeFlow `_: + + * Jordi Ballester + +* `Sygel `_: + + * Manuel Regidor + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/delivery-carrier `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/delivery_ups_oca/__init__.py b/delivery_ups_oca/__init__.py new file mode 100644 index 0000000000..31660d6a96 --- /dev/null +++ b/delivery_ups_oca/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import models diff --git a/delivery_ups_oca/__manifest__.py b/delivery_ups_oca/__manifest__.py new file mode 100644 index 0000000000..84db2bb8dd --- /dev/null +++ b/delivery_ups_oca/__manifest__.py @@ -0,0 +1,25 @@ +# Copyright 2020 Hunki Enterprises BV +# Copyright 2021-2022 Tecnativa - Víctor Martínez +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +{ + "name": "Delivery UPS OCA", + "summary": "Integrate UPS webservice", + "version": "15.0.1.0.1", + "development_status": "Beta", + "category": "Delivery", + "website": "https://github.com/OCA/delivery-carrier", + "author": "Hunki Enterprises BV, Tecnativa, ForgeFlow, Odoo Community Association (OCA)", + "license": "AGPL-3", + "excludes": ["delivery_ups"], + "depends": [ + "delivery", + "delivery_package_number", + "delivery_price_method", + "delivery_state", + ], + "data": [ + "data/product_packaging_data.xml", + "views/delivery_carrier_view.xml", + "views/stock_picking_view.xml", + ], +} diff --git a/delivery_ups_oca/data/product_packaging_data.xml b/delivery_ups_oca/data/product_packaging_data.xml new file mode 100644 index 0000000000..339c120c2f --- /dev/null +++ b/delivery_ups_oca/data/product_packaging_data.xml @@ -0,0 +1,120 @@ + + + + + 01 + UPS Letter + ups + + + 02 + Customer Supplied Package + ups + + + 03 + Tube + ups + + + 04 + PAK + ups + + + 21 + UPS Express Box + ups + + + 24 + UPS 25KG Box + ups + + + 25 + UPS 10KG Box + ups + + + 30 + Pallet + ups + + + 2a + Small Express Box + ups + + + 2b + Medium Express Box + ups + + + 2c + Large Express Box + ups + + + 56 + Flats + ups + + + 57 + Parcels + ups + + + 58 + BPM + ups + + + 59 + First Class + ups + + + 60 + Priority + ups + + + 61 + Machineables + ups + + + 62 + Irregulars + ups + + + 63 + Parcel Post + ups + + + 64 + BPM Parcel + ups + + + 65 + Media Mail + ups + + + 66 + BPM Flat + ups + + + 67 + Standard Flat + ups + + diff --git a/delivery_ups_oca/i18n/delivery_ups_oca.pot b/delivery_ups_oca/i18n/delivery_ups_oca.pot new file mode 100644 index 0000000000..5ade48e4f7 --- /dev/null +++ b/delivery_ups_oca/i18n/delivery_ups_oca.pot @@ -0,0 +1,470 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * delivery_ups_oca +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__02 +msgid "2nd Day Air" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__59 +msgid "2nd Day Air A.M." +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__12 +msgid "3 Day Select" +msgstr "" + +#. module: delivery_ups_oca +#: code:addons/delivery_ups_oca/models/ups_request.py:0 +#, python-format +msgid "Both Client ID and Client Secret must be set in UPS delivery carriers." +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_package_dimension_code__cm +msgid "CM" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,field_description:delivery_ups_oca.field_product_packaging__package_carrier_type +msgid "Carrier" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_cod_funds_code__1 +msgid "Cash" +msgstr "" + +#. module: delivery_ups_oca +#: model_terms:ir.ui.view,arch_db:delivery_ups_oca.view_delivery_carrier_form +msgid "Cash On Delivery" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_cod_funds_code__9 +msgid "Check/Cashier Check/Money Order" +msgstr "" + +#. module: delivery_ups_oca +#: model_terms:ir.ui.view,arch_db:delivery_ups_oca.view_delivery_carrier_form +msgid "Credentials" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,field_description:delivery_ups_oca.field_delivery_carrier__ups_default_packaging_id +msgid "Default Packaging Type" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,field_description:delivery_ups_oca.field_delivery_carrier__display_name +#: model:ir.model.fields,field_description:delivery_ups_oca.field_product_packaging__display_name +#: model:ir.model.fields,field_description:delivery_ups_oca.field_stock_picking__display_name +msgid "Display Name" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_file_format__epl +msgid "EPL" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__m6 +msgid "Economy Mail Innovations" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__08 +msgid "Expedited" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__m4 +msgid "Expedited Mail Innovations" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__07 +msgid "Express" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__54 +msgid "Express Plus" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,field_description:delivery_ups_oca.field_delivery_carrier__ups_file_format +msgid "File format" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__m2 +msgid "First Class Mail" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_file_format__gif +msgid "GIF" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__03 +msgid "Ground" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,field_description:delivery_ups_oca.field_delivery_carrier__id +#: model:ir.model.fields,field_description:delivery_ups_oca.field_product_packaging__id +#: model:ir.model.fields,field_description:delivery_ups_oca.field_stock_picking__id +msgid "ID" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_package_dimension_code__in +msgid "IN" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,help:delivery_ups_oca.field_delivery_carrier__ups_tracking_state_update_sync +msgid "" +"If checked, odoo try to state update from picking according to UPS " +"webservice (you will necessary to activate tracking API)" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,help:delivery_ups_oca.field_delivery_carrier__ups_package_dimension_code +msgid "" +"Is necessary to set dimension code from packages in shipping creation " +"process" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,help:delivery_ups_oca.field_delivery_carrier__ups_package_weight_code +msgid "" +"Is necessary to set weight code from packages in shipping creation process" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_package_weight_code__kgs +msgid "KGS" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_package_weight_code__lbs +msgid "LBS" +msgstr "" + +#. module: delivery_ups_oca +#: model_terms:ir.ui.view,arch_db:delivery_ups_oca.view_delivery_carrier_form +msgid "Label" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,field_description:delivery_ups_oca.field_delivery_carrier____last_update +#: model:ir.model.fields,field_description:delivery_ups_oca.field_product_packaging____last_update +#: model:ir.model.fields,field_description:delivery_ups_oca.field_stock_picking____last_update +msgid "Last Modified on" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__m7 +msgid "Mail Innovations (MI) Returns" +msgstr "" + +#. module: delivery_ups_oca +#: model_terms:ir.ui.view,arch_db:delivery_ups_oca.view_delivery_carrier_form +msgid "Misc" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__01 +msgid "Next Day Air" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__13 +msgid "Next Day Air Saver" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_package_weight_code__ozs +msgid "OZS" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,field_description:delivery_ups_oca.field_delivery_carrier__ups_package_dimension_code +msgid "Package Dimension code" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,field_description:delivery_ups_oca.field_delivery_carrier__ups_package_weight_code +msgid "Package Weight code" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__m3 +msgid "Priority Mail" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__m5 +msgid "Priority Mail Innovations" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model,name:delivery_ups_oca.model_product_packaging +msgid "Product Packaging" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,field_description:delivery_ups_oca.field_delivery_carrier__delivery_type +msgid "Provider" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_file_format__spl +msgid "SPL" +msgstr "" + +#. module: delivery_ups_oca +#: code:addons/delivery_ups_oca/models/ups_request.py:0 +#, python-format +msgid "Sending to UPS: %s" +msgstr "" + +#. module: delivery_ups_oca +#: model_terms:ir.ui.view,arch_db:delivery_ups_oca.view_delivery_carrier_form +msgid "Service" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,field_description:delivery_ups_oca.field_delivery_carrier__ups_service_code +msgid "Service code" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,field_description:delivery_ups_oca.field_delivery_carrier__ups_shipper_number +msgid "Shipper number" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model,name:delivery_ups_oca.model_delivery_carrier +msgid "Shipping Methods" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,field_description:delivery_ups_oca.field_delivery_carrier__ups_tracking_state_update_sync +msgid "Tracking state update sync" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model,name:delivery_ups_oca.model_stock_picking +msgid "Transfer" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__delivery_type__ups +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__product_packaging__package_carrier_type__ups +#: model_terms:ir.ui.view,arch_db:delivery_ups_oca.view_delivery_carrier_form +msgid "UPS" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__70 +msgid "UPS Access PointTM Economy" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,field_description:delivery_ups_oca.field_delivery_carrier__ups_cash_on_delivery +msgid "UPS Cash On Delivery" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,field_description:delivery_ups_oca.field_delivery_carrier__ups_cod_funds_code +msgid "UPS Cod Funds Code" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__74 +msgid "UPS Express®12:00" +msgstr "" + +#. module: delivery_ups_oca +#: model_terms:ir.ui.view,arch_db:delivery_ups_oca.view_picking_withcarrier_out_form +msgid "UPS Label" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__14 +msgid "UPS Next Day Air® Early" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__65 +msgid "UPS Saver" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__11 +msgid "UPS Standard" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__83 +msgid "UPS Today Dedicated Courier" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__85 +msgid "UPS Today Express" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__86 +msgid "UPS Today Express Saver" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__84 +msgid "UPS Today Intercity" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__82 +msgid "UPS Today Standard" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__72 +msgid "UPS Worldwide Economy" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__17 +msgid "UPS Worldwide Economy DDU" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__96 +msgid "UPS Worldwide Express Freight" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__71 +msgid "UPS Worldwide Express Freight Midday" +msgstr "" + +#. module: delivery_ups_oca +#: model_terms:ir.ui.view,arch_db:delivery_ups_oca.view_delivery_carrier_form +msgid "Update Token" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,field_description:delivery_ups_oca.field_delivery_carrier__ups_client_id +msgid "Ups Client" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,field_description:delivery_ups_oca.field_delivery_carrier__ups_client_secret +msgid "Ups Client Secret" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,field_description:delivery_ups_oca.field_delivery_carrier__ups_token +msgid "Ups Token" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,field_description:delivery_ups_oca.field_delivery_carrier__ups_token_expiration_date +msgid "Ups Token Expiration Date" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,field_description:delivery_ups_oca.field_delivery_carrier__ups_use_packages_from_picking +msgid "Use packages from picking" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_file_format__zpl +msgid "ZPL" +msgstr "" + +#. module: delivery_ups_oca +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_01 +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_02 +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_03 +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_04 +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_21 +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_24 +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_25 +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_2a +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_2b +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_2c +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_30 +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_56 +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_57 +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_58 +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_59 +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_60 +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_61 +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_62 +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_63 +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_64 +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_65 +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_66 +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_67 +msgid "kg" +msgstr "" + +#. module: delivery_ups_oca +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_01 +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_02 +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_03 +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_04 +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_21 +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_24 +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_25 +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_2a +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_2b +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_2c +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_30 +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_56 +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_57 +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_58 +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_59 +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_60 +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_61 +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_62 +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_63 +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_64 +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_65 +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_66 +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_67 +msgid "m³" +msgstr "" + +#. module: delivery_ups_oca +#: code:addons/delivery_ups_oca/models/ups_request.py:0 +#, python-format +msgid "{} - Error retrieving the tracking information." +msgstr "" + +#. module: delivery_ups_oca +#: code:addons/delivery_ups_oca/models/ups_request.py:0 +#, python-format +msgid "{} - Warning: {}" +msgstr "" diff --git a/delivery_ups_oca/i18n/it.po b/delivery_ups_oca/i18n/it.po new file mode 100644 index 0000000000..8b36d716dc --- /dev/null +++ b/delivery_ups_oca/i18n/it.po @@ -0,0 +1,471 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * delivery_ups_oca +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__02 +msgid "2nd Day Air" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__59 +msgid "2nd Day Air A.M." +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__12 +msgid "3 Day Select" +msgstr "" + +#. module: delivery_ups_oca +#: code:addons/delivery_ups_oca/models/ups_request.py:0 +#, python-format +msgid "Both Client ID and Client Secret must be set in UPS delivery carriers." +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_package_dimension_code__cm +msgid "CM" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,field_description:delivery_ups_oca.field_product_packaging__package_carrier_type +msgid "Carrier" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_cod_funds_code__1 +msgid "Cash" +msgstr "" + +#. module: delivery_ups_oca +#: model_terms:ir.ui.view,arch_db:delivery_ups_oca.view_delivery_carrier_form +msgid "Cash On Delivery" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_cod_funds_code__9 +msgid "Check/Cashier Check/Money Order" +msgstr "" + +#. module: delivery_ups_oca +#: model_terms:ir.ui.view,arch_db:delivery_ups_oca.view_delivery_carrier_form +msgid "Credentials" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,field_description:delivery_ups_oca.field_delivery_carrier__ups_default_packaging_id +msgid "Default Packaging Type" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,field_description:delivery_ups_oca.field_delivery_carrier__display_name +#: model:ir.model.fields,field_description:delivery_ups_oca.field_product_packaging__display_name +#: model:ir.model.fields,field_description:delivery_ups_oca.field_stock_picking__display_name +msgid "Display Name" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_file_format__epl +msgid "EPL" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__m6 +msgid "Economy Mail Innovations" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__08 +msgid "Expedited" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__m4 +msgid "Expedited Mail Innovations" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__07 +msgid "Express" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__54 +msgid "Express Plus" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,field_description:delivery_ups_oca.field_delivery_carrier__ups_file_format +msgid "File format" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__m2 +msgid "First Class Mail" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_file_format__gif +msgid "GIF" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__03 +msgid "Ground" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,field_description:delivery_ups_oca.field_delivery_carrier__id +#: model:ir.model.fields,field_description:delivery_ups_oca.field_product_packaging__id +#: model:ir.model.fields,field_description:delivery_ups_oca.field_stock_picking__id +msgid "ID" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_package_dimension_code__in +msgid "IN" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,help:delivery_ups_oca.field_delivery_carrier__ups_tracking_state_update_sync +msgid "" +"If checked, odoo try to state update from picking according to UPS " +"webservice (you will necessary to activate tracking API)" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,help:delivery_ups_oca.field_delivery_carrier__ups_package_dimension_code +msgid "" +"Is necessary to set dimension code from packages in shipping creation " +"process" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,help:delivery_ups_oca.field_delivery_carrier__ups_package_weight_code +msgid "" +"Is necessary to set weight code from packages in shipping creation process" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_package_weight_code__kgs +msgid "KGS" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_package_weight_code__lbs +msgid "LBS" +msgstr "" + +#. module: delivery_ups_oca +#: model_terms:ir.ui.view,arch_db:delivery_ups_oca.view_delivery_carrier_form +msgid "Label" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,field_description:delivery_ups_oca.field_delivery_carrier____last_update +#: model:ir.model.fields,field_description:delivery_ups_oca.field_product_packaging____last_update +#: model:ir.model.fields,field_description:delivery_ups_oca.field_stock_picking____last_update +msgid "Last Modified on" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__m7 +msgid "Mail Innovations (MI) Returns" +msgstr "" + +#. module: delivery_ups_oca +#: model_terms:ir.ui.view,arch_db:delivery_ups_oca.view_delivery_carrier_form +msgid "Misc" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__01 +msgid "Next Day Air" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__13 +msgid "Next Day Air Saver" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_package_weight_code__ozs +msgid "OZS" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,field_description:delivery_ups_oca.field_delivery_carrier__ups_package_dimension_code +msgid "Package Dimension code" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,field_description:delivery_ups_oca.field_delivery_carrier__ups_package_weight_code +msgid "Package Weight code" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__m3 +msgid "Priority Mail" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__m5 +msgid "Priority Mail Innovations" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model,name:delivery_ups_oca.model_product_packaging +msgid "Product Packaging" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,field_description:delivery_ups_oca.field_delivery_carrier__delivery_type +msgid "Provider" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_file_format__spl +msgid "SPL" +msgstr "" + +#. module: delivery_ups_oca +#: code:addons/delivery_ups_oca/models/ups_request.py:0 +#, python-format +msgid "Sending to UPS: %s" +msgstr "" + +#. module: delivery_ups_oca +#: model_terms:ir.ui.view,arch_db:delivery_ups_oca.view_delivery_carrier_form +msgid "Service" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,field_description:delivery_ups_oca.field_delivery_carrier__ups_service_code +msgid "Service code" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,field_description:delivery_ups_oca.field_delivery_carrier__ups_shipper_number +msgid "Shipper number" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model,name:delivery_ups_oca.model_delivery_carrier +msgid "Shipping Methods" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,field_description:delivery_ups_oca.field_delivery_carrier__ups_tracking_state_update_sync +msgid "Tracking state update sync" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model,name:delivery_ups_oca.model_stock_picking +msgid "Transfer" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__delivery_type__ups +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__product_packaging__package_carrier_type__ups +#: model_terms:ir.ui.view,arch_db:delivery_ups_oca.view_delivery_carrier_form +msgid "UPS" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__70 +msgid "UPS Access PointTM Economy" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,field_description:delivery_ups_oca.field_delivery_carrier__ups_cash_on_delivery +msgid "UPS Cash On Delivery" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,field_description:delivery_ups_oca.field_delivery_carrier__ups_cod_funds_code +msgid "UPS Cod Funds Code" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__74 +msgid "UPS Express®12:00" +msgstr "" + +#. module: delivery_ups_oca +#: model_terms:ir.ui.view,arch_db:delivery_ups_oca.view_picking_withcarrier_out_form +msgid "UPS Label" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__14 +msgid "UPS Next Day Air® Early" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__65 +msgid "UPS Saver" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__11 +msgid "UPS Standard" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__83 +msgid "UPS Today Dedicated Courier" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__85 +msgid "UPS Today Express" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__86 +msgid "UPS Today Express Saver" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__84 +msgid "UPS Today Intercity" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__82 +msgid "UPS Today Standard" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__72 +msgid "UPS Worldwide Economy" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__17 +msgid "UPS Worldwide Economy DDU" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__96 +msgid "UPS Worldwide Express Freight" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_service_code__71 +msgid "UPS Worldwide Express Freight Midday" +msgstr "" + +#. module: delivery_ups_oca +#: model_terms:ir.ui.view,arch_db:delivery_ups_oca.view_delivery_carrier_form +msgid "Update Token" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,field_description:delivery_ups_oca.field_delivery_carrier__ups_client_id +msgid "Ups Client" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,field_description:delivery_ups_oca.field_delivery_carrier__ups_client_secret +msgid "Ups Client Secret" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,field_description:delivery_ups_oca.field_delivery_carrier__ups_token +msgid "Ups Token" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,field_description:delivery_ups_oca.field_delivery_carrier__ups_token_expiration_date +msgid "Ups Token Expiration Date" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields,field_description:delivery_ups_oca.field_delivery_carrier__ups_use_packages_from_picking +msgid "Use packages from picking" +msgstr "" + +#. module: delivery_ups_oca +#: model:ir.model.fields.selection,name:delivery_ups_oca.selection__delivery_carrier__ups_file_format__zpl +msgid "ZPL" +msgstr "" + +#. module: delivery_ups_oca +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_01 +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_02 +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_03 +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_04 +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_21 +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_24 +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_25 +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_2a +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_2b +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_2c +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_30 +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_56 +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_57 +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_58 +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_59 +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_60 +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_61 +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_62 +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_63 +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_64 +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_65 +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_66 +#: model:product.packaging,weight_uom_name:delivery_ups_oca.product_packaging_ups_67 +msgid "kg" +msgstr "" + +#. module: delivery_ups_oca +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_01 +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_02 +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_03 +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_04 +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_21 +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_24 +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_25 +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_2a +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_2b +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_2c +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_30 +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_56 +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_57 +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_58 +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_59 +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_60 +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_61 +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_62 +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_63 +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_64 +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_65 +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_66 +#: model:product.packaging,volume_uom_name:delivery_ups_oca.product_packaging_ups_67 +msgid "m³" +msgstr "" + +#. module: delivery_ups_oca +#: code:addons/delivery_ups_oca/models/ups_request.py:0 +#, python-format +msgid "{} - Error retrieving the tracking information." +msgstr "" + +#. module: delivery_ups_oca +#: code:addons/delivery_ups_oca/models/ups_request.py:0 +#, python-format +msgid "{} - Warning: {}" +msgstr "" diff --git a/delivery_ups_oca/models/__init__.py b/delivery_ups_oca/models/__init__.py new file mode 100644 index 0000000000..d07a503fc2 --- /dev/null +++ b/delivery_ups_oca/models/__init__.py @@ -0,0 +1,5 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from . import delivery_carrier +from . import product_packaging +from . import stock_picking +from . import ups_request diff --git a/delivery_ups_oca/models/delivery_carrier.py b/delivery_ups_oca/models/delivery_carrier.py new file mode 100644 index 0000000000..25925e1ad9 --- /dev/null +++ b/delivery_ups_oca/models/delivery_carrier.py @@ -0,0 +1,217 @@ +# Copyright 2020 Hunki Enterprises BV +# Copyright 2021-2022 Tecnativa - Víctor Martínez +# Copyright 2023 ForgeFlow, S.L. - Jordi Ballester +# Copyright 2024 Sygel - Manuel Regidor +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + +from .ups_request import UpsRequest + + +class DeliveryCarrier(models.Model): + _inherit = "delivery.carrier" + + delivery_type = fields.Selection( + selection_add=[("ups", "UPS")], + ondelete={ + "ups": lambda recs: recs.write({"delivery_type": "fixed", "fixed_price": 0}) + }, + ) + ups_file_format = fields.Selection( + selection=[("GIF", "GIF"), ("ZPL", "ZPL"), ("EPL", "EPL"), ("SPL", "SPL")], + default="GIF", + string="File format", + ) + ups_shipper_number = fields.Char(string="Shipper number") + ups_service_code = fields.Selection( + selection=[ + ("01", "Next Day Air"), + ("02", "2nd Day Air"), + ("03", "Ground"), + ("07", "Express"), + ("08", "Expedited"), + ("11", "UPS Standard"), + ("12", "3 Day Select"), + ("13", "Next Day Air Saver"), + ("14", "UPS Next Day Air® Early"), + ("17", "UPS Worldwide Economy DDU"), + ("54", "Express Plus"), + ("59", "2nd Day Air A.M."), + ("65", "UPS Saver"), + ("M2", "First Class Mail"), + ("M3", "Priority Mail"), + ("M4", "Expedited Mail Innovations"), + ("M5", "Priority Mail Innovations"), + ("M6", "Economy Mail Innovations"), + ("M7", "Mail Innovations (MI) Returns"), + ("70", "UPS Access PointTM Economy"), + ("71", "UPS Worldwide Express Freight Midday"), + ("72", "UPS Worldwide Economy"), + ("74", "UPS Express®12:00"), + ("82", "UPS Today Standard"), + ("83", "UPS Today Dedicated Courier"), + ("84", "UPS Today Intercity"), + ("85", "UPS Today Express"), + ("86", "UPS Today Express Saver"), + ("96", "UPS Worldwide Express Freight"), + ], + default="11", + string="Service code", + ) + ups_default_packaging_id = fields.Many2one( + comodel_name="product.packaging", + string="Default Packaging Type", + domain=[("package_carrier_type", "=", "ups")], + ) + ups_package_dimension_code = fields.Selection( + selection=[("IN", "IN"), ("CM", "CM")], + default="IN", + string="Package Dimension code", + help="Is necessary to set dimension code from packages in shipping " + "creation process", + ) + ups_package_weight_code = fields.Selection( + selection=[("LBS", "LBS"), ("KGS", "KGS"), ("OZS", "OZS")], + default="LBS", + string="Package Weight code", + help="Is necessary to set weight code from packages in shipping creation " + "process", + ) + ups_tracking_state_update_sync = fields.Boolean( + string="Tracking state update sync", + help="If checked, odoo try to state update from picking according to UPS " + "webservice (you will necessary to activate tracking API)", + ) + ups_use_packages_from_picking = fields.Boolean(string="Use packages from picking") + ups_client_id = fields.Char() + ups_client_secret = fields.Char() + ups_token = fields.Char() + ups_token_expiration_date = fields.Datetime(readonly=True) + ups_cash_on_delivery = fields.Boolean(string="UPS Cash On Delivery") + ups_cod_funds_code = fields.Selection( + selection=[("1", "Cash"), ("9", "Check/Cashier Check/Money Order")], + string="UPS Cod Funds Code", + ) + + def _ups_get_response_price(self, total_charges, currency, company): + """We need to convert the price if the currency is different.""" + price = float(total_charges["MonetaryValue"]) + if total_charges["CurrencyCode"] != currency.name: + price = currency._convert( + price, + self.env["res.currency"].search( + [("name", "=", total_charges["CurrencyCode"])] + ), + company, + fields.Date.today(), + ) + return price + + def ups_rate_shipment(self, order): + ups_request = UpsRequest(self) + response = ups_request.rate_shipment(order) + price = self._ups_get_response_price( + response, order.currency_id, order.company_id + ) + return { + "success": True, + "price": price, + "error_message": False, + "warning_message": False, + } + + def ups_create_shipping(self, picking): + """Send packages of the picking to UPS + return a list of dicts {'exact_price': 'tracking_number':} + suitable for delivery.carrier#send_shipping""" + self.ensure_one() + ups_request = UpsRequest(self) + response = ups_request._send_shipping(picking) + extra_price = self._ups_get_response_price( + response["price"], picking.company_id.currency_id, picking.company_id + ) + picking.carrier_tracking_ref = response["ShipmentIdentificationNumber"] + # Create label from response + self._create_ups_label(picking, response["labels"]) + # Return + return { + "exact_price": extra_price, + "tracking_number": picking.carrier_tracking_ref, + } + + def ups_send_shipping(self, pickings): + return [self.ups_create_shipping(p) for p in pickings] + + def _prepare_ups_label_attachment(self, picking, values): + return { + "name": values["name"], + "type": "binary", + "datas": values["datas"], + "res_model": picking._name, + "res_id": picking.id, + } + + def _create_ups_label(self, picking, labels): + val_list = [] + for label in labels: + format_code = label["format_code"].upper() + attachment_name = "%s-%s.%s" % ( + label["tracking_ref"], + format_code, + format_code, + ) + val_list.append( + self._prepare_ups_label_attachment( + picking, + { + "name": attachment_name, + "datas": label["datas"], + }, + ) + ) + return self.env["ir.attachment"].create(val_list) + + def ups_get_label(self, carrier_tracking_ref): + """Generate label for picking + :param picking - stock.picking record + :returns attachment file + """ + self.ensure_one() + if not carrier_tracking_ref: + return False + ups_request = UpsRequest(self) + response = ups_request.shipping_label(carrier_tracking_ref) + # Create attachment to add pdf label + picking = self.env["stock.picking"].search( + [("carrier_tracking_ref", "=", carrier_tracking_ref)] + ) + return self._create_ups_label(picking, response) + + def ups_get_tracking_link(self, picking): + return "https://ups.com/WebTracking/track?trackingNumber=%s" % ( + picking.carrier_tracking_ref + ) + + def ups_cancel_shipment(self, pickings): + ups_request = UpsRequest(self) + for picking in pickings.filtered(lambda a: a.carrier_tracking_ref): + if ups_request.cancel_shipment(pickings): + picking.write({"tracking_state_history": False}) + return True + + def ups_tracking_state_update(self, picking): + self.ensure_one() + if ( + picking.carrier_id.ups_tracking_state_update_sync + and picking.carrier_tracking_ref + ): + ups_request = UpsRequest(self) + response = ups_request.tracking_state_update(picking) + picking.delivery_state = response["delivery_state"] + picking.tracking_state_history = response["tracking_state_history"] + + def ups_update_token(self): + self.ensure_one() + ups_request = UpsRequest(self) + ups_request._get_new_token() diff --git a/delivery_ups_oca/models/product_packaging.py b/delivery_ups_oca/models/product_packaging.py new file mode 100644 index 0000000000..5f8514b2e4 --- /dev/null +++ b/delivery_ups_oca/models/product_packaging.py @@ -0,0 +1,14 @@ +# Copyright 2020 Hunki Enterprises BV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class ProductPackaging(models.Model): + _inherit = "product.packaging" + + package_carrier_type = fields.Selection( + selection=[("none", "None"), ("ups", "UPS")], + string="Carrier", + default="none", + ) + shipper_package_code = fields.Char() diff --git a/delivery_ups_oca/models/stock_picking.py b/delivery_ups_oca/models/stock_picking.py new file mode 100644 index 0000000000..d12e5ccb85 --- /dev/null +++ b/delivery_ups_oca/models/stock_picking.py @@ -0,0 +1,14 @@ +# Copyright 2022 Tecnativa - Víctor Martínez +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import models + + +class StockPicking(models.Model): + _inherit = "stock.picking" + + def ups_get_label(self): + self.ensure_one() + tracking_ref = self.carrier_tracking_ref + if self.delivery_type != "ups" or not tracking_ref: + return + return self.carrier_id.ups_get_label(tracking_ref) diff --git a/delivery_ups_oca/models/ups_request.py b/delivery_ups_oca/models/ups_request.py new file mode 100644 index 0000000000..c83aaec33a --- /dev/null +++ b/delivery_ups_oca/models/ups_request.py @@ -0,0 +1,418 @@ +# Copyright 2020 Hunki Enterprises BV +# Copyright 2021 Tecnativa - Víctor Martínez +# Copyright 2024 Sygel - Manuel Regidor +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +import datetime +import logging + +import requests + +from odoo import _ +from odoo.exceptions import UserError + +_logger = logging.getLogger(__name__) + + +class UpsRequest(object): + def __init__(self, carrier): + self.carrier = carrier + self.default_packaging_id = self.carrier.ups_default_packaging_id + self.use_packages_from_picking = self.carrier.ups_use_packages_from_picking + self.shipper_number = self.carrier.ups_shipper_number + self.service_code = self.carrier.ups_service_code + self.file_format = self.carrier.ups_file_format + self.package_dimension_code = self.carrier.ups_package_dimension_code + self.package_weight_code = self.carrier.ups_package_weight_code + self.transaction_src = "Odoo (%s)" % self.carrier.name + self.client_id = self.carrier.ups_client_id + self.client_secret = self.carrier.ups_client_secret + self.token = self.carrier.ups_token + self.token_expiration_date = self.carrier.ups_token_expiration_date + self.url = "https://wwwcie.ups.com" + if self.carrier.prod_environment: + self.url = "https://onlinetools.ups.com" + + def _raise_for_status(self, status, skip_errors=True): + errors = status.get("response", {}).get("errors") + if errors: + msg = _("Sending to UPS: %s") % ( + "\n".join("%(code)s %(message)s" % error for error in errors), + ) + if skip_errors: + _logger.info(msg) + else: + raise UserError(msg) + + def _send_request( + self, url, json=None, data=None, headers=None, method="post", auth=None + ): + return getattr(requests, method)( + url, data=data, json=json, headers=headers, auth=auth + ) + + def _get_new_token(self): + if not (self.client_id and self.client_secret): + raise UserError( + _( + "Both Client ID and Client Secret must be set in UPS delivery carriers." + ) + ) + url = "%s/security/v1/oauth/token" % self.url + headers = {"x-merchant-id": self.client_id} + data = {"grant_type": "client_credentials"} + status = self._send_request( + url, data=data, headers=headers, auth=(self.client_id, self.client_secret) + ) + status = status.json() + self._raise_for_status(status, False) + token = status.get("access_token") + self.token = token + self.carrier.ups_token = token + self.carrier.ups_token_expiration_date = ( + datetime.datetime.now() + + datetime.timedelta(seconds=int(status.get("expires_in"))) + ) + + def _process_reply( + self, + url, + json=None, + data=None, + method="post", + headers_extra=None, + ): + if ( + not self.token + or not self.token_expiration_date + or (self.token_expiration_date <= datetime.datetime.now()) + ): + self._get_new_token() + data = data or {} + headers = { + "Authorization": "Bearer {}".format(self.token), + } + if headers_extra: + headers = {**headers, **headers_extra} + status = self._send_request(url, json, data, headers, method) + # Generate a new token + if status.status_code == 401: + self._get_new_token() + status = self._send_request(url, json, data, headers, method) + status = status.json() + ups_last_request = ("URL: {}\nData: {}").format(self.url, data) + self.carrier.log_xml(ups_last_request, "ups_last_request") + self.carrier.log_xml(status or "", "ups_last_response") + return status + + def _quant_package_data_from_picking(self, package, picking, is_package=False): + NumOfPieces = picking.number_of_packages + PackageWeight = picking.shipping_weight + if is_package: + NumOfPieces = sum(package.mapped("quant_ids.quantity")) + PackageWeight = max(package.shipping_weight, package.weight) + package = package.packaging_id + return { + "Description": package.name, + "NumOfPieces": str(NumOfPieces), + "Packaging": { + "Code": package.shipper_package_code, + "Description": package.name, + }, + "Dimensions": { + "UnitOfMeasurement": {"Code": self.package_dimension_code}, + "Length": str(package.packaging_length), + "Width": str(package.width), + "Height": str(package.height), + }, + "PackageWeight": { + "UnitOfMeasurement": {"Code": self.package_weight_code}, + "Weight": str(PackageWeight), + }, + } + + def _partner_to_shipping_data(self, partner, **kwargs): + """Return a dict describing a partner for the shipping request""" + return dict( + **kwargs, + Name=(partner.parent_id or partner).name, + AttentionName=partner.name, + TaxIdentificationNumber=partner.vat, + Phone=dict(Number=partner.phone or partner.mobile), + EMailAddress=partner.email, + Address=dict( + AddressLine=[partner.street, partner.street2 or ""], + City=partner.city, + StateProvinceCode=partner.state_id.code, + PostalCode=partner.zip, + CountryCode=partner.country_id.code, + ), + ) + + def _label_data(self): + res = {"LabelImageFormat": {"Code": self.file_format}} + # According to documentation, we need to specify sizes in some formats + if self.file_format != "GIF": + res["LabelStockSize"] = {"Height": "6", "Width": "4"} + return res + + def _prepare_create_shipping(self, picking): + """Return a dict that can be passed to the shipping endpoint of the UPS API""" + if self.use_packages_from_picking and picking.package_ids: + # modelo: stock.quant.package + packages = [ + self._quant_package_data_from_picking(package, picking, True) + for package in picking.package_ids + ] + else: + # modelo: product.packaging + packages = [] + package_info = self._quant_package_data_from_picking( + self.default_packaging_id, picking, False + ) + package_weight = round( + (picking.shipping_weight / picking.number_of_packages), 2 + ) + for i in range(0, picking.number_of_packages): + package_item = package_info + package_name = "%s (%s)" % (picking.name, i + 1) + package_item["Description"] = package_name + package_item["NumOfPieces"] = "1" + package_item["Packaging"]["Description"] = package_name + package_item["PackageWeight"]["Weight"] = str(package_weight) + packages.append(package_item) + vals = { + "ShipmentRequest": { + "Shipment": { + "Description": picking.name, + "Shipper": self._partner_to_shipping_data( + partner=picking.company_id.partner_id, + ShipperNumber=self.shipper_number, + ), + "ShipTo": self._partner_to_shipping_data(picking.partner_id), + "ShipFrom": self._partner_to_shipping_data( + picking.picking_type_id.warehouse_id.partner_id + or picking.company_id.partner_id + ), + "PaymentInformation": { + "ShipmentCharge": { + "Type": "01", + "BillShipper": { + # we ignore the alternatives paying per credit card or + # paypal for now + "AccountNumber": self.shipper_number, + }, + } + }, + "Service": {"Code": self.service_code}, + "Package": packages, + }, + "LabelSpecification": self._label_data(), + } + } + if picking.carrier_id.ups_cash_on_delivery and picking.sale_id: + vals["ShipmentRequest"]["Shipment"]["ShipmentServiceOptions"] = ( + { + "COD": { + "CODFundsCode": picking.carrier_id.ups_cod_funds_code, + "CODAmount": { + "CurrencyCode": picking.sale_id.currency_id.name, + "MonetaryValue": str(picking.sale_id.amount_total), + }, + } + }, + ) + return vals + + def _send_shipping(self, picking): + status = self._process_reply( + url="%s/api/shipments/v1/ship" % self.url, + json=self._prepare_create_shipping(picking), + ) + self._raise_for_status(status, False) + res = status["ShipmentResponse"]["ShipmentResults"] + PackageResults = res["PackageResults"] + labels = [] + if isinstance(PackageResults, dict): + labels.append( + { + "tracking_ref": PackageResults["TrackingNumber"], + "format_code": PackageResults["ShippingLabel"]["ImageFormat"][ + "Code" + ], + "datas": PackageResults["ShippingLabel"]["GraphicImage"], + } + ) + if isinstance(PackageResults, list): + for label in PackageResults: + labels.append( + { + "tracking_ref": label["TrackingNumber"], + "format_code": label["ShippingLabel"]["ImageFormat"]["Code"], + "datas": label["ShippingLabel"]["GraphicImage"], + } + ) + return { + "price": res["ShipmentCharges"]["TotalCharges"], + "ShipmentIdentificationNumber": res["ShipmentIdentificationNumber"], + "labels": labels, + } + + def _quant_package_data_from_order(self, order): + PackageWeight = 0 + for line in order.order_line.filtered( + lambda x: x.product_id and x.product_id.weight > 0 + ): + PackageWeight += line.product_id.weight * line.product_uom_qty + return { + "PackagingType": {"Code": self.default_packaging_id.shipper_package_code}, + "Dimensions": { + "UnitOfMeasurement": {"Code": self.package_dimension_code}, + "Length": str(self.default_packaging_id.packaging_length), + "Width": str(self.default_packaging_id.width), + "Height": str(self.default_packaging_id.height), + }, + "PackageWeight": { + "UnitOfMeasurement": {"Code": self.package_weight_code}, + "Weight": str(PackageWeight), + }, + } + + def _prepare_rate_shipment(self, order): + packages = [self._quant_package_data_from_order(order)] + return { + "RateRequest": { + "Shipment": { + "Shipper": self._partner_to_shipping_data( + partner=order.company_id.partner_id, + ShipperNumber=self.shipper_number, + ), + "ShipTo": self._partner_to_shipping_data(order.partner_shipping_id), + "ShipFrom": self._partner_to_shipping_data( + order.warehouse_id.partner_id or order.company_id.partner_id + ), + "Service": {"Code": self.service_code}, + "Package": packages, + } + } + } + + def _rate_shipment(self, order, skip_errors=False): + status = self._process_reply( + url="%s/api/rating/v1/Rate" % self.url, + json=self._prepare_rate_shipment(order), + ) + self._raise_for_status(status, skip_errors) + return status + + def rate_shipment(self, order): + status = self._rate_shipment(order) + return status["RateResponse"]["RatedShipment"]["TotalCharges"] + + def _prepare_shipping_label(self, carrier_tracking_ref): + return { + "LabelRecoveryRequest": { + "LabelSpecification": self._label_data(), + "TrackingNumber": carrier_tracking_ref, + } + } + + def shipping_label(self, carrier_tracking_ref): + status = self._process_reply( + url="%s/api/labels/v1/recovery" % self.url, + json=self._prepare_shipping_label(carrier_tracking_ref), + ) + self._raise_for_status(status, False) + labels = [] + labels_data = status["LabelRecoveryResponse"]["LabelResults"] + if isinstance(labels_data, dict): + labels.append( + { + "tracking_ref": labels_data["TrackingNumber"], + "format_code": labels_data["LabelImage"]["LabelImageFormat"][ + "Code" + ], + "datas": labels_data["LabelImage"]["GraphicImage"], + } + ) + elif isinstance(labels_data, list): + for label in labels_data: + labels.append( + { + "tracking_ref": label["TrackingNumber"], + "format_code": label["LabelImage"]["LabelImageFormat"]["Code"], + "datas": label["LabelImage"]["GraphicImage"], + } + ) + + return labels + + def cancel_shipment(self, picking): + url = "%s/api/shipments/v1/void/cancel" % self.url + url = "{}/{}".format(url, picking.carrier_tracking_ref) + status = self._process_reply(url=url, method="delete") + self._raise_for_status(status, False) + return True + + def tracking_state_update(self, picking): + static_states = { + "I": "in_transit", + "D": "customer_delivered", + "E": "incidence", + "P": "customer_delivered", + "M": "in_transit", + } + status = self._process_reply( + url="%s/api/track/v1/details/%s" % (self.url, picking.carrier_tracking_ref), + method="get", + headers_extra={ + "transId": "{}".format(datetime.datetime.now().timestamp()), + "transactionSrc": "{} - Odoo".format(picking.company_id.name), + }, + ) + self._raise_for_status(status, False) + states_list = [] + delivery_state = "incidence" + try: + shipment = status["trackResponse"]["shipment"][0] + if not shipment.get("warnings"): + for activity in shipment["package"][0]["activity"]: + states_list.append( + "{} - {}".format( + datetime.datetime.strptime( + "{}{}".format( + activity.get("date"), activity.get("time") + ), + "%Y%m%d%H%M%S", + ), + activity.get("status").get("description"), + ) + ) + if shipment["package"][0]["activity"]: + delivery_state = static_states.get( + shipment["package"][0]["activity"][0]["status"]["type"], + "incidence", + ) + else: + for warning in shipment.get("warnings"): + states_list.append( + _("%(datetime)s - Warning: %(message)s") + % { + "datetime": datetime.datetime.now().strftime( + "%Y-%m-%d %H:%M:%S" + ), + "message": warning.get("message"), + } + ) + + except Exception: + states_list.append( + _("%(datetime)s - Error retrieving the tracking information.") + % { + "datetime": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + } + ) + return { + "delivery_state": delivery_state, + "tracking_state_history": "\n".join(states_list), + } diff --git a/delivery_ups_oca/readme/CONFIGURE.rst b/delivery_ups_oca/readme/CONFIGURE.rst new file mode 100644 index 0000000000..278499c622 --- /dev/null +++ b/delivery_ups_oca/readme/CONFIGURE.rst @@ -0,0 +1,16 @@ +To configure this module, you need to: + +#. Add a carrier account with delivery type ``ups`` and fill in your credentials (UPS + Client and UPS Client Secret) +#. Configure in Odoo all required fields of the UPS tab with your account data + https://wwwapps.ups.com/ppc/ppc.html (Shipper number, Default Packaging, Package + Dimension Code, Package Weight Code and File Format). +#. If yo have "Tracking state update sync" checked all delivery orders state check will + be done querying UPS services. +#. It is possible to create a UPS carrier for cash on delivery parcels. Select the + ``ups`` delivery type and check the "Cash on Delivery" checkbox under the "UPS" tab. + It is required to select the "UPS COD Funds Code" when the "Cash on Delivery" option + is selected. + +**NOTE** You need to add an APP from https://developer.ups.com/ for using the +webservice. diff --git a/delivery_ups_oca/readme/CONTRIBUTORS.rst b/delivery_ups_oca/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..195c1491bf --- /dev/null +++ b/delivery_ups_oca/readme/CONTRIBUTORS.rst @@ -0,0 +1,13 @@ +* Holger Brunn (https://hunki-enterprises.nl) +* `Tecnativa `_: + + * Víctor Martínez + * Pedro M. Baeza + +* `ForgeFlow `_: + + * Jordi Ballester + +* `Sygel `_: + + * Manuel Regidor diff --git a/delivery_ups_oca/readme/DESCRIPTION.rst b/delivery_ups_oca/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..822f7930ba --- /dev/null +++ b/delivery_ups_oca/readme/DESCRIPTION.rst @@ -0,0 +1,9 @@ +This module adds `UPS `_ to the available carriers. + +It allows you to register shippings, generate labels, get rates from order, read +shipping states and cancel shipments using UPS webservice, so no need of exchanging +any kind of file. + +When a sales order is created in Odoo and the UPS carrier is assigned, the shipping +price that will be obtained will be the price that the UPS webservice estimates +according to the order information (address and products). diff --git a/delivery_ups_oca/readme/ROADMAP.rst b/delivery_ups_oca/readme/ROADMAP.rst new file mode 100644 index 0000000000..ccb679480b --- /dev/null +++ b/delivery_ups_oca/readme/ROADMAP.rst @@ -0,0 +1,2 @@ +* Support international forms +* Support package service options diff --git a/delivery_ups_oca/readme/USAGE.rst b/delivery_ups_oca/readme/USAGE.rst new file mode 100644 index 0000000000..bd70ad0ba8 --- /dev/null +++ b/delivery_ups_oca/readme/USAGE.rst @@ -0,0 +1,9 @@ +You have to set the created shipping method in the delivery order to ship: + +* When the picking is 'Transferred', a *Create Shipping Label* button appears. Just + click on it, and if all went well, the label will be 'attached'. +* If the shipment creation process fails, a validation error will appear displaying UPS + error. +* When the delivery order is cancelled, it's automatically cancelled too in UPS. +* If you have "Tracking state update sync" checked in the shipping method, a periodical + state check will be done querying UPS services. diff --git a/delivery_ups_oca/static/description/icon.png b/delivery_ups_oca/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3e18e9e7f0172b8d5dd5fe006ab8818a5a232345 GIT binary patch literal 38594 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

k*Btf$3Tb;R+o+q@=OMK&Q!6Wz!d{b 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 literal 0 HcmV?d00001 diff --git a/delivery_ups_oca/static/description/index.html b/delivery_ups_oca/static/description/index.html new file mode 100644 index 0000000000..5f10eda5f8 --- /dev/null +++ b/delivery_ups_oca/static/description/index.html @@ -0,0 +1,483 @@ + + + + + +Delivery UPS OCA + + + +

+

Delivery UPS OCA

+ + +

Beta License: AGPL-3 OCA/delivery-carrier Translate me on Weblate Try me on Runboat

+

This module adds UPS to the available carriers.

+

It allows you to register shippings, generate labels, get rates from order, read +shipping states and cancel shipments using UPS webservice, so no need of exchanging +any kind of file.

+

When a sales order is created in Odoo and the UPS carrier is assigned, the shipping +price that will be obtained will be the price that the UPS webservice estimates +according to the order information (address and products).

+

Table of contents

+ +
+

Configuration

+

To configure this module, you need to:

+
    +
  1. Add a carrier account with delivery type ups and fill in your credentials (UPS +Client and UPS Client Secret)
  2. +
  3. Configure in Odoo all required fields of the UPS tab with your account data +https://wwwapps.ups.com/ppc/ppc.html (Shipper number, Default Packaging, Package +Dimension Code, Package Weight Code and File Format).
  4. +
  5. If yo have “Tracking state update sync” checked all delivery orders state check will +be done querying UPS services.
  6. +
  7. It is possible to create a UPS carrier for cash on delivery parcels. Select the +ups delivery type and check the “Cash on Delivery” checkbox under the “UPS” tab. +It is required to select the “UPS COD Funds Code” when the “Cash on Delivery” option +is selected.
  8. +
+

NOTE You need to add an APP from https://developer.ups.com/ for using the +webservice.

+
+
+

Usage

+

You have to set the created shipping method in the delivery order to ship:

+
    +
  • When the picking is ‘Transferred’, a Create Shipping Label button appears. Just +click on it, and if all went well, the label will be ‘attached’.
  • +
  • If the shipment creation process fails, a validation error will appear displaying UPS +error.
  • +
  • When the delivery order is cancelled, it’s automatically cancelled too in UPS.
  • +
  • If you have “Tracking state update sync” checked in the shipping method, a periodical +state check will be done querying UPS services.
  • +
+
+
+

Known issues / Roadmap

+
    +
  • Support international forms
  • +
  • Support package service options
  • +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Hunki Enterprises BV
  • +
  • Tecnativa
  • +
  • ForgeFlow
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/delivery-carrier project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/delivery_ups_oca/tests/__init__.py b/delivery_ups_oca/tests/__init__.py new file mode 100644 index 0000000000..e904a8cbb5 --- /dev/null +++ b/delivery_ups_oca/tests/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import test_delivery_ups diff --git a/delivery_ups_oca/tests/test_delivery_ups.py b/delivery_ups_oca/tests/test_delivery_ups.py new file mode 100644 index 0000000000..7eab0691cb --- /dev/null +++ b/delivery_ups_oca/tests/test_delivery_ups.py @@ -0,0 +1,166 @@ +# Copyright 2020 Hunki Enterprises BV +# Copyright 2021-2022 Tecnativa - Víctor Martínez +# Copyright 2024 Sygel - Manuel Regidor +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +import base64 +from unittest import mock + +from odoo.tests import Form, common + +_module_ns = "odoo.addons.delivery_ups_oca" +_provider_class = _module_ns + ".models.ups_request.UpsRequest" + + +class TestDeliveryUpsBase(common.TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + product_shipping_cost = cls.env["product.product"].create( + { + "type": "service", + "name": "Shipping costs", + "standard_price": 10, + "list_price": 100, + } + ) + cls.carrier = cls.env["delivery.carrier"].create( + { + "name": "UPS", + "delivery_type": "ups", + "product_id": product_shipping_cost.id, + "price_method": "fixed", + "ups_default_packaging_id": cls.env.ref( + "delivery_ups_oca.product_packaging_ups_02" + ).id, + } + ) + cls.company = cls.env.ref("base.main_company") + cls.company.partner_id.write( + { + "phone": "+%s976123456" % cls.company.country_id.phone_code, + "vat": "%s09915370R" % cls.company.country_id.code, + } + ) + cls.partner = cls.env["res.partner"].create( + { + "name": "Test partner", + "country_id": cls.company.country_id.id, + "phone": cls.company.partner_id.phone, + "email": "test@odoo.com", + "street": cls.company.partner_id.street, + "city": cls.company.partner_id.city, + "zip": cls.company.partner_id.zip, + "state_id": cls.company.partner_id.state_id.id, + "vat": cls.company.partner_id.vat, + } + ) + cls.product = cls.env["product.product"].create( + {"name": "Test product", "type": "product", "weight": 10} + ) + cls.sale = cls._create_sale_order(cls) + + def _create_sale_order(self): + order_form = Form(self.env["sale.order"]) + order_form.partner_id = self.partner + with order_form.order_line.new() as line_form: + line_form.product_id = self.product + line_form.product_uom_qty = 10 + sale = order_form.save() + delivery_wizard = Form( + self.env["choose.delivery.carrier"].with_context( + default_order_id=sale.id, + default_carrier_id=self.carrier.id, + ) + ).save() + delivery_wizard.button_confirm() + sale.action_confirm() + return sale + + +class TestDeliveryUps(TestDeliveryUpsBase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.picking = cls.sale.picking_ids[0] + cls.picking.move_lines.quantity_done = 10 + + def test_order_ups_rate_shipment(self): + with mock.patch( + _provider_class + "._rate_shipment", + return_value={ + "RateResponse": { + "RatedShipment": { + "TotalCharges": {"MonetaryValue": 1, "CurrencyCode": "USD"} + } + } + }, + ): + res = self.carrier.ups_rate_shipment(self.sale) + self.assertGreater(res["price"], 0) + self.assertTrue(res["success"]) + + def test_order_ups_rate_shipment_currency_extra(self): + usd = self.env.ref("base.USD") + eur = self.env.ref("base.EUR") + currency = self.env.ref("base.main_company").currency_id + currency_extra = eur if currency == usd else usd + self.sale.currency_id = currency_extra + with mock.patch( + _provider_class + "._rate_shipment", + return_value={ + "RateResponse": { + "RatedShipment": { + "TotalCharges": {"MonetaryValue": 1, "CurrencyCode": "USD"} + } + } + }, + ): + res = self.carrier.ups_rate_shipment(self.sale) + self.assertGreater(res["price"], 0) + self.assertTrue(res["success"]) + + def test_delivery_carrier_ups_integration(self): + self.picking.action_confirm() + self.picking.action_assign() + # Create a simple PDF-like bytes object for testing + label = b"%PDF-1.4\n%EOF" + with mock.patch( + _provider_class + "._send_shipping", + return_value={ + "price": {"CurrencyCode": "USD", "MonetaryValue": "0.0"}, + "ShipmentIdentificationNumber": "123456", + "labels": [ + { + "tracking_ref": "123456", + "format_code": "png", + "datas": base64.b64encode(label), + } + ], + }, + ): + self.picking.send_to_shipper() + self.assertEqual(self.picking.message_attachment_count, 1) + self.assertTrue(self.picking.carrier_tracking_ref) + self.assertFalse(self.picking.tracking_state_history) + self.assertEqual( + self.picking.delivery_state, "shipping_recorded_in_carrier" + ) + if self.picking.carrier_id.ups_tracking_state_update_sync: + with mock.patch( + _provider_class + ".tracking_state_update", + return_value={ + "delivery_state": "in_transit", + "tracking_state_history": "history", + }, + ): + self.picking.tracking_state_update() + self.assertEqual(self.picking.delivery_state, "in_transit") + self.assertTrue(self.picking.tracking_state_history) + with mock.patch( + _provider_class + ".cancel_shipment", + return_value=True, + ): + self.picking.cancel_shipment() + self.assertFalse(self.picking.carrier_tracking_ref) + self.assertEqual(self.picking.delivery_state, "canceled_shipment") diff --git a/delivery_ups_oca/views/delivery_carrier_view.xml b/delivery_ups_oca/views/delivery_carrier_view.xml new file mode 100644 index 0000000000..0c09ed0a8a --- /dev/null +++ b/delivery_ups_oca/views/delivery_carrier_view.xml @@ -0,0 +1,83 @@ + + + + + delivery.carrier + + + + + + + + + + + + + + + + + + + +