diff --git a/despatch_advice_import/README.rst b/despatch_advice_import/README.rst new file mode 100644 index 0000000000..c72f889d04 --- /dev/null +++ b/despatch_advice_import/README.rst @@ -0,0 +1,102 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + +====================== +Despatch Advice Import +====================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:6ca80319c1f9790b27e5685c5eb372aa2f61b0570f467f49e81d28c3c2abf77a + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/license-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%2Fedi-lightgray.png?logo=github + :target: https://github.com/OCA/edi/tree/19.0/despatch_advice_import + :alt: OCA/edi +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/edi-19-0/edi-19-0-despatch_advice_import + :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/edi&target_branch=19.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module will support import despatch advice file + +**Table of contents** + +.. contents:: + :local: + +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 +------- + +* ACSONE SA/NV +* BCIM + +Contributors +------------ + +- Laurent Mignon + +- `Trobz `__: + + - Thien + +- Simone Orsi + +- Jacques-Etienne Baudoux + +Other credits +------------- + + + +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. + +.. |maintainer-jbaudoux| image:: https://github.com/jbaudoux.png?size=40px + :target: https://github.com/jbaudoux + :alt: jbaudoux + +Current `maintainer `__: + +|maintainer-jbaudoux| + +This module is part of the `OCA/edi `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/despatch_advice_import/__init__.py b/despatch_advice_import/__init__.py new file mode 100644 index 0000000000..40272379f7 --- /dev/null +++ b/despatch_advice_import/__init__.py @@ -0,0 +1 @@ +from . import wizard diff --git a/despatch_advice_import/__manifest__.py b/despatch_advice_import/__manifest__.py new file mode 100644 index 0000000000..b829b3e242 --- /dev/null +++ b/despatch_advice_import/__manifest__.py @@ -0,0 +1,22 @@ +# Copyright 2020 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Despatch Advice Import", + "summary": "Despatch Advice import", + "version": "19.0.1.0.0", + "website": "https://github.com/OCA/edi", + "license": "AGPL-3", + "author": "ACSONE SA/NV, BCIM, Odoo Community Association (OCA)", + "maintainers": ["jbaudoux"], + "depends": [ + # Odoo/core + "purchase_stock", + # OCA/edi + "base_business_document_import", + # OCA/reporting-engine + "pdf_xml_attachment", + ], + "data": ["security/ir.model.access.csv", "wizard/despatch_advice_import.xml"], + "installable": True, +} diff --git a/despatch_advice_import/i18n/despatch_advice_import.pot b/despatch_advice_import/i18n/despatch_advice_import.pot new file mode 100644 index 0000000000..66dabce3c1 --- /dev/null +++ b/despatch_advice_import/i18n/despatch_advice_import.pot @@ -0,0 +1,232 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * despatch_advice_import +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.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: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import__allow_validate_over_qty +msgid "Allow Validate Over Quantity" +msgstr "" + +#. module: despatch_advice_import +#: model_terms:ir.ui.view,arch_db:despatch_advice_import.despatch_advice_import_form_view +msgid "Cancel" +msgstr "" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import__create_uid +msgid "Created by" +msgstr "" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import__create_date +msgid "Created on" +msgstr "" + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "Delivery cancelled by the supplier." +msgstr "" + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "Delivery confirmed by the supplier." +msgstr "" + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "Delivery confirmed with amendment by the supplier." +msgstr "" + +#. module: despatch_advice_import +#: model_terms:ir.ui.view,arch_db:despatch_advice_import.despatch_advice_import_form_view +msgid "Despatch Advice Import" +msgstr "" + +#. module: despatch_advice_import +#: model:ir.model,name:despatch_advice_import.model_despatch_advice_import +msgid "Despatch Advice Import from Files" +msgstr "" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import__display_name +msgid "Display Name" +msgstr "" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import__filename +msgid "File Name" +msgstr "" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import__id +msgid "ID" +msgstr "" + +#. module: despatch_advice_import +#: model_terms:ir.ui.view,arch_db:despatch_advice_import.despatch_advice_import_form_view +msgid "" +"If it is a PDF file, Odoo will try to find an XML file in the attachments of" +" the PDF file and then use this XML file." +msgstr "" + +#. module: despatch_advice_import +#: model_terms:ir.ui.view,arch_db:despatch_advice_import.despatch_advice_import_form_view +msgid "" +"If it is an XML file, Odoo will parse it if the module that adds support for" +" this XML format is installed. For the" +msgstr "" + +#. module: despatch_advice_import +#: model:ir.actions.act_window,name:despatch_advice_import.despatch_advice_import_action +msgid "Import" +msgstr "" + +#. module: despatch_advice_import +#: model_terms:ir.ui.view,arch_db:despatch_advice_import.despatch_advice_import_form_view +msgid "Import document" +msgstr "" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import____last_update +msgid "Last Modified on" +msgstr "" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import__write_date +msgid "Last Updated on" +msgstr "" + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "Missing document file" +msgstr "" + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "Missing document filename" +msgstr "" + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "No purchase order found for name %s." +msgstr "" + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/tests/test_despatch_advice_import.py:0 +#, python-format +msgid "No purchase order found for name 123456." +msgstr "" + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "The product quantity is greater than the original product quantity" +msgstr "" + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "There are no embedded XML file in this PDF file." +msgstr "" + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "This XML file is not XML-compliant" +msgstr "" + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "" +"This file '%s' is not recognised as XML nor PDF file. Please check the file " +"and it's extension." +msgstr "" + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "" +"This type of XML Order Document is not supported. Did you install the module" +" to support this XML format?" +msgstr "" + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "" +"This type of XML Order Response is not supported. Did you install the module" +" to support this XML format?" +msgstr "" + +#. module: despatch_advice_import +#: model:ir.ui.menu,name:despatch_advice_import.despatch_advice_import_importer_menu +msgid "UBL Despatch Advice Importer" +msgstr "" + +#. module: despatch_advice_import +#: model_terms:ir.ui.view,arch_db:despatch_advice_import.despatch_advice_import_form_view +msgid "Universal Business Language" +msgstr "" + +#. module: despatch_advice_import +#: model:ir.model.fields,help:despatch_advice_import.field_despatch_advice_import__document +msgid "" +"Upload an Despatch Advice file that you received from your supplier. " +"Supported formats: XML and PDF (PDF with an embeded XML file)." +msgstr "" + +#. module: despatch_advice_import +#: model_terms:ir.ui.view,arch_db:despatch_advice_import.despatch_advice_import_form_view +msgid "" +"Upload below the DespatchAdvice you received from your supplier. When you " +"click on the import button:" +msgstr "" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import__document +msgid "XML or PDF Despatch Advice" +msgstr "" + +#. module: despatch_advice_import +#: model_terms:ir.ui.view,arch_db:despatch_advice_import.despatch_advice_import_form_view +msgid "" +"format (UBL), you should install the module " +"despatch_advice_import_ubl." +msgstr "" diff --git a/despatch_advice_import/i18n/es.po b/despatch_advice_import/i18n/es.po new file mode 100644 index 0000000000..a5d506d67f --- /dev/null +++ b/despatch_advice_import/i18n/es.po @@ -0,0 +1,249 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * despatch_advice_import +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2024-02-14 15:36+0000\n" +"Last-Translator: Ivorra78 \n" +"Language-Team: none\n" +"Language: es\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" +"X-Generator: Weblate 4.17\n" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import__allow_validate_over_qty +msgid "Allow Validate Over Quantity" +msgstr "" + +#. module: despatch_advice_import +#: model_terms:ir.ui.view,arch_db:despatch_advice_import.despatch_advice_import_form_view +msgid "Cancel" +msgstr "Cancelar" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import__create_date +msgid "Created on" +msgstr "Creado el" + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "Delivery cancelled by the supplier." +msgstr "Entrega anulada por el proveedor." + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "Delivery confirmed by the supplier." +msgstr "Entrega confirmada por el proveedor." + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "Delivery confirmed with amendment by the supplier." +msgstr "Entrega confirmada con modificación por el proveedor." + +#. module: despatch_advice_import +#: model_terms:ir.ui.view,arch_db:despatch_advice_import.despatch_advice_import_form_view +msgid "Despatch Advice Import" +msgstr "Aviso de Expedición Importación" + +#. module: despatch_advice_import +#: model:ir.model,name:despatch_advice_import.model_despatch_advice_import +msgid "Despatch Advice Import from Files" +msgstr "Aviso de Expedición Importación desde Ficheros" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import__display_name +msgid "Display Name" +msgstr "Mostrar Nombre" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import__filename +msgid "File Name" +msgstr "Nombre del Archivo" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import__id +msgid "ID" +msgstr "ID" + +#. module: despatch_advice_import +#: model_terms:ir.ui.view,arch_db:despatch_advice_import.despatch_advice_import_form_view +msgid "" +"If it is a PDF file, Odoo will try to find an XML file in the attachments of " +"the PDF file and then use this XML file." +msgstr "" +"Si es un archivo PDF, Odoo tratará de encontrar un archivo XML en los " +"adjuntos del archivo PDF y luego usará este archivo XML." + +#. module: despatch_advice_import +#: model_terms:ir.ui.view,arch_db:despatch_advice_import.despatch_advice_import_form_view +msgid "" +"If it is an XML file, Odoo will parse it if the module that adds support for " +"this XML format is installed. For the" +msgstr "" +"Si es un archivo XML, Odoo lo analizará si está instalado el módulo que " +"agrega soporte para este formato XML. Para el" + +#. module: despatch_advice_import +#: model:ir.actions.act_window,name:despatch_advice_import.despatch_advice_import_action +msgid "Import" +msgstr "Importar" + +#. module: despatch_advice_import +#: model_terms:ir.ui.view,arch_db:despatch_advice_import.despatch_advice_import_form_view +msgid "Import document" +msgstr "Importar documento" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import____last_update +msgid "Last Modified on" +msgstr "Última Modificación el" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import__write_uid +msgid "Last Updated by" +msgstr "Última Actualización por" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import__write_date +msgid "Last Updated on" +msgstr "Última Actualización el" + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "Missing document file" +msgstr "Falta un archivo de documento" + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "Missing document filename" +msgstr "Falta el nombre de archivo del documento" + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "No purchase order found for name %s." +msgstr "No se ha encontrado ninguna orden de compra para el nombre %s." + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/tests/test_despatch_advice_import.py:0 +#, python-format +msgid "No purchase order found for name 123456." +msgstr "No se ha encontrado ninguna orden de compra a nombre de 123456." + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "The product quantity is greater than the original product quantity" +msgstr "La cantidad de producto es superior a la cantidad de producto original" + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "There are no embedded XML file in this PDF file." +msgstr "No hay ningún archivo XML incorporado en este archivo PDF." + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "This XML file is not XML-compliant" +msgstr "Este archivo no es compatible con el formato XML" + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "" +"This file '%s' is not recognised as XML nor PDF file. Please check the file " +"and it's extension." +msgstr "" +"Este archivo '%s' no se reconoce como archivo XML ni PDF. Compruebe el " +"archivo y su extensión." + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "" +"This type of XML Order Document is not supported. Did you install the module " +"to support this XML format?" +msgstr "" +"Este tipo de documento de pedido XML no es compatible. ¿Ha instalado el " +"módulo para admitir este formato XML?" + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "" +"This type of XML Order Response is not supported. Did you install the module " +"to support this XML format?" +msgstr "" +"Este tipo de respuesta de pedido XML no es compatible. ¿Ha instalado el " +"módulo para admitir este formato XML?" + +#. module: despatch_advice_import +#: model:ir.ui.menu,name:despatch_advice_import.despatch_advice_import_importer_menu +msgid "UBL Despatch Advice Importer" +msgstr "Aviso de Expedición UBL Importador" + +#. module: despatch_advice_import +#: model_terms:ir.ui.view,arch_db:despatch_advice_import.despatch_advice_import_form_view +msgid "Universal Business Language" +msgstr "Lenguaje Comercial Universal" + +#. module: despatch_advice_import +#: model:ir.model.fields,help:despatch_advice_import.field_despatch_advice_import__document +msgid "" +"Upload an Despatch Advice file that you received from your supplier. " +"Supported formats: XML and PDF (PDF with an embeded XML file)." +msgstr "" +"Cargue un Archivo de Aviso de Expedición que haya recibido de su proveedor. " +"Formatos admitidos: XML y PDF (PDF con un archivo XML incrustado)." + +#. module: despatch_advice_import +#: model_terms:ir.ui.view,arch_db:despatch_advice_import.despatch_advice_import_form_view +msgid "" +"Upload below the DespatchAdvice you received from your supplier. When you " +"click on the import button:" +msgstr "" +"Cargue a continuación el Aviso de Expedición que ha recibido de su " +"proveedor. Cuando haga clic en el botón de importación:" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import__document +msgid "XML or PDF Despatch Advice" +msgstr "Aviso de Envío XML o PDF" + +#. module: despatch_advice_import +#: model_terms:ir.ui.view,arch_db:despatch_advice_import.despatch_advice_import_form_view +msgid "" +"format (UBL), you should install the module despatch_advice_import_ubl." +msgstr "" +"formato (UBL), debes instalar el módulo despatch_advice_import_ubl." diff --git a/despatch_advice_import/i18n/fr.po b/despatch_advice_import/i18n/fr.po new file mode 100644 index 0000000000..14546227bb --- /dev/null +++ b/despatch_advice_import/i18n/fr.po @@ -0,0 +1,253 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * despatch_advice_import +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2024-04-22 12:35+0000\n" +"Last-Translator: c2cdidier \n" +"Language-Team: none\n" +"Language: fr\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" +"X-Generator: Weblate 4.17\n" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import__allow_validate_over_qty +msgid "Allow Validate Over Quantity" +msgstr "Autoriser la validation d'une quantité supérieure" + +#. module: despatch_advice_import +#: model_terms:ir.ui.view,arch_db:despatch_advice_import.despatch_advice_import_form_view +msgid "Cancel" +msgstr "Annuler" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import__create_uid +msgid "Created by" +msgstr "" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import__create_date +msgid "Created on" +msgstr "créé le" + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "Delivery cancelled by the supplier." +msgstr "Livraison annulée par le fournisseur." + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "Delivery confirmed by the supplier." +msgstr "Livraison confirmée par le fournisseur." + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "Delivery confirmed with amendment by the supplier." +msgstr "Livraison confirmée avec modification par le fournisseur." + +#. module: despatch_advice_import +#: model_terms:ir.ui.view,arch_db:despatch_advice_import.despatch_advice_import_form_view +msgid "Despatch Advice Import" +msgstr "Importation d'avis d'expédition" + +#. module: despatch_advice_import +#: model:ir.model,name:despatch_advice_import.model_despatch_advice_import +msgid "Despatch Advice Import from Files" +msgstr "Importation de fichiers d'avis d'expédition" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import__display_name +msgid "Display Name" +msgstr "Nom affiché" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import__filename +msgid "File Name" +msgstr "Nom du fichier" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import__id +msgid "ID" +msgstr "ID" + +#. module: despatch_advice_import +#: model_terms:ir.ui.view,arch_db:despatch_advice_import.despatch_advice_import_form_view +msgid "" +"If it is a PDF file, Odoo will try to find an XML file in the attachments of" +" the PDF file and then use this XML file." +msgstr "" +"S'il s'agit d'un fichier PDF, Odoo essaiera de trouver un fichier XML dans " +"les pièces jointes du fichier PDF et utilisera ce fichier XML." + +#. module: despatch_advice_import +#: model_terms:ir.ui.view,arch_db:despatch_advice_import.despatch_advice_import_form_view +msgid "" +"If it is an XML file, Odoo will parse it if the module that adds support for" +" this XML format is installed. For the" +msgstr "" +"S'il s'agit d'un fichier XML, Odoo l'analysera si le module qui prend en " +"charge ce format XML est installé. Pour le fichier" + +#. module: despatch_advice_import +#: model:ir.actions.act_window,name:despatch_advice_import.despatch_advice_import_action +msgid "Import" +msgstr "Import" + +#. module: despatch_advice_import +#: model_terms:ir.ui.view,arch_db:despatch_advice_import.despatch_advice_import_form_view +msgid "Import document" +msgstr "Importer le document" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import____last_update +msgid "Last Modified on" +msgstr "Dernière modification le" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import__write_uid +msgid "Last Updated by" +msgstr "Dernière mise à jour par" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import__write_date +msgid "Last Updated on" +msgstr "Dernière mise à jour le" + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "Missing document file" +msgstr "Fichier de document manquant" + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "Missing document filename" +msgstr "Nom de fichier manquant" + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "No purchase order found for name %s." +msgstr "Aucune commande fournisseur trouvée pour le nom %s." + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/tests/test_despatch_advice_import.py:0 +#, python-format +msgid "No purchase order found for name 123456." +msgstr "Aucune commande fournisseur trouvée avec le nom 123456." + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "The product quantity is greater than the original product quantity" +msgstr "" +"La quantité de produit est supérieure à la quantité de produit commandée" + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "There are no embedded XML file in this PDF file." +msgstr "Il n'y a pas de fichier XML intégré dans ce fichier PDF." + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "This XML file is not XML-compliant" +msgstr "Ce fichier XML n'est pas conforme" + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "" +"This file '%s' is not recognised as XML nor PDF file. Please check the file " +"and it's extension." +msgstr "" +"Le fichier '%s' n'est pas reconnu comme un fichier XML ou PDF. Veuillez " +"vérifier le fichier et son extension." + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "" +"This type of XML Order Document is not supported. Did you install the module" +" to support this XML format?" +msgstr "" +"Ce type de document de commande XML n'est pas pris en charge. Avez-vous " +"installé le module pour supporter ce format XML ?" + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "" +"This type of XML Order Response is not supported. Did you install the module" +" to support this XML format?" +msgstr "" +"Ce type de réponse de commande XML n'est pas pris en charge. Avez-vous " +"installé le module pour supporter ce format XML ?" + +#. module: despatch_advice_import +#: model:ir.ui.menu,name:despatch_advice_import.despatch_advice_import_importer_menu +msgid "UBL Despatch Advice Importer" +msgstr "Importateur d'avis d'expédition UBL" + +#. module: despatch_advice_import +#: model_terms:ir.ui.view,arch_db:despatch_advice_import.despatch_advice_import_form_view +msgid "Universal Business Language" +msgstr "Universal Business Language" + +#. module: despatch_advice_import +#: model:ir.model.fields,help:despatch_advice_import.field_despatch_advice_import__document +msgid "" +"Upload an Despatch Advice file that you received from your supplier. " +"Supported formats: XML and PDF (PDF with an embeded XML file)." +msgstr "" +"Téléchargez un fichier d'avis d'expédition que vous avez reçu de votre " +"fournisseur. Formats pris en charge : XML et PDF (PDF avec un fichier XML " +"intégré)." + +#. module: despatch_advice_import +#: model_terms:ir.ui.view,arch_db:despatch_advice_import.despatch_advice_import_form_view +msgid "" +"Upload below the DespatchAdvice you received from your supplier. When you " +"click on the import button:" +msgstr "" +"Téléchargez ci-dessous l'avis d'expédition que vous avez reçu de votre " +"fournisseur. Lorsque vous cliquez sur le bouton d'importation :" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import__document +msgid "XML or PDF Despatch Advice" +msgstr "Avis d'expédition XML ou PDF" + +#. module: despatch_advice_import +#: model_terms:ir.ui.view,arch_db:despatch_advice_import.despatch_advice_import_form_view +msgid "" +"format (UBL), you should install the module " +"despatch_advice_import_ubl." +msgstr "" +"format (UBL), vous devez installer le module " +"despatch_advice_import_ubl." diff --git a/despatch_advice_import/i18n/it.po b/despatch_advice_import/i18n/it.po new file mode 100644 index 0000000000..d7437bcc45 --- /dev/null +++ b/despatch_advice_import/i18n/it.po @@ -0,0 +1,251 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * despatch_advice_import +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2024-08-23 15:06+0000\n" +"Last-Translator: mymage \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" +"X-Generator: Weblate 5.6.2\n" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import__allow_validate_over_qty +msgid "Allow Validate Over Quantity" +msgstr "Consenti validazione sovra quantità" + +#. module: despatch_advice_import +#: model_terms:ir.ui.view,arch_db:despatch_advice_import.despatch_advice_import_form_view +msgid "Cancel" +msgstr "Annulla" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import__create_uid +msgid "Created by" +msgstr "Creato da" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import__create_date +msgid "Created on" +msgstr "Creato il" + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "Delivery cancelled by the supplier." +msgstr "Consegna annullata dal fornitore." + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "Delivery confirmed by the supplier." +msgstr "Consegna confermata dal fornitore." + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "Delivery confirmed with amendment by the supplier." +msgstr "Consegna confermata con correzione dal fornitore." + +#. module: despatch_advice_import +#: model_terms:ir.ui.view,arch_db:despatch_advice_import.despatch_advice_import_form_view +msgid "Despatch Advice Import" +msgstr "Importa avviso spedizione" + +#. module: despatch_advice_import +#: model:ir.model,name:despatch_advice_import.model_despatch_advice_import +msgid "Despatch Advice Import from Files" +msgstr "Importazione di avvisi di spedizione da file" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import__display_name +msgid "Display Name" +msgstr "Nome visualizzato" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import__filename +msgid "File Name" +msgstr "Nome file" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import__id +msgid "ID" +msgstr "ID" + +#. module: despatch_advice_import +#: model_terms:ir.ui.view,arch_db:despatch_advice_import.despatch_advice_import_form_view +msgid "" +"If it is a PDF file, Odoo will try to find an XML file in the attachments of" +" the PDF file and then use this XML file." +msgstr "" +"Se è un file PDF, Odoo cercherà di trovare un file XML negli allegati del " +"file PDF e usarlo." + +#. module: despatch_advice_import +#: model_terms:ir.ui.view,arch_db:despatch_advice_import.despatch_advice_import_form_view +msgid "" +"If it is an XML file, Odoo will parse it if the module that adds support for" +" this XML format is installed. For the" +msgstr "" +"Se è un file XML, Odoo lo analizzerà se è installato il modulo che aggiunge " +"il supporto per questo formato XML. Per il" + +#. module: despatch_advice_import +#: model:ir.actions.act_window,name:despatch_advice_import.despatch_advice_import_action +msgid "Import" +msgstr "Importa" + +#. module: despatch_advice_import +#: model_terms:ir.ui.view,arch_db:despatch_advice_import.despatch_advice_import_form_view +msgid "Import document" +msgstr "Importa documento" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import____last_update +msgid "Last Modified on" +msgstr "Ultima modifica il" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import__write_uid +msgid "Last Updated by" +msgstr "Ultimo aggiornamento di" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import__write_date +msgid "Last Updated on" +msgstr "Ultimo aggiornamento il" + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "Missing document file" +msgstr "File documento mancante" + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "Missing document filename" +msgstr "Nome file documento mancante" + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "No purchase order found for name %s." +msgstr "Nessun ordine di acquisto trovato per il nome %s." + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/tests/test_despatch_advice_import.py:0 +#, python-format +msgid "No purchase order found for name 123456." +msgstr "Nessun ordine di acquisto trovato per il nome 123456." + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "The product quantity is greater than the original product quantity" +msgstr "La quantità del prodotto è superiore alla quantità originale" + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "There are no embedded XML file in this PDF file." +msgstr "In questo PDF non c'è un file XML integrato." + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "This XML file is not XML-compliant" +msgstr "Il file XML non è compatibile al formato XML" + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "" +"This file '%s' is not recognised as XML nor PDF file. Please check the file " +"and it's extension." +msgstr "" +"Il file '%s' non è riconosciuto come file XML o PDF. Controllare il file e " +"la sua estensione." + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "" +"This type of XML Order Document is not supported. Did you install the module" +" to support this XML format?" +msgstr "" +"Questo tipo di documento ordine XML non è supportato. Il modulo per " +"supportare questo formato XML è installato?" + +#. module: despatch_advice_import +#. odoo-python +#: code:addons/despatch_advice_import/wizard/despatch_advice_import.py:0 +#, python-format +msgid "" +"This type of XML Order Response is not supported. Did you install the module" +" to support this XML format?" +msgstr "" +"Questo tipo di risposta ordine XML non è supportato. Il modulo per " +"supportare questo formato XML è installato?" + +#. module: despatch_advice_import +#: model:ir.ui.menu,name:despatch_advice_import.despatch_advice_import_importer_menu +msgid "UBL Despatch Advice Importer" +msgstr "Importatore avviso spedizione UBL" + +#. module: despatch_advice_import +#: model_terms:ir.ui.view,arch_db:despatch_advice_import.despatch_advice_import_form_view +msgid "Universal Business Language" +msgstr "Linguaggio commerciale universale" + +#. module: despatch_advice_import +#: model:ir.model.fields,help:despatch_advice_import.field_despatch_advice_import__document +msgid "" +"Upload an Despatch Advice file that you received from your supplier. " +"Supported formats: XML and PDF (PDF with an embeded XML file)." +msgstr "" +"Caricare un avviso di spedizione ricevuto dal fornitore. Formati supportati: " +"XML e PDF (PDF con XML incorporato)." + +#. module: despatch_advice_import +#: model_terms:ir.ui.view,arch_db:despatch_advice_import.despatch_advice_import_form_view +msgid "" +"Upload below the DespatchAdvice you received from your supplier. When you " +"click on the import button:" +msgstr "" +"Caricare in calce l'avviso di spedizione ricevuto dal fornitore. Quando si " +"fa clic sul pulsante di importazione:" + +#. module: despatch_advice_import +#: model:ir.model.fields,field_description:despatch_advice_import.field_despatch_advice_import__document +msgid "XML or PDF Despatch Advice" +msgstr "Avviso di spedizione XML o PDF" + +#. module: despatch_advice_import +#: model_terms:ir.ui.view,arch_db:despatch_advice_import.despatch_advice_import_form_view +msgid "" +"format (UBL), you should install the module " +"despatch_advice_import_ubl." +msgstr "" +"formato (UBL), bisogna installare il modulo " +"despatch_advice_import_ubl." diff --git a/despatch_advice_import/pyproject.toml b/despatch_advice_import/pyproject.toml new file mode 100644 index 0000000000..4231d0cccb --- /dev/null +++ b/despatch_advice_import/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/despatch_advice_import/readme/CONTRIBUTORS.md b/despatch_advice_import/readme/CONTRIBUTORS.md new file mode 100644 index 0000000000..fa6020e04a --- /dev/null +++ b/despatch_advice_import/readme/CONTRIBUTORS.md @@ -0,0 +1,9 @@ +- Laurent Mignon \ + +- [Trobz](https://trobz.com): + + > - Thien \ + +- Simone Orsi \ + +- Jacques-Etienne Baudoux \ diff --git a/despatch_advice_import/readme/CREDITS.md b/despatch_advice_import/readme/CREDITS.md new file mode 100644 index 0000000000..6305ac9892 --- /dev/null +++ b/despatch_advice_import/readme/CREDITS.md @@ -0,0 +1,2 @@ +The migration of this module from 10.0 to 16.0 was financially supported +by Camptocamp diff --git a/despatch_advice_import/readme/DESCRIPTION.md b/despatch_advice_import/readme/DESCRIPTION.md new file mode 100644 index 0000000000..56252e64ac --- /dev/null +++ b/despatch_advice_import/readme/DESCRIPTION.md @@ -0,0 +1 @@ +This module will support import despatch advice file diff --git a/despatch_advice_import/security/ir.model.access.csv b/despatch_advice_import/security/ir.model.access.csv new file mode 100644 index 0000000000..d81576035b --- /dev/null +++ b/despatch_advice_import/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_despatch_advice_import,despatch.advice.import,model_despatch_advice_import,purchase.group_purchase_user,1,1,1,1 diff --git a/despatch_advice_import/static/description/icon.png b/despatch_advice_import/static/description/icon.png new file mode 100644 index 0000000000..3a0328b516 Binary files /dev/null and b/despatch_advice_import/static/description/icon.png differ diff --git a/despatch_advice_import/static/description/index.html b/despatch_advice_import/static/description/index.html new file mode 100644 index 0000000000..b0ce336881 --- /dev/null +++ b/despatch_advice_import/static/description/index.html @@ -0,0 +1,448 @@ + + + + + +README.rst + + + +
+ + + +Odoo Community Association + +
+

