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 0000000000..3e18e9e7f0 Binary files /dev/null and b/delivery_ups_oca/static/description/icon.png differ 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 + + + + + + + + + + + + + + + + + + + +