Despatch Advice Import

+ +

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

+

This module will support import despatch advice file

+

Table of contents

+ +
+

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

+
    +
  • ACSONE SA/NV
  • +
  • BCIM
  • +
+
+
+

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.

+

Current maintainer:

+

jbaudoux

+

This module is part of the OCA/edi project on GitHub.

+

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

+
+
+
+
+ + diff --git a/despatch_advice_import/tests/__init__.py b/despatch_advice_import/tests/__init__.py new file mode 100644 index 0000000000..d201e095b5 --- /dev/null +++ b/despatch_advice_import/tests/__init__.py @@ -0,0 +1,2 @@ +from . import common +from . import test_despatch_advice_import diff --git a/despatch_advice_import/tests/common.py b/despatch_advice_import/tests/common.py new file mode 100644 index 0000000000..e675a3b54f --- /dev/null +++ b/despatch_advice_import/tests/common.py @@ -0,0 +1,60 @@ +# Copyright 2026 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import Command, fields +from odoo.tests.common import TransactionCase + + +class TestDespatchAdviceImportCommon(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) + cls.DespatchAdviceImport = cls.env["despatch.advice.import"] + cls.env.company.partner_id.vat = "BE0421801233" + cls.supplier = cls.env["res.partner"].create( + { + "name": "Test Supplier", + "supplier_rank": 1, + "vat": "BE0477472701", + } + ) + + @classmethod + def _create_product(cls, name, code, supplier_code): + return cls.env["product.product"].create( + { + "name": name, + "default_code": code, + "seller_ids": [ + Command.create( + { + "partner_id": cls.supplier.id, + "product_code": supplier_code, + } + ) + ], + } + ) + + @classmethod + def _get_po_line_vals(cls, product, product_qty, price_unit): + return { + "product_id": product.id, + "name": product.name, + "date_planned": fields.Datetime.now(), + "product_qty": product_qty, + "product_uom_id": cls.env.ref("uom.product_uom_unit").id, + "price_unit": price_unit, + } + + @classmethod + def _create_purchase_order(cls, line_vals_list): + return cls.env["purchase.order"].create( + { + "partner_id": cls.supplier.id, + "date_order": fields.Datetime.now(), + "date_planned": fields.Datetime.now(), + "order_line": [Command.create(vals) for vals in line_vals_list], + } + ) diff --git a/despatch_advice_import/tests/test_despatch_advice_import.py b/despatch_advice_import/tests/test_despatch_advice_import.py new file mode 100644 index 0000000000..9d75a68afd --- /dev/null +++ b/despatch_advice_import/tests/test_despatch_advice_import.py @@ -0,0 +1,300 @@ +# Copyright 2020 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import base64 + +from odoo.exceptions import UserError + +from .common import TestDespatchAdviceImportCommon + + +class TestDespatchAdviceImport(TestDespatchAdviceImportCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.product_1 = cls._create_product("Product 1", "987654321", "P1") + cls.product_2 = cls._create_product("Product 2", "987654312", "P2") + cls.product_3 = cls._create_product("Product 3", "123456789", "P3") + cls.product_4 = cls._create_product("Product 4", "23456718", "P4") + cls.purchase_order = cls._create_purchase_order( + [ + cls._get_po_line_vals(cls.product_1, 24, 15), + cls._get_po_line_vals(cls.product_2, 5, 25), + cls._get_po_line_vals(cls.product_3, 15, 25), + cls._get_po_line_vals(cls.product_4, 15, 25), + ] + ) + cls.line1, cls.line2, cls.line3, cls.line4 = cls.purchase_order.order_line + cls.purchase_order.button_confirm() + + cls.DespatchAdviceImport = cls.env["despatch.advice.import"].create( + {"document": base64.b64encode(bytes("", "utf-8"))} + ) + + def order_line_to_data(self, order_line, qty=None, backorder_qty=None): + return { + "backorder_qty": backorder_qty, + "qty": qty if qty is not None else order_line.product_qty, + "order_line_id": order_line.id, + "ref": order_line.order_id.name, + "product_ref": order_line.product_id.default_code, + "uom": {"unece_code": order_line.product_uom_id.unece_code}, + } + + def _get_base_data(self): + return { + "company": {"vat": "BE0421801233"}, + "date": "2020-02-04", + "chatter_msg": [], + "lines": [], + "supplier": {"vat": "BE0477472701"}, + "ref": str(self.purchase_order.name), + } + + def test_no_purchase_order_name(self): + """Raise an error when the imported line references an unknown PO.""" + data = self._get_base_data() + data["ref"] = "123456" + data["lines"] = [self.order_line_to_data(self.line1)] + data["lines"][0]["ref"] = "123456" + + with self.assertRaises(UserError) as ue: + self.DespatchAdviceImport.process_data(data) + self.assertEqual( + ue.exception.args[0], + self.env._("No purchase order found for name %(name)s.", name="123456"), + ) + + def test_process_data_with_backorder_qty(self): + """Split the move and keep the postponed quantity on a backorder.""" + data = self._get_base_data() + confirmed_qty = self.line1.product_qty - 21 + data["lines"] = [ + self.order_line_to_data(self.line1, qty=confirmed_qty, backorder_qty=21), + self.order_line_to_data(self.line2), + self.order_line_to_data(self.line3), + self.order_line_to_data(self.line4), + ] + self.DespatchAdviceImport.process_data(data) + + self.assertTrue(self.purchase_order.picking_ids) + move_ids = self.line1.move_ids + self.assertEqual(len(move_ids), 2) + self.assertEqual(sum(move_ids.mapped("product_qty")), self.line1.product_qty) + assigned = move_ids.filtered(lambda s: s.state == "done" and s.product_qty == 3) + self.assertEqual(assigned.product_qty, confirmed_qty) + + move_backorder = move_ids.filtered( + lambda s: s.state == "assigned" and s.product_qty == 21 + ) + self.assertTrue(move_backorder) + self.assertEqual(move_backorder.picking_id.backorder_id, assigned.picking_id) + + def test_process_data_with_no_backorder_qty(self): + """Split the move and cancel the remaining quantity without backorder.""" + data = self._get_base_data() + confirmed_qty = self.line1.product_qty - 21 + data["lines"] = [ + self.order_line_to_data(self.line1, qty=confirmed_qty), + self.order_line_to_data(self.line2), + self.order_line_to_data(self.line3), + self.order_line_to_data(self.line4), + ] + self.DespatchAdviceImport.process_data(data) + + self.assertTrue(self.purchase_order.picking_ids) + move_ids = self.line1.move_ids + self.assertEqual(len(move_ids), 2) + self.assertEqual(sum(move_ids.mapped("product_qty")), self.line1.product_qty) + assigned = move_ids.filtered(lambda s: s.state == "done") + self.assertEqual(assigned.product_qty, confirmed_qty) + cancel = move_ids.filtered(lambda s: s.state == "cancel") + self.assertEqual(cancel.product_qty, 21) + + def test_process_data_create_backorder(self): + """Reuse the same backorder picking for postponed quantities on two lines.""" + data = self._get_base_data() + line1_confirmed_qty = self.line1.product_qty - 3 + line2_confirmed_qty = self.line2.product_qty - 3 + data["lines"] = [ + self.order_line_to_data( + self.line1, + qty=line1_confirmed_qty, + backorder_qty=3, + ), + self.order_line_to_data( + self.line2, + qty=line2_confirmed_qty, + backorder_qty=3, + ), + self.order_line_to_data(self.line3), + self.order_line_to_data(self.line4), + ] + + self.DespatchAdviceImport.process_data(data) + self.assertEqual(self.purchase_order.state, "purchase") + self.assertEqual(len(self.purchase_order.picking_ids), 2) + # line1 + line1_move_ids = self.line1.move_ids + self.assertEqual(len(line1_move_ids), 2) + self.assertEqual( + sum(line1_move_ids.mapped("product_qty")), self.line1.product_qty + ) + move_confirmed = line1_move_ids.filtered( + lambda s: s.state == "done" and s.product_qty == line1_confirmed_qty + ) + self.assertTrue(move_confirmed) + self.assertEqual(move_confirmed.product_qty, line1_confirmed_qty) + move_backorder = line1_move_ids.filtered( + lambda s: s.state == "assigned" and s.product_qty == 3 + ) + self.assertTrue(move_backorder) + self.assertEqual( + move_backorder.picking_id.backorder_id, + move_confirmed.picking_id, + ) + + # line2 + line2_move_ids = self.line2.move_ids + self.assertEqual(len(line2_move_ids), 2) + self.assertEqual( + sum(line2_move_ids.mapped("product_qty")), self.line2.product_qty + ) + move_confirmed = line2_move_ids.filtered( + lambda s: s.state == "done" and s.product_qty == line2_confirmed_qty + ) + self.assertTrue(move_confirmed) + self.assertEqual(move_confirmed.product_qty, line2_confirmed_qty) + + move_backorder = line2_move_ids.filtered( + lambda s: s.state == "assigned" and s.product_qty == 3 + ) + self.assertTrue(move_backorder) + self.assertEqual( + move_backorder.picking_id.backorder_id, + move_confirmed.picking_id, + ) + + def test_partial_delivery_with_backorder(self): + """Backorder only the postponed part and cancel the leftover remainder.""" + data = self._get_base_data() + confirmed_qty = self.line1.product_qty - 3 + data["lines"] = [ + self.order_line_to_data( + self.line1, + qty=confirmed_qty, + backorder_qty=2, + ), + self.order_line_to_data(self.line2), + self.order_line_to_data(self.line3), + self.order_line_to_data(self.line4), + ] + self.DespatchAdviceImport.process_data(data) + self.assertEqual(len(self.purchase_order.picking_ids), 2) + move_ids = self.line1.move_ids + + self.assertEqual(len(move_ids), 3) + self.assertEqual(sum(move_ids.mapped("product_qty")), self.line1.product_qty) + move_confirmed = move_ids.filtered( + lambda s: s.state == "done" and s.product_qty == confirmed_qty + ) + self.assertTrue(move_confirmed) + move_cancel = move_ids.filtered( + lambda s: s.state == "cancel" and s.product_qty == 1 + ) + self.assertTrue(move_cancel) + move_backorder = move_ids.filtered( + lambda s: s.state == "assigned" and s.product_qty == 2 + ) + + self.assertTrue(move_backorder) + self.assertEqual( + move_backorder.picking_id.backorder_id, + move_confirmed.picking_id, + ) + + def test_qty_larger_backorder_qty(self): + """Cancel the extra remainder when confirmed quantity exceeds backorder qty.""" + data = self._get_base_data() + confirmed_qty = 6 + data["lines"] = [ + self.order_line_to_data(self.line1), + self.order_line_to_data(self.line2), + self.order_line_to_data(self.line3, qty=confirmed_qty, backorder_qty=3), + self.order_line_to_data(self.line4), + ] + self.DespatchAdviceImport.process_data(data) + self.assertEqual(len(self.purchase_order.picking_ids), 2) + move_ids = self.line3.move_ids + self.assertEqual(len(move_ids), 3) + self.assertEqual(sum(move_ids.mapped("product_qty")), self.line3.product_qty) + moves_confirmed = move_ids.filtered( + lambda s: s.state == "done" and not s.picking_id.backorder_id + ) + self.assertEqual(sum(moves_confirmed.mapped("product_qty")), confirmed_qty) + + move_cancel = move_ids.filtered( + lambda s: s.state == "cancel" and s.product_qty == 6 + ) + self.assertTrue(move_cancel) + move_backorder = move_ids.filtered( + lambda s: s.state == "assigned" and s.product_qty == 3 + ) + self.assertTrue(move_backorder) + self.assertEqual( + move_backorder.picking_id.backorder_id, + moves_confirmed[0].picking_id, + ) + + def test_qty_equal_backorder_qty(self): + """Keep equal confirmed and backorder quantities and cancel the rest.""" + data = self._get_base_data() + confirmed_qty = 3 + data["lines"] = [ + self.order_line_to_data(self.line1), + self.order_line_to_data(self.line2), + self.order_line_to_data(self.line3), + self.order_line_to_data( + self.line4, + qty=confirmed_qty, + backorder_qty=3, + ), + ] + self.DespatchAdviceImport.process_data(data) + self.assertEqual(len(self.purchase_order.picking_ids), 2) + move_ids = self.line4.move_ids + self.assertEqual(sum(move_ids.mapped("product_qty")), self.line4.product_qty) + moves_confirmed = move_ids.filtered( + lambda s: s.state == "done" and not s.picking_id.backorder_id + ) + self.assertEqual(sum(moves_confirmed.mapped("product_qty")), 3) + + moves_cancel = move_ids.filtered( + lambda s: s.state == "cancel" and not s.picking_id.backorder_id + ) + self.assertEqual(sum(moves_cancel.mapped("product_qty")), 9) + moves_backorder = move_ids.filtered( + lambda s: s.state == "assigned" and s.picking_id.backorder_id + ) + self.assertEqual(sum(moves_backorder.mapped("product_qty")), 3) + + def test_confirmed_qty_larger_reserved_qty(self): + """Allow over-delivery when the imported confirmed quantity exceeds reserved.""" + data = self._get_base_data() + confirmed_qty = self.line1.product_qty + 6 + data["lines"] = [ + self.order_line_to_data(self.line1, qty=confirmed_qty), + self.order_line_to_data(self.line2), + self.order_line_to_data(self.line3), + self.order_line_to_data(self.line4), + ] + self.DespatchAdviceImport.with_context( + allow_validate_over_qty=True + ).process_data(data) + + self.assertTrue(self.purchase_order.picking_ids) + move_ids = self.line1.move_ids + self.assertEqual(len(move_ids), 1) + self.assertEqual(sum(move_ids.mapped("product_qty")), self.line1.product_qty) + assigned = move_ids.filtered(lambda s: s.state == "done") + self.assertEqual(assigned.quantity, confirmed_qty) diff --git a/despatch_advice_import/wizard/__init__.py b/despatch_advice_import/wizard/__init__.py new file mode 100644 index 0000000000..ce838ad980 --- /dev/null +++ b/despatch_advice_import/wizard/__init__.py @@ -0,0 +1 @@ +from . import despatch_advice_import diff --git a/despatch_advice_import/wizard/despatch_advice_import.py b/despatch_advice_import/wizard/despatch_advice_import.py new file mode 100644 index 0000000000..0696f61dc9 --- /dev/null +++ b/despatch_advice_import/wizard/despatch_advice_import.py @@ -0,0 +1,453 @@ +# Copyright 2020 ACSONE SA/NV +# Copyright 2025 Jacques-Etienne Baudoux (BCIM) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging +import mimetypes +from base64 import b64decode, b64encode + +from lxml import etree + +from odoo import api, fields, models +from odoo.exceptions import UserError +from odoo.tools import float_compare + +logger = logging.getLogger(__name__) + + +class DespatchAdviceImport(models.TransientModel): + """Import a supplier despatch advice and apply it on incoming receipts. + + The wizard parses a despatch advice document, matches it to purchase order + lines, and then updates the related stock moves so the receipt reflects + what the supplier confirmed, postponed, or canceled. + """ + + _name = "despatch.advice.import" + _description = "Despatch Advice Import from Files" + + document = fields.Binary( + string="XML or PDF Despatch Advice", + required=True, + help="Upload an Despatch Advice file that you received from " + "your supplier. Supported formats: XML and PDF " + "(PDF with an embeded XML file).", + ) + filename = fields.Char(string="File Name") + allow_validate_over_qty = fields.Boolean( + "Allow Validate Over Quantity", default=True + ) + + # Format of parsed despatch advice + # { + # 'ref': 'PO01234' # the buyer party identifier + # # (specified into the Order document -> po's name) + # 'despatch_advice_type_code': ' scheduled | delivered' + # 'supplier': {'vat': 'FR25499247138'}, + # 'company': {'vat': 'FR12123456789'}, # Only used to check we are not + # # importing the quote in the + # # wrong company by mistake + # 'estimated_delivery_date': '2020-11-20' + # 'lines': [{ + # 'id': 123456, + # 'qty': 2.5, + # 'uom': {'unece_code': 'C62'}, + # 'backorder_qty: None # if provided and qty != expected + # # the backorder qty will be delivered + # # in a next shipping + # }] + + @api.model + def parse_despatch_advice(self, document: bytes, filename: str): + """Parse an uploaded XML or PDF document into a normalized dict. + + :param document: The raw content of the uploaded file, as bytes. + :param filename: The name of the uploaded file, used to detect the file type. + :return: A dict with the parsed despatch advice data, in a normalized format. + """ + if not document: + raise UserError(self.env._("Missing document file")) + if not filename: + raise UserError(self.env._("Missing document filename")) + # Detect the file type from the uploaded filename extension to choose + # the appropriate XML or PDF parsing flow. + filetype = mimetypes.guess_type(filename)[0] + logger.debug("DespatchAdvice file mimetype: %s", filetype) + if filetype in ["application/xml", "text/xml"]: + try: + xml_root = etree.fromstring(document) + except Exception as err: + raise UserError( + self.env._("This XML file is not XML-compliant") + ) from err + if logger.isEnabledFor(logging.DEBUG): + # Format the parsed XML for easier inspection in debug logs. + pretty_xml_string = etree.tostring( + xml_root, pretty_print=True, encoding="UTF-8", xml_declaration=True + ) + logger.debug("Starting to import the following XML file:") + logger.debug(pretty_xml_string) + parsed_despatch_advice = self.parse_xml_despatch_advice(xml_root) + elif filetype == "application/pdf": + parsed_despatch_advice = self.parse_pdf_despatch_advice(document) + else: + raise UserError( + self.env._( + "This file '%(filename)s' is not recognised as XML nor PDF file. " + "Please check the file and it's extension.", + filename=filename, + ) + ) + logger.debug( + "Result of Despatch Advice parsing: %(advice)s", + {"advice": parsed_despatch_advice}, + ) + if "attachments" not in parsed_despatch_advice: + parsed_despatch_advice["attachments"] = {} + parsed_despatch_advice["attachments"][filename] = b64encode(document) + chatter_msg = parsed_despatch_advice.get("chatter_msg") + if chatter_msg is None: + parsed_despatch_advice["chatter_msg"] = [] + elif isinstance(chatter_msg, (tuple, set)): + parsed_despatch_advice["chatter_msg"] = list(chatter_msg) + elif not isinstance(chatter_msg, list): + parsed_despatch_advice["chatter_msg"] = [chatter_msg] + if parsed_despatch_advice.get("company") and not self.env.context.get( + "edi_skip_company_check" + ): + self.env["business.document.import"]._check_company( + parsed_despatch_advice["company"], parsed_despatch_advice["chatter_msg"] + ) + defaults = self.env.context.get("despatch_advice_import__default_vals", {}).get( + "despatch_advice", {} + ) + parsed_despatch_advice.update(defaults) + return parsed_despatch_advice + + @api.model + def parse_xml_despatch_advice(self, xml_root: etree._Element): + """Parse a despatch advice XML tree with a format-specific implementation. + + :param xml_root: The parsed XML root element to interpret. + :return: A normalized dict containing the parsed despatch advice data. + """ + raise UserError( + self.env._( + "This type of XML Order Response is not supported. Did you " + "install the module to support this XML format?" + ) + ) + + @api.model + def parse_pdf_despatch_advice(self, document: bytes): + """Extract embedded XML files from a PDF and parse the first supported one. + + :param document: The raw content of the uploaded PDF file, as bytes. + :return: A normalized dict containing the parsed despatch advice data. + """ + xml_files_dict = self.get_xml_files_from_pdf(document) + if not xml_files_dict: + raise UserError( + self.env._("There are no embedded XML file in this PDF file.") + ) + for xml_filename, xml_root in xml_files_dict.items(): + logger.info("Trying to parse XML file %s", xml_filename) + try: + parsed_despatch_advice = self.parse_xml_despatch_advice(xml_root) + return parsed_despatch_advice + except Exception: + continue + raise UserError( + self.env._( + "This type of XML Order Document is not supported. Did you " + "install the module to support this XML format?" + ) + ) + + @api.model + def get_xml_files_from_pdf(self, document: bytes): + """Return embedded XML attachments from a PDF as parsed XML roots. + + :param document: The raw content of the uploaded PDF file, as bytes. + :return: A dict mapping embedded XML filenames to parsed XML root elements. + """ + return self.env["pdf.xml.tool"].pdf_get_xml_files(document) + + def process_document(self): + """Decode the uploaded file, parse it, and apply its stock impact.""" + self.ensure_one() + parsed_order_document = self.parse_despatch_advice( + b64decode(self.document), self.filename + ) + self.process_data(parsed_order_document) + + def _collect_lines_by_id(self, lines_doc, key: str = "order_line_id"): + """Aggregate imported lines by purchase order line id. + + A despatch advice may contain several entries for the same purchase + line, so we consolidate quantities, lots, and UoM codes first. + + :param lines_doc: The parsed despatch advice lines to group and normalize. + :param key: The line dict key containing the purchase order line identifier. + :return: A dict mapping purchase order line ids to aggregated line data. + """ + lines_by_id = {} + for line in lines_doc: + line = dict(line) + line["qty"] = line.get("qty") or 0.0 + line["backorder_qty"] = line.get("backorder_qty") or 0.0 + line_id = int(line[key]) + if line_id in lines_by_id: + lines_by_id[line_id]["qty"] += line["qty"] + lines_by_id[line_id]["backorder_qty"] += line["backorder_qty"] + if "product_lot" in line: + lines_by_id[line_id]["product_lot"].append(line["product_lot"]) + lines_by_id[line_id]["product_lot"] = list( + set(lines_by_id[line_id]["product_lot"]) + ) + lines_by_id[line_id]["uom"]["unece_code"].append( + line["uom"]["unece_code"] + ) + lines_by_id[line_id]["uom"]["unece_code"] = list( + set(lines_by_id[line_id]["uom"]["unece_code"]) + ) + else: + lines_by_id[line_id] = line + if "product_lot" in line: + lines_by_id[line_id]["product_lot"] = [ + lines_by_id[line_id]["product_lot"] + ] + lines_by_id[line_id]["uom"]["unece_code"] = [ + lines_by_id[line_id]["uom"]["unece_code"] + ] + return lines_by_id + + def process_data(self, parsed_order_document: dict): + """Apply the parsed despatch advice to the matching purchase moves. + + :param parsed_order_document: The normalized despatch advice data to apply. + """ + po_name = parsed_order_document.get("ref") + lines_doc = parsed_order_document.get("lines") + lines_by_id = self._collect_lines_by_id(lines_doc) + lines = self.env["purchase.order.line"].browse(list(lines_by_id)) + for line in lines: + order = line.order_id + line_info = lines_by_id.get(line.id) + + if line_info["ref"]: + if order.name != line_info["ref"]: + raise UserError( + self.env._( + "No purchase order found for name %(name)s.", + name=line_info["ref"], + ), + ) + else: + if order.name != po_name: + raise UserError( + self.env._( + "No purchase order found for name %(name)s.", + name=po_name, + ) + ) + stock_moves = line.move_ids.filtered( + lambda x: x.state not in ("cancel", "done") + ) + moves_qty = sum(stock_moves.mapped("product_uom_qty")) + # Compare the supplier-confirmed quantity with the open moves to + # decide whether we fully accept, reject, or partially split them. + if line_info["qty"] == moves_qty: + self._process_accepted(stock_moves, parsed_order_document) + elif line_info["qty"] > moves_qty and self.allow_validate_over_qty: + self._process_accepted( + stock_moves, parsed_order_document, forced_qty=line_info["qty"] + ) + elif not line_info["qty"] and not line_info["backorder_qty"]: + self._process_rejected(stock_moves, parsed_order_document) + else: + self._process_conditional(stock_moves, parsed_order_document, line_info) + self._process_picking_done(lines[0].move_ids[0]) + + def _process_picking_done(self, move: models.Model): + """Validate the receipt once all line-level changes are applied. + + :param move: A stock move belonging to the picking that should be validated. + :return: True when the picking only contains canceled moves, else None. + """ + picking = move.picking_id + if all(line.state == "cancel" for line in picking.move_ids): + return True + # skip backorder wizard + picking.with_context( + skip_immediate=True, skip_backorder=True, skip_sms=True, skip_expired=True + ).button_validate() + + def _cancel_extra_moves(self, moves: models.Model): + """Cancel moves while mimicking the usual backorder-cancel context. + + :param moves: The stock moves that should be canceled. + """ + # Loose dependency with stock_picking_restrict_cancel_printed module + # that checks we are canceling the backorder to allow move cancellation. + # Mimic odoo setting this cancel_backorder context variable in this case. + moves.with_context(cancel_backorder=True)._action_cancel() + + def _process_rejected(self, stock_moves: models.Model, parsed_order_document: dict): + """Cancel remaining moves when the supplier cancels the delivery. + + :param stock_moves: The open stock moves linked to the purchase order line. + :param parsed_order_document: The normalized despatch advice data being applied. + """ + parsed_order_document["chatter_msg"] = parsed_order_document.get( + "chatter_msg", [] + ) + parsed_order_document["chatter_msg"].append( + self.env._("Delivery cancelled by the supplier.") + ) + self._cancel_extra_moves(stock_moves) + + def _process_accepted( + self, + stock_moves: models.Model, + parsed_order_document: dict, + forced_qty=False, + ): + """Handle a delivery confirmed as-is by the supplier. + + The supplier delivers exactly what was ordered, or more when over-delivery + is allowed. In both cases every move is marked done and picked so the + validation wizard completes without prompting for a backorder. + + Without forced_qty the done quantity matches each move's planned demand. + With forced_qty (over-delivery) that quantity replaces the planned demand + on every move, recording what the supplier actually announced. + + :param stock_moves: The open stock moves linked to the purchase order line. + :param parsed_order_document: The normalized despatch advice data being applied. + :param forced_qty: The supplier-announced quantity to record when it exceeds + the planned demand; False to use each move's planned quantity. + """ + parsed_order_document["chatter_msg"] = ( + parsed_order_document["chatter_msg"] or [] + ) + parsed_order_document["chatter_msg"].append( + self.env._("Delivery confirmed by the supplier.") + ) + stock_moves._action_confirm() + stock_moves._action_assign() + for move in stock_moves: + move._set_quantity_done(forced_qty or move.product_uom_qty) + move.picked = True + + def _split_move(self, move: models.Model, qty_to_split): + """Split a move and return the newly created split-off moves. + + :param move: The stock move that should be split. + :param qty_to_split: The quantity to split off from the original move. + :return: created stock moves, or an empty recordset if nothing was split. + """ + split_moves_vals = move.with_context(cancel_backorder=False)._split( + move.product_uom._compute_quantity( + qty_to_split, + move.product_id.uom_id, + rounding_method="HALF-UP", + ) + ) + if not split_moves_vals: + return self.env["stock.move"] + split_moves = self.env["stock.move"].create(split_moves_vals) + split_moves.with_context( + bypass_entire_pack=True, bypass_procurement_creation=True + )._action_confirm(merge=False) + return split_moves + + def _add_moves_to_backorder(self, moves: models.Model): + """Prepare postponed quantities for validation backorder. + + :param moves: The stock moves that should remain open on a backorder. + :return: The picking that will generate the backorder on validation. + """ + moves.write({"picked": False}) + return moves[:1].picking_id + + def _process_conditional( + self, moves: models.Model, parsed_order_document: dict, line: dict + ): + """Handle a partial delivery with backorder and cancellation logic. + + Confirmed quantities are marked done, postponed quantities are left + unpicked for the validation backorder, and any remaining quantity is + canceled. + + :param moves: The open stock moves linked to the purchase order line. + :param parsed_order_document: The normalized despatch advice data being applied. + :param line: The aggregated imported line data for the purchase order line. + :return: A tuple of `(moves_to_backorder, moves_to_cancel)` recordsets. + """ + digits = self.env["decimal.precision"].precision_get("Product Unit") + chatter = parsed_order_document["chatter_msg"] = ( + parsed_order_document["chatter_msg"] or [] + ) + chatter.append(self.env._("Delivery confirmed with amendment by the supplier.")) + + qty = line["qty"] + backorder_qty = line["backorder_qty"] + moves_qty = sum(moves.mapped("product_uom_qty")) + + if float_compare(qty, moves_qty, precision_digits=digits) >= 0: + raise UserError( + self.env._( + "The product quantity is greater than the original product quantity" + ) + ) + + # confirmed qty < ordered qty + move_ids_to_backorder = [] + move_ids_to_cancel = [] + for move in moves: + if move.product_uom.compare(qty, move.product_uom_qty) >= 0: + # This move is fully confirmed for the current delivery. + move._set_quantity_done(move.product_uom_qty) + move.picked = True + qty -= move.product_uom_qty + continue + if qty and move.product_uom.compare(qty, move.product_uom_qty) < 0: + # Only part of this move is delivered now, so we split the + # remaining quantity into a new move for later handling. + split_moves = self._split_move(move, move.product_uom_qty - qty) + move._set_quantity_done(move.product_uom_qty) + move.picked = True + move = split_moves[:1] + qty = 0.0 + if not backorder_qty: + # No postponed quantity was announced, + # so the remaining move is canceled. + move_ids_to_cancel.append(move.id) + continue + # The remaining quantity is postponed by the supplier and should + # stay open on a backorder picking. + if move.product_uom.compare(backorder_qty, move.product_uom_qty) < 0: + # Only part of this move belongs to the backorder; the extra + # quantity is split off and canceled. + split_moves = self._split_move( + move, move.product_uom_qty - backorder_qty + ) + move_ids_to_cancel += split_moves.ids + + backorder_qty -= move.product_uom_qty + move_ids_to_backorder.append(move.id) + + # move backorder moves to a backorder + if move_ids_to_backorder: + moves_to_backorder = self.env["stock.move"].browse(move_ids_to_backorder) + self._add_moves_to_backorder(moves_to_backorder) + else: + moves_to_backorder = self.env["stock.move"] + # cancel moves to cancel + if move_ids_to_cancel: + moves_to_cancel = self.env["stock.move"].browse(move_ids_to_cancel) + self._cancel_extra_moves(moves_to_cancel) + else: + moves_to_cancel = self.env["stock.move"] + return moves_to_backorder, moves_to_cancel diff --git a/despatch_advice_import/wizard/despatch_advice_import.xml b/despatch_advice_import/wizard/despatch_advice_import.xml new file mode 100644 index 0000000000..5aaaf55f4b --- /dev/null +++ b/despatch_advice_import/wizard/despatch_advice_import.xml @@ -0,0 +1,56 @@ + + + + + despatch.advice.import (in purchase_order_import) + despatch.advice.import + +
+ +
+

Upload below the DespatchAdvice you received from your supplier. When you click on the import button:

+
    +
  1. If it is an XML file, Odoo will parse it if the module that adds support for this XML format is installed. For the Universal Business Language format (UBL), you should install the module despatch_advice_import_ubl.
  2. +
  3. If it is a PDF file, Odoo will try to find an XML file in the attachments of the PDF file and then use this XML file.
  4. +
+
+
+ + + + + + +
+
+
+
+
+ + Import + despatch.advice.import + form + new + + + UBL Despatch Advice Importer + + + + +