From 3bfcdcf6ff547bedf257c323c1dcc3ecafb245a2 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Thu, 9 Jul 2020 11:11:22 +0200 Subject: [PATCH 01/63] Add edi_gs1 --- edi_gs1/README.rst | 86 + edi_gs1/TODO | 13 + edi_gs1/__init__.py | 2 + edi_gs1/__manifest__.py | 21 + edi_gs1/components/__init__.py | 1 + edi_gs1/components/application_receipt_ack.py | 30 + .../data/business_header_output_template.xml | 17 + .../data/business_header_qweb_template.xml | 65 + edi_gs1/data/gs1_backend_data.xml | 12 + edi_gs1/models/__init__.py | 2 + edi_gs1/models/edi_backend.py | 22 + edi_gs1/models/res_partner.py | 12 + edi_gs1/readme/CONTRIBUTORS.rst | 1 + edi_gs1/readme/DESCRIPTION.rst | 9 + edi_gs1/security/gs1_backend_acl.xml | 13 + edi_gs1/static/description/icon.png | Bin 0 -> 9455 bytes edi_gs1/static/description/index.html | 434 ++++ .../ApplicationReceiptAcknowledgement.xsd | 111 + .../static/schemas/gs1/ecom/eComCommon.xsd | 1858 +++++++++++++++++ .../schemas/gs1/shared/SharedCommon.xsd | 1506 +++++++++++++ edi_gs1/static/schemas/sbdh/BasicTypes.xsd | 20 + edi_gs1/static/schemas/sbdh/BusinessScope.xsd | 55 + .../schemas/sbdh/DocumentIdentification.xsd | 14 + edi_gs1/static/schemas/sbdh/Manifest.xsd | 19 + edi_gs1/static/schemas/sbdh/Partner.xsd | 26 + .../sbdh/StandardBusinessDocumentHeader.xsd | 25 + edi_gs1/tests/_OLD_common.py | 69 + edi_gs1/tests/__init__.py | 2 + edi_gs1/tests/_test_business_header_OLD.py | 127 ++ edi_gs1/tests/common.py | 47 + ...icationReceiptAcknowledgement.example1.xml | 65 + edi_gs1/tests/test_application_receipt_ack.py | 20 + edi_gs1/tests/test_business_header.py | 104 + edi_gs1/xmler.py | 135 ++ 34 files changed, 4943 insertions(+) create mode 100644 edi_gs1/README.rst create mode 100644 edi_gs1/TODO create mode 100644 edi_gs1/__init__.py create mode 100644 edi_gs1/__manifest__.py create mode 100644 edi_gs1/components/__init__.py create mode 100644 edi_gs1/components/application_receipt_ack.py create mode 100644 edi_gs1/data/business_header_output_template.xml create mode 100644 edi_gs1/data/business_header_qweb_template.xml create mode 100644 edi_gs1/data/gs1_backend_data.xml create mode 100644 edi_gs1/models/__init__.py create mode 100644 edi_gs1/models/edi_backend.py create mode 100644 edi_gs1/models/res_partner.py create mode 100644 edi_gs1/readme/CONTRIBUTORS.rst create mode 100644 edi_gs1/readme/DESCRIPTION.rst create mode 100644 edi_gs1/security/gs1_backend_acl.xml create mode 100644 edi_gs1/static/description/icon.png create mode 100644 edi_gs1/static/description/index.html create mode 100644 edi_gs1/static/schemas/gs1/ecom/ApplicationReceiptAcknowledgement.xsd create mode 100644 edi_gs1/static/schemas/gs1/ecom/eComCommon.xsd create mode 100644 edi_gs1/static/schemas/gs1/shared/SharedCommon.xsd create mode 100644 edi_gs1/static/schemas/sbdh/BasicTypes.xsd create mode 100644 edi_gs1/static/schemas/sbdh/BusinessScope.xsd create mode 100644 edi_gs1/static/schemas/sbdh/DocumentIdentification.xsd create mode 100644 edi_gs1/static/schemas/sbdh/Manifest.xsd create mode 100644 edi_gs1/static/schemas/sbdh/Partner.xsd create mode 100644 edi_gs1/static/schemas/sbdh/StandardBusinessDocumentHeader.xsd create mode 100644 edi_gs1/tests/_OLD_common.py create mode 100644 edi_gs1/tests/__init__.py create mode 100644 edi_gs1/tests/_test_business_header_OLD.py create mode 100644 edi_gs1/tests/common.py create mode 100644 edi_gs1/tests/examples/ApplicationReceiptAcknowledgement.example1.xml create mode 100644 edi_gs1/tests/test_application_receipt_ack.py create mode 100644 edi_gs1/tests/test_business_header.py create mode 100644 edi_gs1/xmler.py diff --git a/edi_gs1/README.rst b/edi_gs1/README.rst new file mode 100644 index 000000000..14f2fee15 --- /dev/null +++ b/edi_gs1/README.rst @@ -0,0 +1,86 @@ +============ +Base GS1 EDI +============ + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png + :target: https://odoo-community.org/page/development-status + :alt: Alpha +.. |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%2Fedi-lightgray.png?logo=github + :target: https://github.com/OCA/edi/tree/13.0/edi_gs1 + :alt: OCA/edi +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/edi-13-0/edi-13-0-edi_gs1 + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/226/13.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This is the base module for the implementation of the +`GS1 -The Global Language of Business `_ standard. + +This module contains methods to generate and parse XML files compliant with this standard. + +Beside basic machinery it provides common components to generate common GS1 schema elements. +Namely: + + * `StandardBusinessDocumentHeader` + +.. IMPORTANT:: + This is an alpha version, the data model and design can change at any time without warning. + Only for development or testing purpose, do not use in production. + `More details on development status `_ + +**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 smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* ACSONE + +Contributors +~~~~~~~~~~~~ + +* Simone Orsi + +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/edi `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/edi_gs1/TODO b/edi_gs1/TODO new file mode 100644 index 000000000..caf440dd3 --- /dev/null +++ b/edi_gs1/TODO @@ -0,0 +1,13 @@ +Inbound Instruction + +- [x] allow skipping keys / elements by returning none +- [x] check UOM code handling +- [x] finish mapping of what we know +- [] draft RF-EFR module w/ tests +- [] allow export from PO +- [] handle state of export +- [] use storage backend to store it +- [] add lookup of acknowledge message +- [] add new field code to UOM +- [] draft send ack +- [] draft Outbound Notification parse diff --git a/edi_gs1/__init__.py b/edi_gs1/__init__.py new file mode 100644 index 000000000..0f00a6730 --- /dev/null +++ b/edi_gs1/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import components diff --git a/edi_gs1/__manifest__.py b/edi_gs1/__manifest__.py new file mode 100644 index 000000000..d336f2f00 --- /dev/null +++ b/edi_gs1/__manifest__.py @@ -0,0 +1,21 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Base GS1 EDI", + "summary": """ + Base module for GS1 standard EDI exchange""", + "version": "13.0.1.0.0", + "development_status": "Alpha", + "license": "AGPL-3", + "author": "ACSONE,Odoo Community Association (OCA)", + "depends": ["component", "edi_exchange_template"], + "external_dependencies": {"python": ["xmlschema"]}, + "data": [ + "security/gs1_backend_acl.xml", + "data/gs1_backend_data.xml", + "data/business_header_qweb_template.xml", + "data/business_header_output_template.xml", + ], +} diff --git a/edi_gs1/components/__init__.py b/edi_gs1/components/__init__.py new file mode 100644 index 000000000..2fa9d7532 --- /dev/null +++ b/edi_gs1/components/__init__.py @@ -0,0 +1 @@ +# from . import application_receipt_ack diff --git a/edi_gs1/components/application_receipt_ack.py b/edi_gs1/components/application_receipt_ack.py new file mode 100644 index 000000000..ad04c5eee --- /dev/null +++ b/edi_gs1/components/application_receipt_ack.py @@ -0,0 +1,30 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import _, exceptions + +from odoo.addons.component.core import Component + + +class ApplicationReceiptAcknowledgement(Component): + """Parse GS1 applicationReceiptAcknowledgement. + """ + + _name = "gs1.input.applicationReceiptAcknowledgement" + _inherit = "gs1.input.mixin" + _usage = "gs1.in.ApplicationReceiptAcknowledgement" + _xsd_schema_path = "static/schemas/gs1/ecom/ApplicationReceiptAcknowledgement.xsd" + + def get_status(self, result): + try: + ack = result["applicationReceiptAcknowledgement"][0] + except IndexError: + raise exceptions.ValidationError( + _("applicationReceiptAcknowledgement element not found!") + ) + status_wrapper = ack["applicationResponseDocumentLevel"][0] + return status_wrapper["applicationResponseStatusCode"] + + def is_ok(self, result): + return self.get_status(result) == "RECEIVED" diff --git a/edi_gs1/data/business_header_output_template.xml b/edi_gs1/data/business_header_output_template.xml new file mode 100644 index 000000000..5a212615d --- /dev/null +++ b/edi_gs1/data/business_header_output_template.xml @@ -0,0 +1,17 @@ + + + + EDI Exchange - GS1 Business Header + gs1.BusinessHeader + + xml + + +# Default identifier to record name +result = {"instance_identifier": record.name} + + + diff --git a/edi_gs1/data/business_header_qweb_template.xml b/edi_gs1/data/business_header_qweb_template.xml new file mode 100644 index 000000000..2ab74ea3b --- /dev/null +++ b/edi_gs1/data/business_header_qweb_template.xml @@ -0,0 +1,65 @@ + + + + + diff --git a/edi_gs1/data/gs1_backend_data.xml b/edi_gs1/data/gs1_backend_data.xml new file mode 100644 index 000000000..8d9bfb0c6 --- /dev/null +++ b/edi_gs1/data/gs1_backend_data.xml @@ -0,0 +1,12 @@ + + + + default_gs1_backend_type + gs1 + + + GS1 + + + + diff --git a/edi_gs1/models/__init__.py b/edi_gs1/models/__init__.py new file mode 100644 index 000000000..fe5182755 --- /dev/null +++ b/edi_gs1/models/__init__.py @@ -0,0 +1,2 @@ +from . import edi_backend +from . import res_partner diff --git a/edi_gs1/models/edi_backend.py b/edi_gs1/models/edi_backend.py new file mode 100644 index 000000000..2dd74200d --- /dev/null +++ b/edi_gs1/models/edi_backend.py @@ -0,0 +1,22 @@ +# Copyright 2020 ACSONE SA +# @author Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class EDIBackend(models.Model): + _inherit = "edi.backend" + + # TODO: find another way to configure this w/out polluting edi backend + # TODO: add to view + lsp_partner_id = fields.Many2one( + string="Logistic Services Provider (LSP)", + comodel_name="res.partner", + domain=[("is_lsp", "=", True)], + ) + lsc_partner_id = fields.Many2one( + string="Logistic Services Client (LSC)", + comodel_name="res.partner", + default=lambda self: self.env.ref("base.main_partner").id, + ) diff --git a/edi_gs1/models/res_partner.py b/edi_gs1/models/res_partner.py new file mode 100644 index 000000000..6bf612db7 --- /dev/null +++ b/edi_gs1/models/res_partner.py @@ -0,0 +1,12 @@ +# Copyright 2020 ACSONE SA/NV () +# @author: Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from odoo import fields, models + + +class ResPartner(models.Model): + _inherit = "res.partner" + + # TODO: move to another module? Or, is there another module providing this? + gln_code = fields.Char() + is_lsp = fields.Boolean(string="Is Logistic Services Provider (LSP)") diff --git a/edi_gs1/readme/CONTRIBUTORS.rst b/edi_gs1/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..f583948be --- /dev/null +++ b/edi_gs1/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Simone Orsi diff --git a/edi_gs1/readme/DESCRIPTION.rst b/edi_gs1/readme/DESCRIPTION.rst new file mode 100644 index 000000000..491e592cf --- /dev/null +++ b/edi_gs1/readme/DESCRIPTION.rst @@ -0,0 +1,9 @@ +This is the base module for the implementation of the +`GS1 -The Global Language of Business `_ standard. + +This module contains methods to generate and parse XML files compliant with this standard. + +Beside basic machinery it provides common components to generate common GS1 schema elements. +Namely: + + * `StandardBusinessDocumentHeader` diff --git a/edi_gs1/security/gs1_backend_acl.xml b/edi_gs1/security/gs1_backend_acl.xml new file mode 100644 index 000000000..bef3f0f91 --- /dev/null +++ b/edi_gs1/security/gs1_backend_acl.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/edi_gs1/static/description/icon.png b/edi_gs1/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 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 z 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 Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 diff --git a/edi_gs1/static/description/index.html b/edi_gs1/static/description/index.html new file mode 100644 index 000000000..0ad160f14 --- /dev/null +++ b/edi_gs1/static/description/index.html @@ -0,0 +1,434 @@ + + + + + + +Base GS1 EDI + + + +
+

Base GS1 EDI

+ + +

Alpha License: AGPL-3 OCA/edi Translate me on Weblate Try me on Runbot

+

This is the base module for the implementation of the +GS1 -The Global Language of Business standard.

+

This module contains methods to generate and parse XML files compliant with this standard.

+

Beside basic machinery it provides common components to generate common GS1 schema elements. +Namely:

+
+
    +
  • StandardBusinessDocumentHeader
  • +
+
+
+

Important

+

This is an alpha version, the data model and design can change at any time without warning. +Only for development or testing purpose, do not use in production. +More details on development status

+
+

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 smashing it by providing a detailed and welcomed +feedback.

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • ACSONE
  • +
+
+
+

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/edi project on GitHub.

+

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

+
+
+
+ + diff --git a/edi_gs1/static/schemas/gs1/ecom/ApplicationReceiptAcknowledgement.xsd b/edi_gs1/static/schemas/gs1/ecom/ApplicationReceiptAcknowledgement.xsd new file mode 100644 index 000000000..22c9524b7 --- /dev/null +++ b/edi_gs1/static/schemas/gs1/ecom/ApplicationReceiptAcknowledgement.xsd @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/edi_gs1/static/schemas/gs1/ecom/eComCommon.xsd b/edi_gs1/static/schemas/gs1/ecom/eComCommon.xsd new file mode 100644 index 000000000..4ae5569dc --- /dev/null +++ b/edi_gs1/static/schemas/gs1/ecom/eComCommon.xsd @@ -0,0 +1,1858 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/edi_gs1/static/schemas/gs1/shared/SharedCommon.xsd b/edi_gs1/static/schemas/gs1/shared/SharedCommon.xsd new file mode 100644 index 000000000..d51b853df --- /dev/null +++ b/edi_gs1/static/schemas/gs1/shared/SharedCommon.xsd @@ -0,0 +1,1506 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/edi_gs1/static/schemas/sbdh/BasicTypes.xsd b/edi_gs1/static/schemas/sbdh/BasicTypes.xsd new file mode 100644 index 000000000..3718acfe5 --- /dev/null +++ b/edi_gs1/static/schemas/sbdh/BasicTypes.xsd @@ -0,0 +1,20 @@ + + + + + + The MIME type as defined by IANA. Please refer to + http://www.iana.org/assignments/media-types/ for a list of types. + + + + + + + + ISO 639-2; 1998 representation of Language name. Refer to http://www.loc.gov/standards/iso639-2/iso639jac.html to get the latest version of the standard. + + + + + diff --git a/edi_gs1/static/schemas/sbdh/BusinessScope.xsd b/edi_gs1/static/schemas/sbdh/BusinessScope.xsd new file mode 100644 index 000000000..19ddd7ce0 --- /dev/null +++ b/edi_gs1/static/schemas/sbdh/BusinessScope.xsd @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/edi_gs1/static/schemas/sbdh/DocumentIdentification.xsd b/edi_gs1/static/schemas/sbdh/DocumentIdentification.xsd new file mode 100644 index 000000000..5264a2e27 --- /dev/null +++ b/edi_gs1/static/schemas/sbdh/DocumentIdentification.xsd @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/edi_gs1/static/schemas/sbdh/Manifest.xsd b/edi_gs1/static/schemas/sbdh/Manifest.xsd new file mode 100644 index 000000000..59bed11f1 --- /dev/null +++ b/edi_gs1/static/schemas/sbdh/Manifest.xsd @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/edi_gs1/static/schemas/sbdh/Partner.xsd b/edi_gs1/static/schemas/sbdh/Partner.xsd new file mode 100644 index 000000000..ff4258da6 --- /dev/null +++ b/edi_gs1/static/schemas/sbdh/Partner.xsd @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/edi_gs1/static/schemas/sbdh/StandardBusinessDocumentHeader.xsd b/edi_gs1/static/schemas/sbdh/StandardBusinessDocumentHeader.xsd new file mode 100644 index 000000000..b97fb45f4 --- /dev/null +++ b/edi_gs1/static/schemas/sbdh/StandardBusinessDocumentHeader.xsd @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/edi_gs1/tests/_OLD_common.py b/edi_gs1/tests/_OLD_common.py new file mode 100644 index 000000000..38547cb8e --- /dev/null +++ b/edi_gs1/tests/_OLD_common.py @@ -0,0 +1,69 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import os + +import xmlunittest + +from odoo.tests.common import SavepointCase + +from odoo.addons.component.tests.common import ComponentMixin + + +class BaseTestCase(SavepointCase, ComponentMixin): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) + cls.setUpComponent() + cls.backend = cls._get_backend() + # Logistic Services Provider (LSP) + cls.lsp_partner = cls.env.ref("base.res_partner_3") + # Logistic Services Client (LSC) + cls.lsc_partner = cls.env.ref("base.main_partner") + cls.backend.lsp_partner_id = cls.lsp_partner + + # set fake GLN codes + cls.lsp_partner.gln_code = "1".zfill(13) + cls.lsc_partner.gln_code = "2".zfill(13) + + @classmethod + def _get_backend(cls): + return cls.env.ref("base_gs1.default_gs1_backend") + + +class BaseXMLTestCase(BaseTestCase, xmlunittest.XmlTestMixin): + """Test XML files.""" + + def _dev_write_example_file(self, test_file, filename, content): + from pathlib import Path + + path = Path(test_file).parent / ("examples/test." + filename) + with open(path, "w") as out: + out.write(content) + + def flatten(self, txt): + return "".join([x.strip() for x in txt.splitlines()]) + + def read_test_file(self, filename): + path = os.path.join(os.path.dirname(__file__), "examples", filename) + with open(path, "r") as thefile: + return thefile.read() + + # @freeze_time("2018-08-10 17:10:00") + # def check_filename(self, name_template): + # """ + # Test the filename of the export, the name template can have a date + # parameter or a date and time parameter + # """ + # day = fields.Date.today().replace('-', '') + # time = fields.Datetime.now().split(' ')[1].replace(':', '') + # expected = name_template.format(day, time) + # with self.backend.work_on( + # self.model._name, timestamp=self.timestamp + # ) as work: + # writer = work.component(usage='local.xml.writer') + # self.assertEqual(writer.filename(), expected) + # writer = work.component(usage='sftp.xml.writer') + # self.assertEqual(writer.filename(), expected) diff --git a/edi_gs1/tests/__init__.py b/edi_gs1/tests/__init__.py new file mode 100644 index 000000000..a691b3870 --- /dev/null +++ b/edi_gs1/tests/__init__.py @@ -0,0 +1,2 @@ +from . import test_business_header +from . import test_application_receipt_ack diff --git a/edi_gs1/tests/_test_business_header_OLD.py b/edi_gs1/tests/_test_business_header_OLD.py new file mode 100644 index 000000000..15013e478 --- /dev/null +++ b/edi_gs1/tests/_test_business_header_OLD.py @@ -0,0 +1,127 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from freezegun import freeze_time + +from .common import BaseXMLTestCase + + +class BusinessHeaderTestCase(BaseXMLTestCase): + def _get_handler(self, usage="gs1.StandardBusinessDocumentHeader", **kw): + # FIXME: use a real record + record = object() + return self.backend._get_component( + work_ctx=dict( + record=record, + sender=self.lsc_partner, + receiver=self.lsp_partner, + instance_identifier="TEST-DOC-XYZ", + ), + usage=usage, + **kw + ) + + def test_header_version_data(self): + handler = self._get_handler() + result = handler._header_version() + expected = {"HeaderVersion": {"@ns": "sh", "@value": "1.0"}} + self.assertEqual(result, expected) + + def _expected_contact(self, key, record): + ctype = "Buyer" if key == "Sender" else "Seller" + return { + key: { + "@ns": "sh", + "Identifier": {"@ns": "sh", "@attrs": {"Authority": "GS1"}}, + "ContactInformation": { + "@ns": "sh", + "Contact": {"@ns": "sh", "@value": record.name}, + "EmailAddress": {"@ns": "sh", "@value": record.email}, + "TelephoneNumber": {"@ns": "sh", "@value": record.phone}, + "ContactTypeIdentifier": {"@ns": "sh", "@value": ctype}, + }, + }, + } + + def test_sender_data(self): + handler = self._get_handler() + result = handler._sender() + expected = self._expected_contact("Sender", self.lsc_partner) + self.assertEqual(result, expected) + + def test_receiver_data(self): + handler = self._get_handler() + result = handler._receiver() + expected = self._expected_contact("Receiver", self.lsp_partner) + self.assertEqual(result, expected) + + @freeze_time("2020-07-08 07:30:00") + def test_document_id_data(self): + handler = self._get_handler() + result = handler._document_identification() + expected = { + "DocumentIdentification": { + "@ns": "sh", + "Standard": {"@ns": "sh", "@value": "GS1"}, + "TypeVersion": {"@ns": "sh", "@value": "3.4"}, + "InstanceIdentifier": {"@ns": "sh", "@value": "TEST-DOC-XYZ"}, + "Type": {"@ns": "sh", "@value": ""}, + "MultipleType": {"@ns": "sh", "@value": "false"}, + "CreationDateAndTime": {"@ns": "sh", "@value": "2020-07-08T07:30:00"}, + } + } + self.assertEqual(result, expected) + + @freeze_time("2020-07-08 07:30:00") + def test_xml(self): + handler = self._get_handler() + result = handler.generate_xml() + # namespace is include in main document root + # but we need it to parse the result which would fail w/out this + result = result.replace( + "", + "".format( + handler._xmlns["sh"] + ), + ) + expected = """ + + 1.0 + + + + {sender.name} + {sender.email} + {sender.phone} + Buyer + + + + + + {receiver.name} + {receiver.email} + {receiver.phone} + Seller + + + + GS1 + 3.4 + {identifier} + + false + {date} + + + """.format( + ns=handler._xmlns["sh"], + sender=self.lsc_partner, + receiver=self.lsp_partner, + identifier="TEST-DOC-XYZ", + date="2020-07-08T07:30:00", + ) + self.assertXmlEquivalentOutputs(self.flatten(result), self.flatten(expected)) + # when valid returns none + self.assertFalse(handler.validate_schema(result)) diff --git a/edi_gs1/tests/common.py b/edi_gs1/tests/common.py new file mode 100644 index 000000000..428a0719c --- /dev/null +++ b/edi_gs1/tests/common.py @@ -0,0 +1,47 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import os + +import xmlunittest + +from odoo.tests.common import SavepointCase, tagged + + +@tagged("-at_install", "post_install") +class BaseTestCase(SavepointCase, xmlunittest.XmlTestMixin): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) + cls.backend = cls._get_backend() + # Logistic Services Provider (LSP) + cls.lsp_partner = cls.env.ref("base.res_partner_3") + # Logistic Services Client (LSC) + cls.lsc_partner = cls.env.ref("base.main_partner") + cls.backend.lsp_partner_id = cls.lsp_partner + cls.backend.lsc_partner_id = cls.lsc_partner + + # set fake GLN codes + cls.lsp_partner.gln_code = "1".zfill(13) + cls.lsc_partner.gln_code = "2".zfill(13) + + @classmethod + def _get_backend(cls): + return cls.env.ref("edi_gs1.default_gs1_backend") + + def _dev_write_example_file(self, test_file, filename, content): + from pathlib import Path + + path = Path(test_file).parent / ("examples/test." + filename) + with open(path, "w") as out: + out.write(content) + + def flatten(self, txt): + return "".join([x.strip() for x in txt.splitlines()]) + + def read_test_file(self, filename): + path = os.path.join(os.path.dirname(__file__), "examples", filename) + with open(path, "r") as thefile: + return thefile.read() diff --git a/edi_gs1/tests/examples/ApplicationReceiptAcknowledgement.example1.xml b/edi_gs1/tests/examples/ApplicationReceiptAcknowledgement.example1.xml new file mode 100644 index 000000000..8cd39ca3f --- /dev/null +++ b/edi_gs1/tests/examples/ApplicationReceiptAcknowledgement.example1.xml @@ -0,0 +1,65 @@ + + + + 1.0 + + + + John Doe + John_Doe@purchasing.XYZretailer.com + +1-212-555-1213 + +1-212-555-2122 + Buyer + + + + + + Mary Smith + Mary_Smith@widgets.com + +1-312-555-1214 + +1-312-555-2125 + Seller + + + + GS1 + 3.4 + 100002 + + false + 2006-01-10T12:00:01.000-05:00 + + + + 2011-03-11T11:43:00.000-05:00 + ORIGINAL + + RA0009 + + 4098765000010 + + + + ERROR + 2011-03-11T11:00:00.000-05:00 + 35 + + PO3352 + + 5412345000013 + + + + INCOMPLETE_MESSAGE + + + + diff --git a/edi_gs1/tests/test_application_receipt_ack.py b/edi_gs1/tests/test_application_receipt_ack.py new file mode 100644 index 000000000..ba9b3ff36 --- /dev/null +++ b/edi_gs1/tests/test_application_receipt_ack.py @@ -0,0 +1,20 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from .common import BaseTestCase + + +class ApplicationReceiptTestCase(BaseTestCase): + def _get_handler(self, usage="gs1.in.ApplicationReceiptAcknowledgement", **kw): + return self.backend._get_component(work_ctx=dict(), usage=usage, **kw) + + def test_parse(self): + fname = "ApplicationReceiptAcknowledgement.example1.xml" + file_content = self.read_test_file(fname) + handler = self._get_handler() + result = handler.parse_xml(file_content) + # expected = {} + # TODO: improve it + self.assertTrue(result["applicationReceiptAcknowledgement"]) + self.assertFalse(handler.is_ok(result)) diff --git a/edi_gs1/tests/test_business_header.py b/edi_gs1/tests/test_business_header.py new file mode 100644 index 000000000..5b0edb3c3 --- /dev/null +++ b/edi_gs1/tests/test_business_header.py @@ -0,0 +1,104 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from freezegun import freeze_time + +from .common import BaseTestCase + +BH_NS = "http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader" + + +class BusinessHeaderTestCase(BaseTestCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls._setup_records() + + @classmethod + def _setup_records(cls): + cls.bh_tmpl = cls.env.ref("edi_gs1.edi_exchange_template_business_header") + vals = { + "backend_id": cls.backend.id, + "backend_type_id": cls.backend.backend_type_id.id, + "name": "Template output 1", + "direction": "output", + "code": "test_type_out1", + "exchange_file_ext": "txt", + "exchange_filename_pattern": "{record.ref}-{type.code}-{dt}", + } + cls.exc_type = cls.env["edi.exchange.type"].create(vals) + cls.related_record = cls.env.ref("base.partner_demo") + vals = { + # Does not really matter which record we bind the exchange to + "model": cls.related_record._name, + "res_id": cls.related_record.id, + "type_id": cls.exc_type.id, + } + cls.exc_record = cls.backend.create_record("test_type_out1", vals) + + def test_template_render_values(self): + values = self.bh_tmpl._get_render_values(self.exc_record) + self.assertEqual( + values, + { + "backend": self.backend, + "date_to_string": self.bh_tmpl._date_to_string, + "exchange_record": self.exc_record, + "instance_identifier": self.related_record.name, + "record": self.related_record, + "template": self.bh_tmpl, + "utc_now": self.bh_tmpl._utc_now, + "render_edi_template": self.bh_tmpl._render_template, + }, + ) + + @freeze_time("2020-07-08 07:30:00") + def test_xml(self): + output = self.backend.generate_output( + self.exc_record, + template_code="gs1.BusinessHeader", + sender=self.lsc_partner, + receiver=self.lsp_partner, + ) + expected = """ + + 1.0 + + + + {sender.name} + {sender.email} + {sender.phone} + Buyer + + + + + + {receiver.name} + {receiver.email} + {receiver.phone} + Seller + + + + GS1 + 3.4 + {identifier} + + false + {date} + + + """.format( + ns=BH_NS, + sender=self.lsc_partner, + receiver=self.lsp_partner, + identifier=self.related_record.name, + date="2020-07-08T07:30:00", + ) + self.assertXmlEquivalentOutputs(self.flatten(output), self.flatten(expected)) + # when valid returns none + # TODO + # self.assertFalse(handler.validate_schema(result)) diff --git a/edi_gs1/xmler.py b/edi_gs1/xmler.py new file mode 100644 index 000000000..7c9ccfb47 --- /dev/null +++ b/edi_gs1/xmler.py @@ -0,0 +1,135 @@ +""" +Converts a Python dictionary to a valid XML string + +https://github.com/watzon/xmler Slightly modified and adapted for py3. + +TODO: project seems dead but it's very small and shall be easy to maintain. +Decide if we want to keep it here or contribute back. +""" + +from xml.dom import minidom +from xml.etree.ElementTree import Element, tostring + +__version__ = "0.2.0" +version = __version__ + + +def dict2xml(input_dict, encoding="utf-8", pretty=False): + """Converts a python dictionary into a valid XML string + + Args: + - encoding specifies the encoding to be included in the encoding + segment. If set to False no encoding segment will be displayed. + - customRoot defines the tag to wrap the returned output. Can be + a string, dictionary, or False if no custom root is to be used. + + Returns: + A XML formatted string representing the dictionary. + + Examples: + ``` + dic = { + "Envelope": { + "@ns": "soapenv", + "@attrs": { + "xmlns:soapenv": "http://schemas.xmlsoap.org/soap/envelope/", + "xmlns:urn": "urn:partner.soap.sforce.com" + }, + "Header": { + "@ns": "soapenv", + "SessionHeader": { + "@ns": "urn", + "sessionId": { + "@ns": "urn", + "@value": "00D36000000b28L!ARsAQMtHo4XD71VYRxoz" + } + } + }, + "Body": { + "@ns": "soapenv", + "query": { + "@ns": "urn", + "queryString": { + "@ns": "urn", + "@value": "SELECT Id, Name FROM Account LIMIT 2" + } + } + } + } + } + + xml = xml2dict(dict, customRoot=False) + print(xml) + ``` + + output: + ``` + + + + + {0} + + + + ``` + """ + + xml_string = tostring(parse(input_dict, pretty=pretty), encoding=encoding) + + if pretty: + xml_pretty_string = minidom.parseString(xml_string) + return xml_pretty_string.toprettyxml().decode(encoding) + else: + return xml_string.decode(encoding) + + +def parse(input_dict, parent=None, pretty=False): + parent = parent or {} + for key, value in input_dict.items(): + + if isinstance(value, (float, int)): + # Enfoce strings here + value = str(value) + + parent["name"] = key + parent["value"] = value + + if isinstance(value, dict): + if "@ns" in value: + parent["namespace"] = value.pop("@ns", None) + + if "@attrs" in value: + parent["attributes"] = value.pop("@attrs", None) + + if "@name" in value: + parent["name"] = value.pop("@name", None) + + if "@value" in value: + val = value["@value"] + if isinstance(val, (float, int)): + # Enfoce strings here + val = str(val) + parent["value"] = value = val + + if "namespace" in parent: + parent["name"] = "{}:{}".format(parent["namespace"], parent["name"]) + + if "attributes" in parent: + element = Element(parent["name"], parent["attributes"]) + else: + element = Element(parent["name"]) + + if isinstance(parent["value"], dict): + for child_key, child_value in parent["value"].items(): + element.append(parse({child_key: child_value}, parent={})) + + elif isinstance(parent["value"], (list, set, tuple)): + for child in parent["value"]: + element.append(parse(child, parent={})) + + else: + element.text = parent["value"] + + return element From ef695ee3fdea6bf28d16d8e532b2b3cc9dbdcd11 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Thu, 26 Nov 2020 08:45:52 +0100 Subject: [PATCH 02/63] edi_gs1: fix output tests --- .../data/business_header_output_template.xml | 2 +- edi_gs1/data/gs1_backend_data.xml | 11 ++++--- ... => _TODO_test_application_receipt_ack.py} | 0 edi_gs1/tests/__init__.py | 3 +- edi_gs1/tests/common.py | 8 +++-- edi_gs1/tests/test_business_header.py | 29 +++++++------------ 6 files changed, 24 insertions(+), 29 deletions(-) rename edi_gs1/tests/{test_application_receipt_ack.py => _TODO_test_application_receipt_ack.py} (100%) diff --git a/edi_gs1/data/business_header_output_template.xml b/edi_gs1/data/business_header_output_template.xml index 5a212615d..75939ce46 100644 --- a/edi_gs1/data/business_header_output_template.xml +++ b/edi_gs1/data/business_header_output_template.xml @@ -6,7 +6,7 @@ > EDI Exchange - GS1 Business Header gs1.BusinessHeader - + xml diff --git a/edi_gs1/data/gs1_backend_data.xml b/edi_gs1/data/gs1_backend_data.xml index 8d9bfb0c6..83f1a0b55 100644 --- a/edi_gs1/data/gs1_backend_data.xml +++ b/edi_gs1/data/gs1_backend_data.xml @@ -1,12 +1,11 @@ - - default_gs1_backend_type + + GS1 gs1 - - GS1 - - + + GS1 default + diff --git a/edi_gs1/tests/test_application_receipt_ack.py b/edi_gs1/tests/_TODO_test_application_receipt_ack.py similarity index 100% rename from edi_gs1/tests/test_application_receipt_ack.py rename to edi_gs1/tests/_TODO_test_application_receipt_ack.py diff --git a/edi_gs1/tests/__init__.py b/edi_gs1/tests/__init__.py index a691b3870..321fab57d 100644 --- a/edi_gs1/tests/__init__.py +++ b/edi_gs1/tests/__init__.py @@ -1,2 +1,3 @@ from . import test_business_header -from . import test_application_receipt_ack + +# from . import test_application_receipt_ack diff --git a/edi_gs1/tests/common.py b/edi_gs1/tests/common.py index 428a0719c..86c7022ab 100644 --- a/edi_gs1/tests/common.py +++ b/edi_gs1/tests/common.py @@ -6,11 +6,13 @@ import xmlunittest -from odoo.tests.common import SavepointCase, tagged +from odoo.tests.common import tagged + +from odoo.addons.component.tests.common import SavepointComponentCase @tagged("-at_install", "post_install") -class BaseTestCase(SavepointCase, xmlunittest.XmlTestMixin): +class BaseTestCase(SavepointComponentCase, xmlunittest.XmlTestMixin): @classmethod def setUpClass(cls): super().setUpClass() @@ -29,7 +31,7 @@ def setUpClass(cls): @classmethod def _get_backend(cls): - return cls.env.ref("edi_gs1.default_gs1_backend") + return cls.env.ref("edi_gs1.edi_backend_gs1_default") def _dev_write_example_file(self, test_file, filename, content): from pathlib import Path diff --git a/edi_gs1/tests/test_business_header.py b/edi_gs1/tests/test_business_header.py index 5b0edb3c3..534c42a89 100644 --- a/edi_gs1/tests/test_business_header.py +++ b/edi_gs1/tests/test_business_header.py @@ -39,27 +39,20 @@ def _setup_records(cls): def test_template_render_values(self): values = self.bh_tmpl._get_render_values(self.exc_record) - self.assertEqual( - values, - { - "backend": self.backend, - "date_to_string": self.bh_tmpl._date_to_string, - "exchange_record": self.exc_record, - "instance_identifier": self.related_record.name, - "record": self.related_record, - "template": self.bh_tmpl, - "utc_now": self.bh_tmpl._utc_now, - "render_edi_template": self.bh_tmpl._render_template, - }, - ) + expected = { + "backend": self.backend, + "exchange_record": self.exc_record, + "instance_identifier": self.related_record.name, + "record": self.related_record, + "template": self.bh_tmpl, + } + for k, v in expected.items(): + self.assertEqual(values[k], v) @freeze_time("2020-07-08 07:30:00") def test_xml(self): - output = self.backend.generate_output( - self.exc_record, - template_code="gs1.BusinessHeader", - sender=self.lsc_partner, - receiver=self.lsp_partner, + output = self.bh_tmpl.generate_output( + self.exc_record, sender=self.lsc_partner, receiver=self.lsp_partner, ) expected = """ From 39fc5329d58f2bda8c198b5cb1b2248b559419f0 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Wed, 2 Dec 2020 17:54:28 +0100 Subject: [PATCH 03/63] edi_gs1: BH instance identifier = record identifier --- edi_gs1/data/business_header_output_template.xml | 2 +- edi_gs1/tests/test_business_header.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/edi_gs1/data/business_header_output_template.xml b/edi_gs1/data/business_header_output_template.xml index 75939ce46..4ab29a20b 100644 --- a/edi_gs1/data/business_header_output_template.xml +++ b/edi_gs1/data/business_header_output_template.xml @@ -11,7 +11,7 @@ # Default identifier to record name -result = {"instance_identifier": record.name} +result = {"instance_identifier": exchange_record.identifier} diff --git a/edi_gs1/tests/test_business_header.py b/edi_gs1/tests/test_business_header.py index 534c42a89..e6d6bf9e9 100644 --- a/edi_gs1/tests/test_business_header.py +++ b/edi_gs1/tests/test_business_header.py @@ -42,7 +42,7 @@ def test_template_render_values(self): expected = { "backend": self.backend, "exchange_record": self.exc_record, - "instance_identifier": self.related_record.name, + "instance_identifier": self.exc_record.identifier, "record": self.related_record, "template": self.bh_tmpl, } @@ -88,7 +88,7 @@ def test_xml(self): ns=BH_NS, sender=self.lsc_partner, receiver=self.lsp_partner, - identifier=self.related_record.name, + identifier=self.exc_record.identifier, date="2020-07-08T07:30:00", ) self.assertXmlEquivalentOutputs(self.flatten(output), self.flatten(expected)) From 6c8b1b43cb03febf5ad9239b2f2988491f1e6819 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Wed, 2 Dec 2020 17:56:13 +0100 Subject: [PATCH 04/63] edi_gs1: add basic types for ack --- edi_gs1/__manifest__.py | 1 + edi_gs1/data/exchange_type_data.xml | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 edi_gs1/data/exchange_type_data.xml diff --git a/edi_gs1/__manifest__.py b/edi_gs1/__manifest__.py index d336f2f00..008162dc3 100644 --- a/edi_gs1/__manifest__.py +++ b/edi_gs1/__manifest__.py @@ -15,6 +15,7 @@ "data": [ "security/gs1_backend_acl.xml", "data/gs1_backend_data.xml", + "data/exchange_type_data.xml", "data/business_header_qweb_template.xml", "data/business_header_output_template.xml", ], diff --git a/edi_gs1/data/exchange_type_data.xml b/edi_gs1/data/exchange_type_data.xml new file mode 100644 index 000000000..a8ff5ec98 --- /dev/null +++ b/edi_gs1/data/exchange_type_data.xml @@ -0,0 +1,19 @@ + + + + + GS1 ApplicationReceiptAcknowledgement + applicationReceiptAcknowledgement_in + input + {record_name}-{type.code}-{dt} + xml + + + + GS1 ApplicationReceiptAcknowledgement + applicationReceiptAcknowledgement_out + output + {record_name}-{type.code}-{dt} + xml + + From e396af130f14b7320517703a82a971bc1e23fe76 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Wed, 2 Dec 2020 17:56:52 +0100 Subject: [PATCH 05/63] edi_gs1: add basic component for output --- edi_gs1/components/__init__.py | 2 +- edi_gs1/components/output_mixin.py | 40 ++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 edi_gs1/components/output_mixin.py diff --git a/edi_gs1/components/__init__.py b/edi_gs1/components/__init__.py index 2fa9d7532..14ce72c88 100644 --- a/edi_gs1/components/__init__.py +++ b/edi_gs1/components/__init__.py @@ -1 +1 @@ -# from . import application_receipt_ack +from . import output_mixin diff --git a/edi_gs1/components/output_mixin.py b/edi_gs1/components/output_mixin.py new file mode 100644 index 000000000..f03910f44 --- /dev/null +++ b/edi_gs1/components/output_mixin.py @@ -0,0 +1,40 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.tools import DotDict + +from odoo.addons.component.core import AbstractComponent + + +class GS1OutputMessageMixin(AbstractComponent): + """Common gs1 output mixin. + """ + + _name = "edi.gs1.output.mixin" + _inherit = [ + "edi.output.mixin", + ] + _work_context_validate_attrs = [ + "record", + "sender", + "receiver", + ] + + def generate_info(self): + return DotDict(self._generate_info()) + + def _generate_info(self): + raise NotImplementedError("You must provide `_generate_info`") + + @property + def replace_existing(self): + """Pass `replace_existing` to work context to change this value.""" + return getattr(self.work, "replace_existing", False) + + def _document_action_code(self): + # Brand new file + code = "ADD" + if self.replace_existing: + code = "CHANGE_BY_REFRESH" + return code From 8275866379e1ec0db131cdd1b9579444543f76f0 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Wed, 2 Dec 2020 18:06:18 +0100 Subject: [PATCH 06/63] edi_gs1: add schemas for warehouse exchanges --- .../schemas/gs1/ecom/WarehousingCommon.xsd | 280 ++++++++++++++++++ .../ecom/WarehousingInboundInstruction.xsd | 77 +++++ .../ecom/WarehousingInboundNotification.xsd | 86 ++++++ .../ecom/WarehousingOutboundInstruction.xsd | 87 ++++++ .../ecom/WarehousingOutboundNotification.xsd | 96 ++++++ 5 files changed, 626 insertions(+) create mode 100644 edi_gs1/static/schemas/gs1/ecom/WarehousingCommon.xsd create mode 100644 edi_gs1/static/schemas/gs1/ecom/WarehousingInboundInstruction.xsd create mode 100644 edi_gs1/static/schemas/gs1/ecom/WarehousingInboundNotification.xsd create mode 100644 edi_gs1/static/schemas/gs1/ecom/WarehousingOutboundInstruction.xsd create mode 100644 edi_gs1/static/schemas/gs1/ecom/WarehousingOutboundNotification.xsd diff --git a/edi_gs1/static/schemas/gs1/ecom/WarehousingCommon.xsd b/edi_gs1/static/schemas/gs1/ecom/WarehousingCommon.xsd new file mode 100644 index 000000000..2b2add5fb --- /dev/null +++ b/edi_gs1/static/schemas/gs1/ecom/WarehousingCommon.xsd @@ -0,0 +1,280 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/edi_gs1/static/schemas/gs1/ecom/WarehousingInboundInstruction.xsd b/edi_gs1/static/schemas/gs1/ecom/WarehousingInboundInstruction.xsd new file mode 100644 index 000000000..eb4f90aaf --- /dev/null +++ b/edi_gs1/static/schemas/gs1/ecom/WarehousingInboundInstruction.xsd @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/edi_gs1/static/schemas/gs1/ecom/WarehousingInboundNotification.xsd b/edi_gs1/static/schemas/gs1/ecom/WarehousingInboundNotification.xsd new file mode 100644 index 000000000..ad3811cc6 --- /dev/null +++ b/edi_gs1/static/schemas/gs1/ecom/WarehousingInboundNotification.xsd @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/edi_gs1/static/schemas/gs1/ecom/WarehousingOutboundInstruction.xsd b/edi_gs1/static/schemas/gs1/ecom/WarehousingOutboundInstruction.xsd new file mode 100644 index 000000000..a03f46cb7 --- /dev/null +++ b/edi_gs1/static/schemas/gs1/ecom/WarehousingOutboundInstruction.xsd @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/edi_gs1/static/schemas/gs1/ecom/WarehousingOutboundNotification.xsd b/edi_gs1/static/schemas/gs1/ecom/WarehousingOutboundNotification.xsd new file mode 100644 index 000000000..d52717da7 --- /dev/null +++ b/edi_gs1/static/schemas/gs1/ecom/WarehousingOutboundNotification.xsd @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 9c606cad52aa6fe019e6bd80b6ee982b396ead06 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Wed, 2 Dec 2020 23:06:24 +0100 Subject: [PATCH 07/63] edi_gs1: clean dead code --- edi_gs1/tests/_OLD_common.py | 69 ---------- .../_TODO_test_application_receipt_ack.py | 20 --- edi_gs1/tests/_test_business_header_OLD.py | 127 ------------------ 3 files changed, 216 deletions(-) delete mode 100644 edi_gs1/tests/_OLD_common.py delete mode 100644 edi_gs1/tests/_TODO_test_application_receipt_ack.py delete mode 100644 edi_gs1/tests/_test_business_header_OLD.py diff --git a/edi_gs1/tests/_OLD_common.py b/edi_gs1/tests/_OLD_common.py deleted file mode 100644 index 38547cb8e..000000000 --- a/edi_gs1/tests/_OLD_common.py +++ /dev/null @@ -1,69 +0,0 @@ -# Copyright 2020 ACSONE -# @author: Simone Orsi -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -import os - -import xmlunittest - -from odoo.tests.common import SavepointCase - -from odoo.addons.component.tests.common import ComponentMixin - - -class BaseTestCase(SavepointCase, ComponentMixin): - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) - cls.setUpComponent() - cls.backend = cls._get_backend() - # Logistic Services Provider (LSP) - cls.lsp_partner = cls.env.ref("base.res_partner_3") - # Logistic Services Client (LSC) - cls.lsc_partner = cls.env.ref("base.main_partner") - cls.backend.lsp_partner_id = cls.lsp_partner - - # set fake GLN codes - cls.lsp_partner.gln_code = "1".zfill(13) - cls.lsc_partner.gln_code = "2".zfill(13) - - @classmethod - def _get_backend(cls): - return cls.env.ref("base_gs1.default_gs1_backend") - - -class BaseXMLTestCase(BaseTestCase, xmlunittest.XmlTestMixin): - """Test XML files.""" - - def _dev_write_example_file(self, test_file, filename, content): - from pathlib import Path - - path = Path(test_file).parent / ("examples/test." + filename) - with open(path, "w") as out: - out.write(content) - - def flatten(self, txt): - return "".join([x.strip() for x in txt.splitlines()]) - - def read_test_file(self, filename): - path = os.path.join(os.path.dirname(__file__), "examples", filename) - with open(path, "r") as thefile: - return thefile.read() - - # @freeze_time("2018-08-10 17:10:00") - # def check_filename(self, name_template): - # """ - # Test the filename of the export, the name template can have a date - # parameter or a date and time parameter - # """ - # day = fields.Date.today().replace('-', '') - # time = fields.Datetime.now().split(' ')[1].replace(':', '') - # expected = name_template.format(day, time) - # with self.backend.work_on( - # self.model._name, timestamp=self.timestamp - # ) as work: - # writer = work.component(usage='local.xml.writer') - # self.assertEqual(writer.filename(), expected) - # writer = work.component(usage='sftp.xml.writer') - # self.assertEqual(writer.filename(), expected) diff --git a/edi_gs1/tests/_TODO_test_application_receipt_ack.py b/edi_gs1/tests/_TODO_test_application_receipt_ack.py deleted file mode 100644 index ba9b3ff36..000000000 --- a/edi_gs1/tests/_TODO_test_application_receipt_ack.py +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2020 ACSONE -# @author: Simone Orsi -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from .common import BaseTestCase - - -class ApplicationReceiptTestCase(BaseTestCase): - def _get_handler(self, usage="gs1.in.ApplicationReceiptAcknowledgement", **kw): - return self.backend._get_component(work_ctx=dict(), usage=usage, **kw) - - def test_parse(self): - fname = "ApplicationReceiptAcknowledgement.example1.xml" - file_content = self.read_test_file(fname) - handler = self._get_handler() - result = handler.parse_xml(file_content) - # expected = {} - # TODO: improve it - self.assertTrue(result["applicationReceiptAcknowledgement"]) - self.assertFalse(handler.is_ok(result)) diff --git a/edi_gs1/tests/_test_business_header_OLD.py b/edi_gs1/tests/_test_business_header_OLD.py deleted file mode 100644 index 15013e478..000000000 --- a/edi_gs1/tests/_test_business_header_OLD.py +++ /dev/null @@ -1,127 +0,0 @@ -# Copyright 2020 ACSONE -# @author: Simone Orsi -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from freezegun import freeze_time - -from .common import BaseXMLTestCase - - -class BusinessHeaderTestCase(BaseXMLTestCase): - def _get_handler(self, usage="gs1.StandardBusinessDocumentHeader", **kw): - # FIXME: use a real record - record = object() - return self.backend._get_component( - work_ctx=dict( - record=record, - sender=self.lsc_partner, - receiver=self.lsp_partner, - instance_identifier="TEST-DOC-XYZ", - ), - usage=usage, - **kw - ) - - def test_header_version_data(self): - handler = self._get_handler() - result = handler._header_version() - expected = {"HeaderVersion": {"@ns": "sh", "@value": "1.0"}} - self.assertEqual(result, expected) - - def _expected_contact(self, key, record): - ctype = "Buyer" if key == "Sender" else "Seller" - return { - key: { - "@ns": "sh", - "Identifier": {"@ns": "sh", "@attrs": {"Authority": "GS1"}}, - "ContactInformation": { - "@ns": "sh", - "Contact": {"@ns": "sh", "@value": record.name}, - "EmailAddress": {"@ns": "sh", "@value": record.email}, - "TelephoneNumber": {"@ns": "sh", "@value": record.phone}, - "ContactTypeIdentifier": {"@ns": "sh", "@value": ctype}, - }, - }, - } - - def test_sender_data(self): - handler = self._get_handler() - result = handler._sender() - expected = self._expected_contact("Sender", self.lsc_partner) - self.assertEqual(result, expected) - - def test_receiver_data(self): - handler = self._get_handler() - result = handler._receiver() - expected = self._expected_contact("Receiver", self.lsp_partner) - self.assertEqual(result, expected) - - @freeze_time("2020-07-08 07:30:00") - def test_document_id_data(self): - handler = self._get_handler() - result = handler._document_identification() - expected = { - "DocumentIdentification": { - "@ns": "sh", - "Standard": {"@ns": "sh", "@value": "GS1"}, - "TypeVersion": {"@ns": "sh", "@value": "3.4"}, - "InstanceIdentifier": {"@ns": "sh", "@value": "TEST-DOC-XYZ"}, - "Type": {"@ns": "sh", "@value": ""}, - "MultipleType": {"@ns": "sh", "@value": "false"}, - "CreationDateAndTime": {"@ns": "sh", "@value": "2020-07-08T07:30:00"}, - } - } - self.assertEqual(result, expected) - - @freeze_time("2020-07-08 07:30:00") - def test_xml(self): - handler = self._get_handler() - result = handler.generate_xml() - # namespace is include in main document root - # but we need it to parse the result which would fail w/out this - result = result.replace( - "", - "".format( - handler._xmlns["sh"] - ), - ) - expected = """ - - 1.0 - - - - {sender.name} - {sender.email} - {sender.phone} - Buyer - - - - - - {receiver.name} - {receiver.email} - {receiver.phone} - Seller - - - - GS1 - 3.4 - {identifier} - - false - {date} - - - """.format( - ns=handler._xmlns["sh"], - sender=self.lsc_partner, - receiver=self.lsp_partner, - identifier="TEST-DOC-XYZ", - date="2020-07-08T07:30:00", - ) - self.assertXmlEquivalentOutputs(self.flatten(result), self.flatten(expected)) - # when valid returns none - self.assertFalse(handler.validate_schema(result)) From 9a2e52cdec927bcbf9b0a5612f70c86288d45ca2 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Thu, 3 Dec 2020 11:08:28 +0100 Subject: [PATCH 08/63] edi_gs1: depend on edi_xml --- edi_gs1/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edi_gs1/__manifest__.py b/edi_gs1/__manifest__.py index 008162dc3..b83cfc168 100644 --- a/edi_gs1/__manifest__.py +++ b/edi_gs1/__manifest__.py @@ -10,7 +10,7 @@ "development_status": "Alpha", "license": "AGPL-3", "author": "ACSONE,Odoo Community Association (OCA)", - "depends": ["component", "edi_exchange_template"], + "depends": ["component", "edi_exchange_template", "edi_xml"], "external_dependencies": {"python": ["xmlschema"]}, "data": [ "security/gs1_backend_acl.xml", From ce96622d00f9fcc84d983e83fc0746982032a957 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Thu, 3 Dec 2020 19:01:55 +0100 Subject: [PATCH 09/63] edi_gs1: component has only exc record in ctx --- edi_gs1/components/output_mixin.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/edi_gs1/components/output_mixin.py b/edi_gs1/components/output_mixin.py index f03910f44..9572b6c00 100644 --- a/edi_gs1/components/output_mixin.py +++ b/edi_gs1/components/output_mixin.py @@ -16,11 +16,15 @@ class GS1OutputMessageMixin(AbstractComponent): "edi.output.mixin", ] _work_context_validate_attrs = [ - "record", + "exchange_record", "sender", "receiver", ] + @property + def record(self): + return self.work.exchange_record.record + def generate_info(self): return DotDict(self._generate_info()) From 706d19b2b9719a1403e7ccdc9901d91061f0445c Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Fri, 4 Dec 2020 08:07:42 +0100 Subject: [PATCH 10/63] edi_gs1: SBH tests validate xml --- edi_gs1/tests/common.py | 10 ++++++++++ edi_gs1/tests/test_business_header.py | 8 +++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/edi_gs1/tests/common.py b/edi_gs1/tests/common.py index 86c7022ab..d0fa2e574 100644 --- a/edi_gs1/tests/common.py +++ b/edi_gs1/tests/common.py @@ -13,6 +13,9 @@ @tagged("-at_install", "post_install") class BaseTestCase(SavepointComponentCase, xmlunittest.XmlTestMixin): + + _schema_path = "edi_gs1:static/schemas/sbdh/StandardBusinessDocumentHeader.xsd" + @classmethod def setUpClass(cls): super().setUpClass() @@ -47,3 +50,10 @@ def read_test_file(self, filename): path = os.path.join(os.path.dirname(__file__), "examples", filename) with open(path, "r") as thefile: return thefile.read() + + _schema_path = "" + + def _get_xml_handler(self): + return self.backend._find_component( + ["edi.xml"], work_ctx={"schema_path": self._schema_path}, safe=False, + ) diff --git a/edi_gs1/tests/test_business_header.py b/edi_gs1/tests/test_business_header.py index e6d6bf9e9..0896be8e6 100644 --- a/edi_gs1/tests/test_business_header.py +++ b/edi_gs1/tests/test_business_header.py @@ -10,6 +10,9 @@ class BusinessHeaderTestCase(BaseTestCase): + + _schema_path = "edi_gs1:static/schemas/sbdh/StandardBusinessDocumentHeader.xsd" + @classmethod def setUpClass(cls): super().setUpClass() @@ -92,6 +95,5 @@ def test_xml(self): date="2020-07-08T07:30:00", ) self.assertXmlEquivalentOutputs(self.flatten(output), self.flatten(expected)) - # when valid returns none - # TODO - # self.assertFalse(handler.validate_schema(result)) + handler = self._get_xml_handler() + self.assertEqual(handler.validate(output), None) From d01bb2d52d3798c0443badd732c7c0670e0cc98c Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Fri, 4 Dec 2020 09:20:56 +0100 Subject: [PATCH 11/63] edi: draft gs1 input mixin --- edi_gs1/components/__init__.py | 1 + edi_gs1/components/input_mixin.py | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 edi_gs1/components/input_mixin.py diff --git a/edi_gs1/components/__init__.py b/edi_gs1/components/__init__.py index 14ce72c88..b4bd9cc66 100644 --- a/edi_gs1/components/__init__.py +++ b/edi_gs1/components/__init__.py @@ -1 +1,2 @@ from . import output_mixin +from . import input_mixin diff --git a/edi_gs1/components/input_mixin.py b/edi_gs1/components/input_mixin.py new file mode 100644 index 000000000..13108ccda --- /dev/null +++ b/edi_gs1/components/input_mixin.py @@ -0,0 +1,24 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.addons.component.core import AbstractComponent + + +class GS1InputMessageMixin(AbstractComponent): + """Common gs1 input mixin. + """ + + _name = "edi.gs1.input.mixin" + _inherit = ["edi.component.input.mixin"] + + def process(self): + handler = self.backend._find_component( + ["edi.xml"], work_ctx={"schema_path": self.work.schema_path}, safe=False + ) + data = handler.parse_xml(self.exchage_record._get_file_content()) + self._process_data(data) + return True + + def _process_data(self, data): + NotImplementedError() From 27dafe8dd3df30fd0bb35162857d0a7ebc7a468a Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Thu, 11 Mar 2021 08:07:34 +0100 Subject: [PATCH 12/63] DROPME: add TODOs --- edi_gs1/data/exchange_type_data.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/edi_gs1/data/exchange_type_data.xml b/edi_gs1/data/exchange_type_data.xml index a8ff5ec98..5e2c3d316 100644 --- a/edi_gs1/data/exchange_type_data.xml +++ b/edi_gs1/data/exchange_type_data.xml @@ -7,6 +7,7 @@ input {record_name}-{type.code}-{dt} xml + From 75ba63aaea6a67ab35efc20ce86de9808f1cc3c7 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Mon, 15 Mar 2021 10:01:21 +0100 Subject: [PATCH 13/63] edi_gs1: adapt tests to core changes --- edi_gs1/tests/common.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/edi_gs1/tests/common.py b/edi_gs1/tests/common.py index d0fa2e574..c3df549bb 100644 --- a/edi_gs1/tests/common.py +++ b/edi_gs1/tests/common.py @@ -55,5 +55,8 @@ def read_test_file(self, filename): def _get_xml_handler(self): return self.backend._find_component( - ["edi.xml"], work_ctx={"schema_path": self._schema_path}, safe=False, + self.backend._name, + ["edi.xml"], + work_ctx={"schema_path": self._schema_path}, + safe=False, ) From 1d0298581dac51f7d66142d6cf655109f784e32a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Honor=C3=A9?= Date: Wed, 17 Mar 2021 11:39:55 +0100 Subject: [PATCH 14/63] [ADD] edi_gs1: add gs1_code on UoM (+ auto init based on default values) + add a contact details qweb template --- edi_gs1/__init__.py | 1 + edi_gs1/__manifest__.py | 5 +- .../data/contact_details_qweb_template.xml | 44 ++++++++++++ edi_gs1/hooks.py | 14 ++++ edi_gs1/models/__init__.py | 1 + edi_gs1/models/uom_uom.py | 70 +++++++++++++++++++ edi_gs1/tests/common.py | 3 + edi_gs1/views/uom_uom.xml | 16 +++++ 8 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 edi_gs1/data/contact_details_qweb_template.xml create mode 100644 edi_gs1/hooks.py create mode 100644 edi_gs1/models/uom_uom.py create mode 100644 edi_gs1/views/uom_uom.xml diff --git a/edi_gs1/__init__.py b/edi_gs1/__init__.py index 0f00a6730..d20ee91b9 100644 --- a/edi_gs1/__init__.py +++ b/edi_gs1/__init__.py @@ -1,2 +1,3 @@ from . import models from . import components +from .hooks import post_init_hook diff --git a/edi_gs1/__manifest__.py b/edi_gs1/__manifest__.py index b83cfc168..478e8ff81 100644 --- a/edi_gs1/__manifest__.py +++ b/edi_gs1/__manifest__.py @@ -10,7 +10,7 @@ "development_status": "Alpha", "license": "AGPL-3", "author": "ACSONE,Odoo Community Association (OCA)", - "depends": ["component", "edi_exchange_template", "edi_xml"], + "depends": ["component", "edi_exchange_template", "edi_xml", "uom"], "external_dependencies": {"python": ["xmlschema"]}, "data": [ "security/gs1_backend_acl.xml", @@ -18,5 +18,8 @@ "data/exchange_type_data.xml", "data/business_header_qweb_template.xml", "data/business_header_output_template.xml", + "data/contact_details_qweb_template.xml", + "views/uom_uom.xml", ], + "post_init_hook": "post_init_hook", } diff --git a/edi_gs1/data/contact_details_qweb_template.xml b/edi_gs1/data/contact_details_qweb_template.xml new file mode 100644 index 000000000..8281408a9 --- /dev/null +++ b/edi_gs1/data/contact_details_qweb_template.xml @@ -0,0 +1,44 @@ + + + + diff --git a/edi_gs1/hooks.py b/edi_gs1/hooks.py new file mode 100644 index 000000000..b36b3f8b9 --- /dev/null +++ b/edi_gs1/hooks.py @@ -0,0 +1,14 @@ +# Copyright 2021 ACSONE SA/NV () +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from odoo.api import SUPERUSER_ID, Environment + + +def post_init_hook(cr, registry): + """ + Hook used to init automatically some GS1 code on existing UoM + :param cr: database cursor + :param registry: + :return: + """ + env = Environment(cr, SUPERUSER_ID, {"tracking_disable": True}) + env["uom.uom"]._execute_gs1_map_code() diff --git a/edi_gs1/models/__init__.py b/edi_gs1/models/__init__.py index fe5182755..bf8796723 100644 --- a/edi_gs1/models/__init__.py +++ b/edi_gs1/models/__init__.py @@ -1,2 +1,3 @@ from . import edi_backend from . import res_partner +from . import uom_uom diff --git a/edi_gs1/models/uom_uom.py b/edi_gs1/models/uom_uom.py new file mode 100644 index 000000000..acc375296 --- /dev/null +++ b/edi_gs1/models/uom_uom.py @@ -0,0 +1,70 @@ +# Copyright 2020 ACSONE SA/NV () +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from odoo import api, fields, models + + +class UomUom(models.Model): + _inherit = "uom.uom" + + gs1_code = fields.Char( + string="GS1 code", help="Code used to export with GS1 format" + ) + + @api.model + def _gs1_mapping_code(self): + """ + Get a dict with the mapping between the UoM code (from Odoo) and the GS1 code. + This mapping table is used into the init_hook of the module to automatically + init existing UoM. + key should be always in lower case! + Codes come from this url: + https://resources.gs1us.org/GS1-US-Data-Hub-Help-Center/ArtMID/3451/ + ArticleID/116/Unit-of-Measure-Codes + :return: dict + """ + map_code = { + "kg": "KGM", + "day": "DAY", + "days": "DAY", + "box": "BX", + "boxes": "BX", + "dozen": "DZN", + "dozens": "DZN", + "hours": "HUR", + "hour": "HUR", + "liters": "LTR", + "liter": "LTR", + "cm": "CMT", + "fl oz": "OZI", + "foot(ft)": "FOT", + "foot": "FOT", + "g": "GRM", + "gal": "GLI", + "gals": "GLI", + "inches": "INH", + "km": "KMT", + "m": "MTR", + } + return map_code + + @api.model + def _execute_gs1_map_code(self, overwrite_existing=False): + """ + Execute (or update) the gs1 mapping based on UoM name (lower case). + By default, it doesn't update UoM with a value into gs1_code. + But it can be forced by setting the overwrite_existing parameter to True. + :param overwrite_existing: bool + :return: bool + """ + domain = [("gs1_code", "=", False)] + if overwrite_existing: + domain = [] + uoms = self.env["uom.uom"].search(domain) + map_code = uoms._gs1_mapping_code() + for uom in uoms: + # Key always in lower case so the name too! + name = uom.name.lower() + gs1_code = map_code.get(name) + if gs1_code and uom.gs1_code != gs1_code: + uom.write({"gs1_code": gs1_code}) + return True diff --git a/edi_gs1/tests/common.py b/edi_gs1/tests/common.py index c3df549bb..cfa605f7c 100644 --- a/edi_gs1/tests/common.py +++ b/edi_gs1/tests/common.py @@ -31,6 +31,9 @@ def setUpClass(cls): # set fake GLN codes cls.lsp_partner.gln_code = "1".zfill(13) cls.lsc_partner.gln_code = "2".zfill(13) + # We have to trigger this gs1_code update manually in case of a submodule + # update them. + cls.env['uom.uom']._execute_gs1_map_code() @classmethod def _get_backend(cls): diff --git a/edi_gs1/views/uom_uom.xml b/edi_gs1/views/uom_uom.xml new file mode 100644 index 000000000..f6ef358ad --- /dev/null +++ b/edi_gs1/views/uom_uom.xml @@ -0,0 +1,16 @@ + + + + + uom.uom.form (in edi_gs1) + uom.uom + + + + + + + + + From 26873373c6b55a3c6270e2373758f9d454f146e6 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Mon, 15 Mar 2021 10:06:54 +0100 Subject: [PATCH 15/63] edi_exchange_template: rework info provider mixins --- edi_gs1/components/output_mixin.py | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/edi_gs1/components/output_mixin.py b/edi_gs1/components/output_mixin.py index 9572b6c00..cd724d0e8 100644 --- a/edi_gs1/components/output_mixin.py +++ b/edi_gs1/components/output_mixin.py @@ -2,7 +2,6 @@ # @author: Simone Orsi # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo.tools import DotDict from odoo.addons.component.core import AbstractComponent @@ -12,30 +11,13 @@ class GS1OutputMessageMixin(AbstractComponent): """ _name = "edi.gs1.output.mixin" - _inherit = [ - "edi.output.mixin", - ] + _inherit = ["edi.output.info.mixin"] _work_context_validate_attrs = [ "exchange_record", "sender", "receiver", ] - @property - def record(self): - return self.work.exchange_record.record - - def generate_info(self): - return DotDict(self._generate_info()) - - def _generate_info(self): - raise NotImplementedError("You must provide `_generate_info`") - - @property - def replace_existing(self): - """Pass `replace_existing` to work context to change this value.""" - return getattr(self.work, "replace_existing", False) - def _document_action_code(self): # Brand new file code = "ADD" From e88928678926ef7256cb9886a320397efe3099e4 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Thu, 18 Mar 2021 10:44:23 +0100 Subject: [PATCH 16/63] edi_gs1: add gs1_code to UoM GS1 standard requires UoM specific mapping. Here we add a specific field for it and provide mapping of values as well as auto compute for existing UoM. --- edi_gs1/tests/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edi_gs1/tests/common.py b/edi_gs1/tests/common.py index cfa605f7c..77690dfbf 100644 --- a/edi_gs1/tests/common.py +++ b/edi_gs1/tests/common.py @@ -33,7 +33,7 @@ def setUpClass(cls): cls.lsc_partner.gln_code = "2".zfill(13) # We have to trigger this gs1_code update manually in case of a submodule # update them. - cls.env['uom.uom']._execute_gs1_map_code() + cls.env["uom.uom"]._execute_gs1_map_code() @classmethod def _get_backend(cls): From 040c8d4c08a067785f99ecaf6e2289105074f550 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Thu, 18 Mar 2021 10:46:48 +0100 Subject: [PATCH 17/63] edi_gs1_stock: improve outbound instruction * Add qweb tmpl for contact details * Fix fake res.partner record for shipper --- .../data/contact_details_qweb_template.xml | 39 ++++++++++--------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/edi_gs1/data/contact_details_qweb_template.xml b/edi_gs1/data/contact_details_qweb_template.xml index 8281408a9..8cbe00ef1 100644 --- a/edi_gs1/data/contact_details_qweb_template.xml +++ b/edi_gs1/data/contact_details_qweb_template.xml @@ -9,35 +9,38 @@ t-esc="additionalPartyIdentification.value" />
- - - - - - - + + + + + + +
- + EMAIL - + TELEPHONE - + - + - - - - - - - + + + + + + + From 69dd9531f8f5aede32a2fde90dde94e3a0817da2 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Thu, 18 Mar 2021 11:51:19 +0100 Subject: [PATCH 18/63] edi_gs1: split ack data type + imp conf --- edi_gs1/__manifest__.py | 3 ++- edi_gs1/data/ack_in_exchange_type_data.xml | 27 +++++++++++++++++++++ edi_gs1/data/ack_out_exchange_type_data.xml | 20 +++++++++++++++ edi_gs1/data/exchange_type_data.xml | 20 --------------- 4 files changed, 49 insertions(+), 21 deletions(-) create mode 100644 edi_gs1/data/ack_in_exchange_type_data.xml create mode 100644 edi_gs1/data/ack_out_exchange_type_data.xml delete mode 100644 edi_gs1/data/exchange_type_data.xml diff --git a/edi_gs1/__manifest__.py b/edi_gs1/__manifest__.py index 478e8ff81..a1a34c5b8 100644 --- a/edi_gs1/__manifest__.py +++ b/edi_gs1/__manifest__.py @@ -15,7 +15,8 @@ "data": [ "security/gs1_backend_acl.xml", "data/gs1_backend_data.xml", - "data/exchange_type_data.xml", + "data/ack_in_exchange_type_data.xml", + "data/ack_out_exchange_type_data.xml", "data/business_header_qweb_template.xml", "data/business_header_output_template.xml", "data/contact_details_qweb_template.xml", diff --git a/edi_gs1/data/ack_in_exchange_type_data.xml b/edi_gs1/data/ack_in_exchange_type_data.xml new file mode 100644 index 000000000..d458c4f1d --- /dev/null +++ b/edi_gs1/data/ack_in_exchange_type_data.xml @@ -0,0 +1,27 @@ + + + + + GS1 ApplicationReceiptAcknowledgement + applicationReceiptAcknowledgement_in + input + + + xml + + components: + validate: + usage: edi.xml + work_ctx: + schema_path: edi_gs1:static/schemas/gs1/ecom/ApplicationReceiptAcknowledgement.xsd + process: + usage: gs1.in.ApplicationReceiptAcknowledgement + work_ctx: + schema_path: edi_gs1:static/schemas/gs1/ecom/ApplicationReceiptAcknowledgement.xsd + + + diff --git a/edi_gs1/data/ack_out_exchange_type_data.xml b/edi_gs1/data/ack_out_exchange_type_data.xml new file mode 100644 index 000000000..a22a5a276 --- /dev/null +++ b/edi_gs1/data/ack_out_exchange_type_data.xml @@ -0,0 +1,20 @@ + + + + + GS1 ApplicationReceiptAcknowledgement + applicationReceiptAcknowledgement_out + output + {record_name}-{type.code}-{dt} + xml + + + + components: + validate: + usage: edi.xml + work_ctx: + schema_path: edi_gs1:static/schemas/gs1/ecom/ApplicationReceiptAcknowledgement.xsd + + + diff --git a/edi_gs1/data/exchange_type_data.xml b/edi_gs1/data/exchange_type_data.xml deleted file mode 100644 index 5e2c3d316..000000000 --- a/edi_gs1/data/exchange_type_data.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - GS1 ApplicationReceiptAcknowledgement - applicationReceiptAcknowledgement_in - input - {record_name}-{type.code}-{dt} - xml - - - - - GS1 ApplicationReceiptAcknowledgement - applicationReceiptAcknowledgement_out - output - {record_name}-{type.code}-{dt} - xml - - From 635c5461a353a7c1abe7d92b1930b63bd736c53e Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Thu, 18 Mar 2021 11:54:16 +0100 Subject: [PATCH 19/63] edi_gs1: draft ack input process component --- edi_gs1/components/__init__.py | 1 + edi_gs1/components/application_receipt_ack.py | 20 +++++++++++++------ edi_gs1/components/input_mixin.py | 4 +++- edi_gs1/data/ack_in_exchange_type_data.xml | 2 -- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/edi_gs1/components/__init__.py b/edi_gs1/components/__init__.py index b4bd9cc66..ed79a5d61 100644 --- a/edi_gs1/components/__init__.py +++ b/edi_gs1/components/__init__.py @@ -1,2 +1,3 @@ from . import output_mixin from . import input_mixin +from . import application_receipt_ack diff --git a/edi_gs1/components/application_receipt_ack.py b/edi_gs1/components/application_receipt_ack.py index ad04c5eee..f97d68c4d 100644 --- a/edi_gs1/components/application_receipt_ack.py +++ b/edi_gs1/components/application_receipt_ack.py @@ -12,19 +12,27 @@ class ApplicationReceiptAcknowledgement(Component): """ _name = "gs1.input.applicationReceiptAcknowledgement" - _inherit = "gs1.input.mixin" + _inherit = "edi.gs1.input.mixin" _usage = "gs1.in.ApplicationReceiptAcknowledgement" _xsd_schema_path = "static/schemas/gs1/ecom/ApplicationReceiptAcknowledgement.xsd" - def get_status(self, result): + def _process_data(self, data): + if not self.is_ok(data): + # TODO: give a proper message based on the real state + # options: ERROR, RECEIVED, WARNING + raise exceptions.ValidationError(_("Exchange not received")) + + def is_ok(self, data): + return self._get_status(data) == "RECEIVED" + + def _get_status(self, data): try: - ack = result["applicationReceiptAcknowledgement"][0] + ack = data["applicationReceiptAcknowledgement"][0] except IndexError: + # Maybe raise an EDIValidationError? raise exceptions.ValidationError( _("applicationReceiptAcknowledgement element not found!") ) status_wrapper = ack["applicationResponseDocumentLevel"][0] + # options: ERROR, RECEIVED, WARNING return status_wrapper["applicationResponseStatusCode"] - - def is_ok(self, result): - return self.get_status(result) == "RECEIVED" diff --git a/edi_gs1/components/input_mixin.py b/edi_gs1/components/input_mixin.py index 13108ccda..c840dc7c9 100644 --- a/edi_gs1/components/input_mixin.py +++ b/edi_gs1/components/input_mixin.py @@ -11,10 +11,12 @@ class GS1InputMessageMixin(AbstractComponent): _name = "edi.gs1.input.mixin" _inherit = ["edi.component.input.mixin"] + _xsd_schema_path = None def process(self): + schema_path = self._xsd_schema_path or self.work.schema_path handler = self.backend._find_component( - ["edi.xml"], work_ctx={"schema_path": self.work.schema_path}, safe=False + ["edi.xml"], work_ctx={"schema_path": schema_path}, safe=False ) data = handler.parse_xml(self.exchage_record._get_file_content()) self._process_data(data) diff --git a/edi_gs1/data/ack_in_exchange_type_data.xml b/edi_gs1/data/ack_in_exchange_type_data.xml index d458c4f1d..572c905af 100644 --- a/edi_gs1/data/ack_in_exchange_type_data.xml +++ b/edi_gs1/data/ack_in_exchange_type_data.xml @@ -20,8 +20,6 @@ schema_path: edi_gs1:static/schemas/gs1/ecom/ApplicationReceiptAcknowledgement.xsd process: usage: gs1.in.ApplicationReceiptAcknowledgement - work_ctx: - schema_path: edi_gs1:static/schemas/gs1/ecom/ApplicationReceiptAcknowledgement.xsd
From bb78bb45a91e2e90e14acbebaa2fe9485eac0665 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Honor=C3=A9?= Date: Wed, 31 Mar 2021 10:32:22 +0200 Subject: [PATCH 20/63] [ADD] edi_gs1: lsp and lsc partners on backend --- edi_gs1/__manifest__.py | 2 ++ edi_gs1/models/edi_backend.py | 1 - edi_gs1/views/edi_backend.xml | 19 +++++++++++++++++++ edi_gs1/views/res_partner.xml | 24 ++++++++++++++++++++++++ 4 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 edi_gs1/views/edi_backend.xml create mode 100644 edi_gs1/views/res_partner.xml diff --git a/edi_gs1/__manifest__.py b/edi_gs1/__manifest__.py index a1a34c5b8..b9ee275b8 100644 --- a/edi_gs1/__manifest__.py +++ b/edi_gs1/__manifest__.py @@ -20,6 +20,8 @@ "data/business_header_qweb_template.xml", "data/business_header_output_template.xml", "data/contact_details_qweb_template.xml", + "views/res_partner.xml", + "views/edi_backend.xml", "views/uom_uom.xml", ], "post_init_hook": "post_init_hook", diff --git a/edi_gs1/models/edi_backend.py b/edi_gs1/models/edi_backend.py index 2dd74200d..a642e07e7 100644 --- a/edi_gs1/models/edi_backend.py +++ b/edi_gs1/models/edi_backend.py @@ -9,7 +9,6 @@ class EDIBackend(models.Model): _inherit = "edi.backend" # TODO: find another way to configure this w/out polluting edi backend - # TODO: add to view lsp_partner_id = fields.Many2one( string="Logistic Services Provider (LSP)", comodel_name="res.partner", diff --git a/edi_gs1/views/edi_backend.xml b/edi_gs1/views/edi_backend.xml new file mode 100644 index 000000000..b988efeae --- /dev/null +++ b/edi_gs1/views/edi_backend.xml @@ -0,0 +1,19 @@ + + + + + edi.backend.form (in edi_gs1) + edi.backend + + + + + + + + + + + + diff --git a/edi_gs1/views/res_partner.xml b/edi_gs1/views/res_partner.xml new file mode 100644 index 000000000..5ba50adfc --- /dev/null +++ b/edi_gs1/views/res_partner.xml @@ -0,0 +1,24 @@ + + + + + res.partner.form (in edi_gs1) + res.partner + + + + + + + + + + + + + + + + + From 688bf51921d881d0a8eda114ca7c9d2b30450e0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Honor=C3=A9?= Date: Wed, 31 Mar 2021 10:37:53 +0200 Subject: [PATCH 21/63] [FIX] edi_gs1: empty doc_type invalidate the document generation --- edi_gs1/data/business_header_qweb_template.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edi_gs1/data/business_header_qweb_template.xml b/edi_gs1/data/business_header_qweb_template.xml index 2ab74ea3b..033eaad66 100644 --- a/edi_gs1/data/business_header_qweb_template.xml +++ b/edi_gs1/data/business_header_qweb_template.xml @@ -53,7 +53,7 @@ 100002 - + false Date: Wed, 31 Mar 2021 10:44:22 +0200 Subject: [PATCH 22/63] [UPD] edi_gs1: update group on security --- edi_gs1/security/gs1_backend_acl.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/edi_gs1/security/gs1_backend_acl.xml b/edi_gs1/security/gs1_backend_acl.xml index bef3f0f91..eda64df05 100644 --- a/edi_gs1/security/gs1_backend_acl.xml +++ b/edi_gs1/security/gs1_backend_acl.xml @@ -5,6 +5,7 @@ - + xml components: diff --git a/edi_gs1/views/edi_backend.xml b/edi_gs1/views/edi_backend.xml index b988efeae..c3f91b523 100644 --- a/edi_gs1/views/edi_backend.xml +++ b/edi_gs1/views/edi_backend.xml @@ -1,17 +1,17 @@ - + edi.backend.form (in edi_gs1) edi.backend - - + + - - + + diff --git a/edi_gs1/views/uom_uom.xml b/edi_gs1/views/uom_uom.xml index f6ef358ad..f37265ae1 100644 --- a/edi_gs1/views/uom_uom.xml +++ b/edi_gs1/views/uom_uom.xml @@ -1,15 +1,15 @@ - + uom.uom.form (in edi_gs1) uom.uom - - + + - + From 04c4a3b0973155201a09fd50b34b542de12ea2e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Honor=C3=A9?= Date: Fri, 16 Apr 2021 11:45:24 +0200 Subject: [PATCH 24/63] [FIX] Fix unit test about output generation + missing security rules --- edi_gs1/tests/test_business_header.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/edi_gs1/tests/test_business_header.py b/edi_gs1/tests/test_business_header.py index 0896be8e6..40c5309ef 100644 --- a/edi_gs1/tests/test_business_header.py +++ b/edi_gs1/tests/test_business_header.py @@ -54,8 +54,8 @@ def test_template_render_values(self): @freeze_time("2020-07-08 07:30:00") def test_xml(self): - output = self.bh_tmpl.generate_output( - self.exc_record, sender=self.lsc_partner, receiver=self.lsp_partner, + output = self.bh_tmpl.exchange_generate( + self.exc_record, sender=self.lsc_partner, receiver=self.lsp_partner ) expected = """ From 63e7e61fbfdf11b5ada792b24a5718051163446f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Honor=C3=A9?= Date: Fri, 2 Jul 2021 14:07:07 +0200 Subject: [PATCH 25/63] [UPD] GS1 .xsd files update --- edi_gs1/static/schemas/gs1/ecom/WarehousingCommon.xsd | 6 +++--- .../schemas/gs1/ecom/WarehousingInboundInstruction.xsd | 6 +++--- .../schemas/gs1/ecom/WarehousingInboundNotification.xsd | 6 +++--- .../schemas/gs1/ecom/WarehousingOutboundInstruction.xsd | 6 +++--- .../schemas/gs1/ecom/WarehousingOutboundNotification.xsd | 6 +++--- edi_gs1/static/schemas/gs1/ecom/eComCommon.xsd | 6 +++--- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/edi_gs1/static/schemas/gs1/ecom/WarehousingCommon.xsd b/edi_gs1/static/schemas/gs1/ecom/WarehousingCommon.xsd index 2b2add5fb..3395efe4d 100644 --- a/edi_gs1/static/schemas/gs1/ecom/WarehousingCommon.xsd +++ b/edi_gs1/static/schemas/gs1/ecom/WarehousingCommon.xsd @@ -1,5 +1,5 @@ - + - + - + - + - + - + Date: Fri, 2 Jul 2021 14:08:45 +0200 Subject: [PATCH 26/63] [FIX] edi_gs1: fix typo and find_component. --- edi_gs1/__manifest__.py | 2 +- edi_gs1/components/input_mixin.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/edi_gs1/__manifest__.py b/edi_gs1/__manifest__.py index f0f134032..91bbbaa43 100644 --- a/edi_gs1/__manifest__.py +++ b/edi_gs1/__manifest__.py @@ -12,7 +12,7 @@ "website": "https://github.com/OCA/edi", "author": "ACSONE,Odoo Community Association (OCA)", "depends": ["component", "edi_exchange_template", "edi_xml", "uom"], - "external_dependencies": {"python": ["xmlschema"]}, + "external_dependencies": {"python": ["xmlschema", "xmlunittest"]}, "data": [ "security/gs1_backend_acl.xml", "data/gs1_backend_data.xml", diff --git a/edi_gs1/components/input_mixin.py b/edi_gs1/components/input_mixin.py index c840dc7c9..6ee3444aa 100644 --- a/edi_gs1/components/input_mixin.py +++ b/edi_gs1/components/input_mixin.py @@ -16,9 +16,12 @@ class GS1InputMessageMixin(AbstractComponent): def process(self): schema_path = self._xsd_schema_path or self.work.schema_path handler = self.backend._find_component( - ["edi.xml"], work_ctx={"schema_path": schema_path}, safe=False + self.backend._name, + ["edi.xml"], + work_ctx={"schema_path": schema_path}, + safe=False, ) - data = handler.parse_xml(self.exchage_record._get_file_content()) + data = handler.parse_xml(self.exchange_record._get_file_content()) self._process_data(data) return True From de487627d55f210e773a09594d45289fef9df2fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Honor=C3=A9?= Date: Fri, 9 Jul 2021 15:03:24 +0200 Subject: [PATCH 27/63] [FIX] edi: fix due to refactor --- edi_gs1/views/edi_backend.xml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/edi_gs1/views/edi_backend.xml b/edi_gs1/views/edi_backend.xml index c3f91b523..33aacd56a 100644 --- a/edi_gs1/views/edi_backend.xml +++ b/edi_gs1/views/edi_backend.xml @@ -8,12 +8,16 @@ - - - - - - + + + + + + + + + + From c82f10b2560cd760344d01ea0a9aaef6592c6231 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Honor=C3=A9?= Date: Thu, 24 Nov 2022 17:31:01 +0100 Subject: [PATCH 28/63] [GS1] Generate the Warehouse Outbound Notification ACK --- edi_gs1/components/__init__.py | 1 + edi_gs1/components/application_send_ack.py | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 edi_gs1/components/application_send_ack.py diff --git a/edi_gs1/components/__init__.py b/edi_gs1/components/__init__.py index ed79a5d61..9ccb8dd7d 100644 --- a/edi_gs1/components/__init__.py +++ b/edi_gs1/components/__init__.py @@ -1,3 +1,4 @@ from . import output_mixin from . import input_mixin from . import application_receipt_ack +from . import application_send_ack diff --git a/edi_gs1/components/application_send_ack.py b/edi_gs1/components/application_send_ack.py new file mode 100644 index 000000000..e8fa69696 --- /dev/null +++ b/edi_gs1/components/application_send_ack.py @@ -0,0 +1,16 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + + +from odoo.addons.component.core import Component + + +class ApplicationSendAcknowledgement(Component): + """Generate GS1 applicationSendAcknowledgement. + """ + + _name = "gs1.output.applicationSendAcknowledgement" + _inherit = "edi.gs1.output.mixin" + _usage = "gs1.out.ApplicationSendAcknowledgement" + _xsd_schema_path = "static/schemas/gs1/ecom/ApplicationReceiptAcknowledgement.xsd" From 935d5a9da79df800a03504030d799473819c579c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Honor=C3=A9?= Date: Thu, 15 Dec 2022 16:07:19 +0100 Subject: [PATCH 29/63] [FIX] edi_gs1: remove useless _xsd_schema_path --- edi_gs1/components/application_receipt_ack.py | 1 - 1 file changed, 1 deletion(-) diff --git a/edi_gs1/components/application_receipt_ack.py b/edi_gs1/components/application_receipt_ack.py index f97d68c4d..e95a0fbb3 100644 --- a/edi_gs1/components/application_receipt_ack.py +++ b/edi_gs1/components/application_receipt_ack.py @@ -14,7 +14,6 @@ class ApplicationReceiptAcknowledgement(Component): _name = "gs1.input.applicationReceiptAcknowledgement" _inherit = "edi.gs1.input.mixin" _usage = "gs1.in.ApplicationReceiptAcknowledgement" - _xsd_schema_path = "static/schemas/gs1/ecom/ApplicationReceiptAcknowledgement.xsd" def _process_data(self, data): if not self.is_ok(data): From 8e1d4dbbc6f91efb932a76c2d91728b6e95b9478 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Honor=C3=A9?= Date: Fri, 19 Jun 2026 15:08:05 +0200 Subject: [PATCH 30/63] [IMP] edi_gs1_oca: pre-commit auto fixes --- edi_gs1/readme/CONTRIBUTORS.rst | 1 - edi_gs1/readme/DESCRIPTION.rst | 9 --- {edi_gs1 => edi_gs1_oca}/README.rst | 56 ++++++++------ {edi_gs1 => edi_gs1_oca}/TODO | 0 {edi_gs1 => edi_gs1_oca}/__init__.py | 0 {edi_gs1 => edi_gs1_oca}/__manifest__.py | 2 +- .../components/__init__.py | 0 .../components/application_receipt_ack.py | 3 +- .../components/application_send_ack.py | 3 +- .../components/input_mixin.py | 3 +- .../components/output_mixin.py | 3 +- .../data/ack_in_exchange_type_data.xml | 0 .../data/ack_out_exchange_type_data.xml | 0 .../data/business_header_output_template.xml | 0 .../data/business_header_qweb_template.xml | 0 .../data/contact_details_qweb_template.xml | 0 .../data/gs1_backend_data.xml | 0 {edi_gs1 => edi_gs1_oca}/hooks.py | 0 {edi_gs1 => edi_gs1_oca}/models/__init__.py | 0 .../models/edi_backend.py | 0 .../models/res_partner.py | 0 {edi_gs1 => edi_gs1_oca}/models/uom_uom.py | 0 edi_gs1_oca/pyproject.toml | 3 + edi_gs1_oca/readme/CONTRIBUTORS.md | 1 + edi_gs1_oca/readme/DESCRIPTION.md | 10 +++ .../security/gs1_backend_acl.xml | 0 .../static/description/icon.png | Bin .../static/description/index.html | 73 ++++++++++-------- .../ApplicationReceiptAcknowledgement.xsd | 0 .../schemas/gs1/ecom/WarehousingCommon.xsd | 0 .../ecom/WarehousingInboundInstruction.xsd | 0 .../ecom/WarehousingInboundNotification.xsd | 0 .../ecom/WarehousingOutboundInstruction.xsd | 0 .../ecom/WarehousingOutboundNotification.xsd | 0 .../static/schemas/gs1/ecom/eComCommon.xsd | 0 .../schemas/gs1/shared/SharedCommon.xsd | 0 .../static/schemas/sbdh/BasicTypes.xsd | 0 .../static/schemas/sbdh/BusinessScope.xsd | 0 .../schemas/sbdh/DocumentIdentification.xsd | 0 .../static/schemas/sbdh/Manifest.xsd | 0 .../static/schemas/sbdh/Partner.xsd | 0 .../sbdh/StandardBusinessDocumentHeader.xsd | 0 {edi_gs1 => edi_gs1_oca}/tests/__init__.py | 0 {edi_gs1 => edi_gs1_oca}/tests/common.py | 3 +- ...icationReceiptAcknowledgement.example1.xml | 0 .../tests/test_business_header.py | 1 - .../views/edi_backend.xml | 0 .../views/res_partner.xml | 0 {edi_gs1 => edi_gs1_oca}/views/uom_uom.xml | 0 {edi_gs1 => edi_gs1_oca}/xmler.py | 1 - requirements.txt | 2 + 51 files changed, 96 insertions(+), 78 deletions(-) delete mode 100644 edi_gs1/readme/CONTRIBUTORS.rst delete mode 100644 edi_gs1/readme/DESCRIPTION.rst rename {edi_gs1 => edi_gs1_oca}/README.rst (53%) rename {edi_gs1 => edi_gs1_oca}/TODO (100%) rename {edi_gs1 => edi_gs1_oca}/__init__.py (100%) rename {edi_gs1 => edi_gs1_oca}/__manifest__.py (94%) rename {edi_gs1 => edi_gs1_oca}/components/__init__.py (100%) rename {edi_gs1 => edi_gs1_oca}/components/application_receipt_ack.py (95%) rename {edi_gs1 => edi_gs1_oca}/components/application_send_ack.py (88%) rename {edi_gs1 => edi_gs1_oca}/components/input_mixin.py (95%) rename {edi_gs1 => edi_gs1_oca}/components/output_mixin.py (93%) rename {edi_gs1 => edi_gs1_oca}/data/ack_in_exchange_type_data.xml (100%) rename {edi_gs1 => edi_gs1_oca}/data/ack_out_exchange_type_data.xml (100%) rename {edi_gs1 => edi_gs1_oca}/data/business_header_output_template.xml (100%) rename {edi_gs1 => edi_gs1_oca}/data/business_header_qweb_template.xml (100%) rename {edi_gs1 => edi_gs1_oca}/data/contact_details_qweb_template.xml (100%) rename {edi_gs1 => edi_gs1_oca}/data/gs1_backend_data.xml (100%) rename {edi_gs1 => edi_gs1_oca}/hooks.py (100%) rename {edi_gs1 => edi_gs1_oca}/models/__init__.py (100%) rename {edi_gs1 => edi_gs1_oca}/models/edi_backend.py (100%) rename {edi_gs1 => edi_gs1_oca}/models/res_partner.py (100%) rename {edi_gs1 => edi_gs1_oca}/models/uom_uom.py (100%) create mode 100644 edi_gs1_oca/pyproject.toml create mode 100644 edi_gs1_oca/readme/CONTRIBUTORS.md create mode 100644 edi_gs1_oca/readme/DESCRIPTION.md rename {edi_gs1 => edi_gs1_oca}/security/gs1_backend_acl.xml (100%) rename {edi_gs1 => edi_gs1_oca}/static/description/icon.png (100%) rename {edi_gs1 => edi_gs1_oca}/static/description/index.html (72%) rename {edi_gs1 => edi_gs1_oca}/static/schemas/gs1/ecom/ApplicationReceiptAcknowledgement.xsd (100%) rename {edi_gs1 => edi_gs1_oca}/static/schemas/gs1/ecom/WarehousingCommon.xsd (100%) rename {edi_gs1 => edi_gs1_oca}/static/schemas/gs1/ecom/WarehousingInboundInstruction.xsd (100%) rename {edi_gs1 => edi_gs1_oca}/static/schemas/gs1/ecom/WarehousingInboundNotification.xsd (100%) rename {edi_gs1 => edi_gs1_oca}/static/schemas/gs1/ecom/WarehousingOutboundInstruction.xsd (100%) rename {edi_gs1 => edi_gs1_oca}/static/schemas/gs1/ecom/WarehousingOutboundNotification.xsd (100%) rename {edi_gs1 => edi_gs1_oca}/static/schemas/gs1/ecom/eComCommon.xsd (100%) rename {edi_gs1 => edi_gs1_oca}/static/schemas/gs1/shared/SharedCommon.xsd (100%) rename {edi_gs1 => edi_gs1_oca}/static/schemas/sbdh/BasicTypes.xsd (100%) rename {edi_gs1 => edi_gs1_oca}/static/schemas/sbdh/BusinessScope.xsd (100%) rename {edi_gs1 => edi_gs1_oca}/static/schemas/sbdh/DocumentIdentification.xsd (100%) rename {edi_gs1 => edi_gs1_oca}/static/schemas/sbdh/Manifest.xsd (100%) rename {edi_gs1 => edi_gs1_oca}/static/schemas/sbdh/Partner.xsd (100%) rename {edi_gs1 => edi_gs1_oca}/static/schemas/sbdh/StandardBusinessDocumentHeader.xsd (100%) rename {edi_gs1 => edi_gs1_oca}/tests/__init__.py (100%) rename {edi_gs1 => edi_gs1_oca}/tests/common.py (98%) rename {edi_gs1 => edi_gs1_oca}/tests/examples/ApplicationReceiptAcknowledgement.example1.xml (100%) rename {edi_gs1 => edi_gs1_oca}/tests/test_business_header.py (99%) rename {edi_gs1 => edi_gs1_oca}/views/edi_backend.xml (100%) rename {edi_gs1 => edi_gs1_oca}/views/res_partner.xml (100%) rename {edi_gs1 => edi_gs1_oca}/views/uom_uom.xml (100%) rename {edi_gs1 => edi_gs1_oca}/xmler.py (99%) diff --git a/edi_gs1/readme/CONTRIBUTORS.rst b/edi_gs1/readme/CONTRIBUTORS.rst deleted file mode 100644 index f583948be..000000000 --- a/edi_gs1/readme/CONTRIBUTORS.rst +++ /dev/null @@ -1 +0,0 @@ -* Simone Orsi diff --git a/edi_gs1/readme/DESCRIPTION.rst b/edi_gs1/readme/DESCRIPTION.rst deleted file mode 100644 index 491e592cf..000000000 --- a/edi_gs1/readme/DESCRIPTION.rst +++ /dev/null @@ -1,9 +0,0 @@ -This is the base module for the implementation of the -`GS1 -The Global Language of Business `_ standard. - -This module contains methods to generate and parse XML files compliant with this standard. - -Beside basic machinery it provides common components to generate common GS1 schema elements. -Namely: - - * `StandardBusinessDocumentHeader` diff --git a/edi_gs1/README.rst b/edi_gs1_oca/README.rst similarity index 53% rename from edi_gs1/README.rst rename to edi_gs1_oca/README.rst index 14f2fee15..75a3541b5 100644 --- a/edi_gs1/README.rst +++ b/edi_gs1_oca/README.rst @@ -1,39 +1,47 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + ============ Base GS1 EDI ============ -.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:a5ab15a8b1dea2fd4da5955af650a8db9296c1a96cb53d4d7d98188d059a14da + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png :target: https://odoo-community.org/page/development-status :alt: Alpha -.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png +.. |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/13.0/edi_gs1 - :alt: OCA/edi +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fedi--framework-lightgray.png?logo=github + :target: https://github.com/OCA/edi-framework/tree/19.0/edi_gs1_oca + :alt: OCA/edi-framework .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/edi-13-0/edi-13-0-edi_gs1 + :target: https://translation.odoo-community.org/projects/edi-framework-19-0/edi-framework-19-0-edi_gs1_oca :alt: Translate me on Weblate -.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png - :target: https://runbot.odoo-community.org/runbot/226/13.0 - :alt: Try me on Runbot +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/edi-framework&target_branch=19.0 + :alt: Try me on Runboat -|badge1| |badge2| |badge3| |badge4| |badge5| +|badge1| |badge2| |badge3| |badge4| |badge5| -This is the base module for the implementation of the -`GS1 -The Global Language of Business `_ standard. +This is the base module for the implementation of the `GS1 -The Global +Language of Business `__ standard. -This module contains methods to generate and parse XML files compliant with this standard. +This module contains methods to generate and parse XML files compliant +with this standard. -Beside basic machinery it provides common components to generate common GS1 schema elements. -Namely: +Beside basic machinery it provides common components to generate common +GS1 schema elements. Namely: - * `StandardBusinessDocumentHeader` + - StandardBusinessDocumentHeader .. IMPORTANT:: This is an alpha version, the data model and design can change at any time without warning. @@ -48,10 +56,10 @@ Namely: Bug Tracker =========== -Bugs are tracked on `GitHub Issues `_. +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 smashing it by providing a detailed and welcomed -`feedback `_. +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. @@ -59,17 +67,17 @@ Credits ======= Authors -~~~~~~~ +------- * ACSONE Contributors -~~~~~~~~~~~~ +------------ -* Simone Orsi +- Simone Orsi Maintainers -~~~~~~~~~~~ +----------- This module is maintained by the OCA. @@ -81,6 +89,6 @@ 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/edi `_ project on GitHub. +This module is part of the `OCA/edi-framework `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/edi_gs1/TODO b/edi_gs1_oca/TODO similarity index 100% rename from edi_gs1/TODO rename to edi_gs1_oca/TODO diff --git a/edi_gs1/__init__.py b/edi_gs1_oca/__init__.py similarity index 100% rename from edi_gs1/__init__.py rename to edi_gs1_oca/__init__.py diff --git a/edi_gs1/__manifest__.py b/edi_gs1_oca/__manifest__.py similarity index 94% rename from edi_gs1/__manifest__.py rename to edi_gs1_oca/__manifest__.py index 91bbbaa43..dcf600e9b 100644 --- a/edi_gs1/__manifest__.py +++ b/edi_gs1_oca/__manifest__.py @@ -9,7 +9,7 @@ "version": "13.0.1.0.0", "development_status": "Alpha", "license": "AGPL-3", - "website": "https://github.com/OCA/edi", + "website": "https://github.com/OCA/edi-framework", "author": "ACSONE,Odoo Community Association (OCA)", "depends": ["component", "edi_exchange_template", "edi_xml", "uom"], "external_dependencies": {"python": ["xmlschema", "xmlunittest"]}, diff --git a/edi_gs1/components/__init__.py b/edi_gs1_oca/components/__init__.py similarity index 100% rename from edi_gs1/components/__init__.py rename to edi_gs1_oca/components/__init__.py diff --git a/edi_gs1/components/application_receipt_ack.py b/edi_gs1_oca/components/application_receipt_ack.py similarity index 95% rename from edi_gs1/components/application_receipt_ack.py rename to edi_gs1_oca/components/application_receipt_ack.py index e95a0fbb3..f9033e023 100644 --- a/edi_gs1/components/application_receipt_ack.py +++ b/edi_gs1_oca/components/application_receipt_ack.py @@ -8,8 +8,7 @@ class ApplicationReceiptAcknowledgement(Component): - """Parse GS1 applicationReceiptAcknowledgement. - """ + """Parse GS1 applicationReceiptAcknowledgement.""" _name = "gs1.input.applicationReceiptAcknowledgement" _inherit = "edi.gs1.input.mixin" diff --git a/edi_gs1/components/application_send_ack.py b/edi_gs1_oca/components/application_send_ack.py similarity index 88% rename from edi_gs1/components/application_send_ack.py rename to edi_gs1_oca/components/application_send_ack.py index e8fa69696..b1df370d3 100644 --- a/edi_gs1/components/application_send_ack.py +++ b/edi_gs1_oca/components/application_send_ack.py @@ -7,8 +7,7 @@ class ApplicationSendAcknowledgement(Component): - """Generate GS1 applicationSendAcknowledgement. - """ + """Generate GS1 applicationSendAcknowledgement.""" _name = "gs1.output.applicationSendAcknowledgement" _inherit = "edi.gs1.output.mixin" diff --git a/edi_gs1/components/input_mixin.py b/edi_gs1_oca/components/input_mixin.py similarity index 95% rename from edi_gs1/components/input_mixin.py rename to edi_gs1_oca/components/input_mixin.py index 6ee3444aa..b09db3a03 100644 --- a/edi_gs1/components/input_mixin.py +++ b/edi_gs1_oca/components/input_mixin.py @@ -6,8 +6,7 @@ class GS1InputMessageMixin(AbstractComponent): - """Common gs1 input mixin. - """ + """Common gs1 input mixin.""" _name = "edi.gs1.input.mixin" _inherit = ["edi.component.input.mixin"] diff --git a/edi_gs1/components/output_mixin.py b/edi_gs1_oca/components/output_mixin.py similarity index 93% rename from edi_gs1/components/output_mixin.py rename to edi_gs1_oca/components/output_mixin.py index cd724d0e8..1f0895217 100644 --- a/edi_gs1/components/output_mixin.py +++ b/edi_gs1_oca/components/output_mixin.py @@ -7,8 +7,7 @@ class GS1OutputMessageMixin(AbstractComponent): - """Common gs1 output mixin. - """ + """Common gs1 output mixin.""" _name = "edi.gs1.output.mixin" _inherit = ["edi.output.info.mixin"] diff --git a/edi_gs1/data/ack_in_exchange_type_data.xml b/edi_gs1_oca/data/ack_in_exchange_type_data.xml similarity index 100% rename from edi_gs1/data/ack_in_exchange_type_data.xml rename to edi_gs1_oca/data/ack_in_exchange_type_data.xml diff --git a/edi_gs1/data/ack_out_exchange_type_data.xml b/edi_gs1_oca/data/ack_out_exchange_type_data.xml similarity index 100% rename from edi_gs1/data/ack_out_exchange_type_data.xml rename to edi_gs1_oca/data/ack_out_exchange_type_data.xml diff --git a/edi_gs1/data/business_header_output_template.xml b/edi_gs1_oca/data/business_header_output_template.xml similarity index 100% rename from edi_gs1/data/business_header_output_template.xml rename to edi_gs1_oca/data/business_header_output_template.xml diff --git a/edi_gs1/data/business_header_qweb_template.xml b/edi_gs1_oca/data/business_header_qweb_template.xml similarity index 100% rename from edi_gs1/data/business_header_qweb_template.xml rename to edi_gs1_oca/data/business_header_qweb_template.xml diff --git a/edi_gs1/data/contact_details_qweb_template.xml b/edi_gs1_oca/data/contact_details_qweb_template.xml similarity index 100% rename from edi_gs1/data/contact_details_qweb_template.xml rename to edi_gs1_oca/data/contact_details_qweb_template.xml diff --git a/edi_gs1/data/gs1_backend_data.xml b/edi_gs1_oca/data/gs1_backend_data.xml similarity index 100% rename from edi_gs1/data/gs1_backend_data.xml rename to edi_gs1_oca/data/gs1_backend_data.xml diff --git a/edi_gs1/hooks.py b/edi_gs1_oca/hooks.py similarity index 100% rename from edi_gs1/hooks.py rename to edi_gs1_oca/hooks.py diff --git a/edi_gs1/models/__init__.py b/edi_gs1_oca/models/__init__.py similarity index 100% rename from edi_gs1/models/__init__.py rename to edi_gs1_oca/models/__init__.py diff --git a/edi_gs1/models/edi_backend.py b/edi_gs1_oca/models/edi_backend.py similarity index 100% rename from edi_gs1/models/edi_backend.py rename to edi_gs1_oca/models/edi_backend.py diff --git a/edi_gs1/models/res_partner.py b/edi_gs1_oca/models/res_partner.py similarity index 100% rename from edi_gs1/models/res_partner.py rename to edi_gs1_oca/models/res_partner.py diff --git a/edi_gs1/models/uom_uom.py b/edi_gs1_oca/models/uom_uom.py similarity index 100% rename from edi_gs1/models/uom_uom.py rename to edi_gs1_oca/models/uom_uom.py diff --git a/edi_gs1_oca/pyproject.toml b/edi_gs1_oca/pyproject.toml new file mode 100644 index 000000000..4231d0ccc --- /dev/null +++ b/edi_gs1_oca/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/edi_gs1_oca/readme/CONTRIBUTORS.md b/edi_gs1_oca/readme/CONTRIBUTORS.md new file mode 100644 index 000000000..5792655b0 --- /dev/null +++ b/edi_gs1_oca/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +- Simone Orsi \<\> diff --git a/edi_gs1_oca/readme/DESCRIPTION.md b/edi_gs1_oca/readme/DESCRIPTION.md new file mode 100644 index 000000000..7f5b2ba48 --- /dev/null +++ b/edi_gs1_oca/readme/DESCRIPTION.md @@ -0,0 +1,10 @@ +This is the base module for the implementation of the [GS1 -The Global +Language of Business](http://gs1.org/) standard. + +This module contains methods to generate and parse XML files compliant +with this standard. + +Beside basic machinery it provides common components to generate common +GS1 schema elements. Namely: + +> - StandardBusinessDocumentHeader diff --git a/edi_gs1/security/gs1_backend_acl.xml b/edi_gs1_oca/security/gs1_backend_acl.xml similarity index 100% rename from edi_gs1/security/gs1_backend_acl.xml rename to edi_gs1_oca/security/gs1_backend_acl.xml diff --git a/edi_gs1/static/description/icon.png b/edi_gs1_oca/static/description/icon.png similarity index 100% rename from edi_gs1/static/description/icon.png rename to edi_gs1_oca/static/description/icon.png diff --git a/edi_gs1/static/description/index.html b/edi_gs1_oca/static/description/index.html similarity index 72% rename from edi_gs1/static/description/index.html rename to edi_gs1_oca/static/description/index.html index 0ad160f14..32db3d701 100644 --- a/edi_gs1/static/description/index.html +++ b/edi_gs1_oca/static/description/index.html @@ -1,20 +1,20 @@ - - -Base GS1 EDI + +README.rst -
-

Base GS1 EDI

+
+ + +Odoo Community Association + +
+

Base GS1 EDI

-

Alpha License: AGPL-3 OCA/edi Translate me on Weblate Try me on Runbot

-

This is the base module for the implementation of the -GS1 -The Global Language of Business standard.

-

This module contains methods to generate and parse XML files compliant with this standard.

-

Beside basic machinery it provides common components to generate common GS1 schema elements. -Namely:

+

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

+

This is the base module for the implementation of the GS1 -The Global +Language of Business standard.

+

This module contains methods to generate and parse XML files compliant +with this standard.

+

Beside basic machinery it provides common components to generate common +GS1 schema elements. Namely:

    -
  • StandardBusinessDocumentHeader
  • +
  • StandardBusinessDocumentHeader
@@ -387,48 +395,51 @@

Base GS1 EDI

Table of contents

-

Bug Tracker

-

Bugs are tracked on GitHub Issues. +

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 smashing it by providing a detailed and welcomed -feedback.

+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

+

Credits

-

Authors

+

Authors

  • ACSONE
-

Maintainers

+

Maintainers

This module is maintained by the OCA.

-Odoo Community Association + +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/edi project on GitHub.

+

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

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

+
diff --git a/edi_gs1/static/schemas/gs1/ecom/ApplicationReceiptAcknowledgement.xsd b/edi_gs1_oca/static/schemas/gs1/ecom/ApplicationReceiptAcknowledgement.xsd similarity index 100% rename from edi_gs1/static/schemas/gs1/ecom/ApplicationReceiptAcknowledgement.xsd rename to edi_gs1_oca/static/schemas/gs1/ecom/ApplicationReceiptAcknowledgement.xsd diff --git a/edi_gs1/static/schemas/gs1/ecom/WarehousingCommon.xsd b/edi_gs1_oca/static/schemas/gs1/ecom/WarehousingCommon.xsd similarity index 100% rename from edi_gs1/static/schemas/gs1/ecom/WarehousingCommon.xsd rename to edi_gs1_oca/static/schemas/gs1/ecom/WarehousingCommon.xsd diff --git a/edi_gs1/static/schemas/gs1/ecom/WarehousingInboundInstruction.xsd b/edi_gs1_oca/static/schemas/gs1/ecom/WarehousingInboundInstruction.xsd similarity index 100% rename from edi_gs1/static/schemas/gs1/ecom/WarehousingInboundInstruction.xsd rename to edi_gs1_oca/static/schemas/gs1/ecom/WarehousingInboundInstruction.xsd diff --git a/edi_gs1/static/schemas/gs1/ecom/WarehousingInboundNotification.xsd b/edi_gs1_oca/static/schemas/gs1/ecom/WarehousingInboundNotification.xsd similarity index 100% rename from edi_gs1/static/schemas/gs1/ecom/WarehousingInboundNotification.xsd rename to edi_gs1_oca/static/schemas/gs1/ecom/WarehousingInboundNotification.xsd diff --git a/edi_gs1/static/schemas/gs1/ecom/WarehousingOutboundInstruction.xsd b/edi_gs1_oca/static/schemas/gs1/ecom/WarehousingOutboundInstruction.xsd similarity index 100% rename from edi_gs1/static/schemas/gs1/ecom/WarehousingOutboundInstruction.xsd rename to edi_gs1_oca/static/schemas/gs1/ecom/WarehousingOutboundInstruction.xsd diff --git a/edi_gs1/static/schemas/gs1/ecom/WarehousingOutboundNotification.xsd b/edi_gs1_oca/static/schemas/gs1/ecom/WarehousingOutboundNotification.xsd similarity index 100% rename from edi_gs1/static/schemas/gs1/ecom/WarehousingOutboundNotification.xsd rename to edi_gs1_oca/static/schemas/gs1/ecom/WarehousingOutboundNotification.xsd diff --git a/edi_gs1/static/schemas/gs1/ecom/eComCommon.xsd b/edi_gs1_oca/static/schemas/gs1/ecom/eComCommon.xsd similarity index 100% rename from edi_gs1/static/schemas/gs1/ecom/eComCommon.xsd rename to edi_gs1_oca/static/schemas/gs1/ecom/eComCommon.xsd diff --git a/edi_gs1/static/schemas/gs1/shared/SharedCommon.xsd b/edi_gs1_oca/static/schemas/gs1/shared/SharedCommon.xsd similarity index 100% rename from edi_gs1/static/schemas/gs1/shared/SharedCommon.xsd rename to edi_gs1_oca/static/schemas/gs1/shared/SharedCommon.xsd diff --git a/edi_gs1/static/schemas/sbdh/BasicTypes.xsd b/edi_gs1_oca/static/schemas/sbdh/BasicTypes.xsd similarity index 100% rename from edi_gs1/static/schemas/sbdh/BasicTypes.xsd rename to edi_gs1_oca/static/schemas/sbdh/BasicTypes.xsd diff --git a/edi_gs1/static/schemas/sbdh/BusinessScope.xsd b/edi_gs1_oca/static/schemas/sbdh/BusinessScope.xsd similarity index 100% rename from edi_gs1/static/schemas/sbdh/BusinessScope.xsd rename to edi_gs1_oca/static/schemas/sbdh/BusinessScope.xsd diff --git a/edi_gs1/static/schemas/sbdh/DocumentIdentification.xsd b/edi_gs1_oca/static/schemas/sbdh/DocumentIdentification.xsd similarity index 100% rename from edi_gs1/static/schemas/sbdh/DocumentIdentification.xsd rename to edi_gs1_oca/static/schemas/sbdh/DocumentIdentification.xsd diff --git a/edi_gs1/static/schemas/sbdh/Manifest.xsd b/edi_gs1_oca/static/schemas/sbdh/Manifest.xsd similarity index 100% rename from edi_gs1/static/schemas/sbdh/Manifest.xsd rename to edi_gs1_oca/static/schemas/sbdh/Manifest.xsd diff --git a/edi_gs1/static/schemas/sbdh/Partner.xsd b/edi_gs1_oca/static/schemas/sbdh/Partner.xsd similarity index 100% rename from edi_gs1/static/schemas/sbdh/Partner.xsd rename to edi_gs1_oca/static/schemas/sbdh/Partner.xsd diff --git a/edi_gs1/static/schemas/sbdh/StandardBusinessDocumentHeader.xsd b/edi_gs1_oca/static/schemas/sbdh/StandardBusinessDocumentHeader.xsd similarity index 100% rename from edi_gs1/static/schemas/sbdh/StandardBusinessDocumentHeader.xsd rename to edi_gs1_oca/static/schemas/sbdh/StandardBusinessDocumentHeader.xsd diff --git a/edi_gs1/tests/__init__.py b/edi_gs1_oca/tests/__init__.py similarity index 100% rename from edi_gs1/tests/__init__.py rename to edi_gs1_oca/tests/__init__.py diff --git a/edi_gs1/tests/common.py b/edi_gs1_oca/tests/common.py similarity index 98% rename from edi_gs1/tests/common.py rename to edi_gs1_oca/tests/common.py index 77690dfbf..255e89a36 100644 --- a/edi_gs1/tests/common.py +++ b/edi_gs1_oca/tests/common.py @@ -13,7 +13,6 @@ @tagged("-at_install", "post_install") class BaseTestCase(SavepointComponentCase, xmlunittest.XmlTestMixin): - _schema_path = "edi_gs1:static/schemas/sbdh/StandardBusinessDocumentHeader.xsd" @classmethod @@ -51,7 +50,7 @@ def flatten(self, txt): def read_test_file(self, filename): path = os.path.join(os.path.dirname(__file__), "examples", filename) - with open(path, "r") as thefile: + with open(path) as thefile: return thefile.read() _schema_path = "" diff --git a/edi_gs1/tests/examples/ApplicationReceiptAcknowledgement.example1.xml b/edi_gs1_oca/tests/examples/ApplicationReceiptAcknowledgement.example1.xml similarity index 100% rename from edi_gs1/tests/examples/ApplicationReceiptAcknowledgement.example1.xml rename to edi_gs1_oca/tests/examples/ApplicationReceiptAcknowledgement.example1.xml diff --git a/edi_gs1/tests/test_business_header.py b/edi_gs1_oca/tests/test_business_header.py similarity index 99% rename from edi_gs1/tests/test_business_header.py rename to edi_gs1_oca/tests/test_business_header.py index 40c5309ef..abee08ac1 100644 --- a/edi_gs1/tests/test_business_header.py +++ b/edi_gs1_oca/tests/test_business_header.py @@ -10,7 +10,6 @@ class BusinessHeaderTestCase(BaseTestCase): - _schema_path = "edi_gs1:static/schemas/sbdh/StandardBusinessDocumentHeader.xsd" @classmethod diff --git a/edi_gs1/views/edi_backend.xml b/edi_gs1_oca/views/edi_backend.xml similarity index 100% rename from edi_gs1/views/edi_backend.xml rename to edi_gs1_oca/views/edi_backend.xml diff --git a/edi_gs1/views/res_partner.xml b/edi_gs1_oca/views/res_partner.xml similarity index 100% rename from edi_gs1/views/res_partner.xml rename to edi_gs1_oca/views/res_partner.xml diff --git a/edi_gs1/views/uom_uom.xml b/edi_gs1_oca/views/uom_uom.xml similarity index 100% rename from edi_gs1/views/uom_uom.xml rename to edi_gs1_oca/views/uom_uom.xml diff --git a/edi_gs1/xmler.py b/edi_gs1_oca/xmler.py similarity index 99% rename from edi_gs1/xmler.py rename to edi_gs1_oca/xmler.py index 7c9ccfb47..24888ec63 100644 --- a/edi_gs1/xmler.py +++ b/edi_gs1_oca/xmler.py @@ -88,7 +88,6 @@ def dict2xml(input_dict, encoding="utf-8", pretty=False): def parse(input_dict, parent=None, pretty=False): parent = parent or {} for key, value in input_dict.items(): - if isinstance(value, (float, int)): # Enfoce strings here value = str(value) diff --git a/requirements.txt b/requirements.txt index 4ec60a70e..d950e810d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,5 @@ # generated from manifests external_dependencies PyYAML openupgradelib +xmlschema +xmlunittest From ada475e70cea31d82bebdf27fa9ffbb71a762e9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Honor=C3=A9?= Date: Mon, 29 Jun 2026 11:02:34 +0200 Subject: [PATCH 31/63] [MIG] edi_gs1_oca: migration to 19.0 Migrate from the legacy component-based EDI framework to the new edi_core_oca model-based architecture: - Replace components (AbstractComponent / _usage / _find_component) with model-based handlers inheriting edi.oca.handler.* abstract models. - Add edi.gs1.xml helper (XSD validate/parse), edi.gs1.input.mixin, edi.gs1.output.mixin and edi.gs1.handler.* (validate + process ack). - Wire handlers on exchange types via input/output_validate_model_id and process_model_id; move schema_path to advanced_settings (gs1: section). - Drop the edi.exchange.template.output record; render QWeb templates through the output mixin and purge nswrapper helpers (utils.py). - Update post_init_hook to the new env-based signature. - Point view inherit_id to edi_core_oca and fix module xmlids. - Adapt tests: create partners instead of removed demo xmlids and set contact details (Odoo 19 QWeb t-out uses the element body as fallback when the value is falsy). --- edi_gs1_oca/__init__.py | 1 - edi_gs1_oca/__manifest__.py | 5 +- edi_gs1_oca/components/__init__.py | 4 - .../components/application_receipt_ack.py | 36 --------- .../components/application_send_ack.py | 15 ---- edi_gs1_oca/components/input_mixin.py | 28 ------- edi_gs1_oca/components/output_mixin.py | 25 ------ .../data/ack_in_exchange_type_data.xml | 16 ++-- .../data/ack_out_exchange_type_data.xml | 13 ++- .../data/business_header_output_template.xml | 17 ---- .../data/business_header_qweb_template.xml | 4 +- edi_gs1_oca/hooks.py | 7 +- edi_gs1_oca/models/__init__.py | 4 + edi_gs1_oca/models/edi_gs1_handler.py | 81 +++++++++++++++++++ edi_gs1_oca/models/edi_gs1_input.py | 28 +++++++ edi_gs1_oca/models/edi_gs1_output.py | 74 +++++++++++++++++ edi_gs1_oca/models/edi_gs1_xml.py | 63 +++++++++++++++ edi_gs1_oca/tests/common.py | 37 ++++----- edi_gs1_oca/tests/test_business_header.py | 23 +++--- edi_gs1_oca/utils.py | 39 +++++++++ edi_gs1_oca/views/edi_backend.xml | 4 +- edi_gs1_oca/views/res_partner.xml | 2 +- edi_gs1_oca/views/uom_uom.xml | 4 +- 23 files changed, 341 insertions(+), 189 deletions(-) delete mode 100644 edi_gs1_oca/components/__init__.py delete mode 100644 edi_gs1_oca/components/application_receipt_ack.py delete mode 100644 edi_gs1_oca/components/application_send_ack.py delete mode 100644 edi_gs1_oca/components/input_mixin.py delete mode 100644 edi_gs1_oca/components/output_mixin.py delete mode 100644 edi_gs1_oca/data/business_header_output_template.xml create mode 100644 edi_gs1_oca/models/edi_gs1_handler.py create mode 100644 edi_gs1_oca/models/edi_gs1_input.py create mode 100644 edi_gs1_oca/models/edi_gs1_output.py create mode 100644 edi_gs1_oca/models/edi_gs1_xml.py create mode 100644 edi_gs1_oca/utils.py diff --git a/edi_gs1_oca/__init__.py b/edi_gs1_oca/__init__.py index d20ee91b9..cc6b6354a 100644 --- a/edi_gs1_oca/__init__.py +++ b/edi_gs1_oca/__init__.py @@ -1,3 +1,2 @@ from . import models -from . import components from .hooks import post_init_hook diff --git a/edi_gs1_oca/__manifest__.py b/edi_gs1_oca/__manifest__.py index dcf600e9b..375824a45 100644 --- a/edi_gs1_oca/__manifest__.py +++ b/edi_gs1_oca/__manifest__.py @@ -6,12 +6,12 @@ "name": "Base GS1 EDI", "summary": """ Base module for GS1 standard EDI exchange""", - "version": "13.0.1.0.0", + "version": "19.0.1.0.0", "development_status": "Alpha", "license": "AGPL-3", "website": "https://github.com/OCA/edi-framework", "author": "ACSONE,Odoo Community Association (OCA)", - "depends": ["component", "edi_exchange_template", "edi_xml", "uom"], + "depends": ["edi_core_oca", "uom"], "external_dependencies": {"python": ["xmlschema", "xmlunittest"]}, "data": [ "security/gs1_backend_acl.xml", @@ -19,7 +19,6 @@ "data/ack_in_exchange_type_data.xml", "data/ack_out_exchange_type_data.xml", "data/business_header_qweb_template.xml", - "data/business_header_output_template.xml", "data/contact_details_qweb_template.xml", "views/res_partner.xml", "views/edi_backend.xml", diff --git a/edi_gs1_oca/components/__init__.py b/edi_gs1_oca/components/__init__.py deleted file mode 100644 index 9ccb8dd7d..000000000 --- a/edi_gs1_oca/components/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from . import output_mixin -from . import input_mixin -from . import application_receipt_ack -from . import application_send_ack diff --git a/edi_gs1_oca/components/application_receipt_ack.py b/edi_gs1_oca/components/application_receipt_ack.py deleted file mode 100644 index f9033e023..000000000 --- a/edi_gs1_oca/components/application_receipt_ack.py +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright 2020 ACSONE -# @author: Simone Orsi -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from odoo import _, exceptions - -from odoo.addons.component.core import Component - - -class ApplicationReceiptAcknowledgement(Component): - """Parse GS1 applicationReceiptAcknowledgement.""" - - _name = "gs1.input.applicationReceiptAcknowledgement" - _inherit = "edi.gs1.input.mixin" - _usage = "gs1.in.ApplicationReceiptAcknowledgement" - - def _process_data(self, data): - if not self.is_ok(data): - # TODO: give a proper message based on the real state - # options: ERROR, RECEIVED, WARNING - raise exceptions.ValidationError(_("Exchange not received")) - - def is_ok(self, data): - return self._get_status(data) == "RECEIVED" - - def _get_status(self, data): - try: - ack = data["applicationReceiptAcknowledgement"][0] - except IndexError: - # Maybe raise an EDIValidationError? - raise exceptions.ValidationError( - _("applicationReceiptAcknowledgement element not found!") - ) - status_wrapper = ack["applicationResponseDocumentLevel"][0] - # options: ERROR, RECEIVED, WARNING - return status_wrapper["applicationResponseStatusCode"] diff --git a/edi_gs1_oca/components/application_send_ack.py b/edi_gs1_oca/components/application_send_ack.py deleted file mode 100644 index b1df370d3..000000000 --- a/edi_gs1_oca/components/application_send_ack.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright 2020 ACSONE -# @author: Simone Orsi -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - - -from odoo.addons.component.core import Component - - -class ApplicationSendAcknowledgement(Component): - """Generate GS1 applicationSendAcknowledgement.""" - - _name = "gs1.output.applicationSendAcknowledgement" - _inherit = "edi.gs1.output.mixin" - _usage = "gs1.out.ApplicationSendAcknowledgement" - _xsd_schema_path = "static/schemas/gs1/ecom/ApplicationReceiptAcknowledgement.xsd" diff --git a/edi_gs1_oca/components/input_mixin.py b/edi_gs1_oca/components/input_mixin.py deleted file mode 100644 index b09db3a03..000000000 --- a/edi_gs1_oca/components/input_mixin.py +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright 2020 ACSONE -# @author: Simone Orsi -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from odoo.addons.component.core import AbstractComponent - - -class GS1InputMessageMixin(AbstractComponent): - """Common gs1 input mixin.""" - - _name = "edi.gs1.input.mixin" - _inherit = ["edi.component.input.mixin"] - _xsd_schema_path = None - - def process(self): - schema_path = self._xsd_schema_path or self.work.schema_path - handler = self.backend._find_component( - self.backend._name, - ["edi.xml"], - work_ctx={"schema_path": schema_path}, - safe=False, - ) - data = handler.parse_xml(self.exchange_record._get_file_content()) - self._process_data(data) - return True - - def _process_data(self, data): - NotImplementedError() diff --git a/edi_gs1_oca/components/output_mixin.py b/edi_gs1_oca/components/output_mixin.py deleted file mode 100644 index 1f0895217..000000000 --- a/edi_gs1_oca/components/output_mixin.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright 2020 ACSONE -# @author: Simone Orsi -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - - -from odoo.addons.component.core import AbstractComponent - - -class GS1OutputMessageMixin(AbstractComponent): - """Common gs1 output mixin.""" - - _name = "edi.gs1.output.mixin" - _inherit = ["edi.output.info.mixin"] - _work_context_validate_attrs = [ - "exchange_record", - "sender", - "receiver", - ] - - def _document_action_code(self): - # Brand new file - code = "ADD" - if self.replace_existing: - code = "CHANGE_BY_REFRESH" - return code diff --git a/edi_gs1_oca/data/ack_in_exchange_type_data.xml b/edi_gs1_oca/data/ack_in_exchange_type_data.xml index 40fcc019f..e63b86fbe 100644 --- a/edi_gs1_oca/data/ack_in_exchange_type_data.xml +++ b/edi_gs1_oca/data/ack_in_exchange_type_data.xml @@ -1,7 +1,7 @@ - + GS1 ApplicationReceiptAcknowledgement applicationReceiptAcknowledgement_in input @@ -12,14 +12,14 @@ --> xml + + - components: - validate: - usage: edi.xml - work_ctx: - schema_path: edi_gs1:static/schemas/gs1/ecom/ApplicationReceiptAcknowledgement.xsd - process: - usage: gs1.in.ApplicationReceiptAcknowledgement +gs1: + schema_path: edi_gs1_oca:static/schemas/gs1/ecom/ApplicationReceiptAcknowledgement.xsd diff --git a/edi_gs1_oca/data/ack_out_exchange_type_data.xml b/edi_gs1_oca/data/ack_out_exchange_type_data.xml index a22a5a276..edc47991c 100644 --- a/edi_gs1_oca/data/ack_out_exchange_type_data.xml +++ b/edi_gs1_oca/data/ack_out_exchange_type_data.xml @@ -1,20 +1,17 @@ - + GS1 ApplicationReceiptAcknowledgement applicationReceiptAcknowledgement_out output + {record_name}-{type.code}-{dt} xml - - + - components: - validate: - usage: edi.xml - work_ctx: - schema_path: edi_gs1:static/schemas/gs1/ecom/ApplicationReceiptAcknowledgement.xsd +gs1: + schema_path: edi_gs1_oca:static/schemas/gs1/ecom/ApplicationReceiptAcknowledgement.xsd diff --git a/edi_gs1_oca/data/business_header_output_template.xml b/edi_gs1_oca/data/business_header_output_template.xml deleted file mode 100644 index 4ab29a20b..000000000 --- a/edi_gs1_oca/data/business_header_output_template.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - EDI Exchange - GS1 Business Header - gs1.BusinessHeader - - xml - - -# Default identifier to record name -result = {"instance_identifier": exchange_record.identifier} - - - diff --git a/edi_gs1_oca/data/business_header_qweb_template.xml b/edi_gs1_oca/data/business_header_qweb_template.xml index 033eaad66..30f104fc0 100644 --- a/edi_gs1_oca/data/business_header_qweb_template.xml +++ b/edi_gs1_oca/data/business_header_qweb_template.xml @@ -36,13 +36,13 @@ 1.0 - + - + diff --git a/edi_gs1_oca/hooks.py b/edi_gs1_oca/hooks.py index b36b3f8b9..c6dbb045f 100644 --- a/edi_gs1_oca/hooks.py +++ b/edi_gs1_oca/hooks.py @@ -1,14 +1,11 @@ # Copyright 2021 ACSONE SA/NV () # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from odoo.api import SUPERUSER_ID, Environment -def post_init_hook(cr, registry): +def post_init_hook(env): """ Hook used to init automatically some GS1 code on existing UoM - :param cr: database cursor - :param registry: + :param env: Odoo Environment :return: """ - env = Environment(cr, SUPERUSER_ID, {"tracking_disable": True}) env["uom.uom"]._execute_gs1_map_code() diff --git a/edi_gs1_oca/models/__init__.py b/edi_gs1_oca/models/__init__.py index bf8796723..a0074d0b1 100644 --- a/edi_gs1_oca/models/__init__.py +++ b/edi_gs1_oca/models/__init__.py @@ -1,3 +1,7 @@ +from . import edi_gs1_xml +from . import edi_gs1_output +from . import edi_gs1_input +from . import edi_gs1_handler from . import edi_backend from . import res_partner from . import uom_uom diff --git a/edi_gs1_oca/models/edi_gs1_handler.py b/edi_gs1_oca/models/edi_gs1_handler.py new file mode 100644 index 000000000..bff5164cc --- /dev/null +++ b/edi_gs1_oca/models/edi_gs1_handler.py @@ -0,0 +1,81 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import exceptions, models + +from odoo.addons.edi_core_oca.exceptions import EDIValidationError + + +class EdiGs1HandlerValidate(models.AbstractModel): + """Validate GS1 exchange files against their XSD schema. + + Wired on exchange types via ``input_validate_model_id`` / + ``output_validate_model_id``. The schema is read from the exchange type + advanced settings:: + + gs1: + schema_path: edi_gs1_oca:static/schemas/.../Foo.xsd + """ + + _name = "edi.gs1.handler.validate" + _inherit = ["edi.oca.handler.input.validate", "edi.oca.handler.output.validate"] + _description = "GS1 EDI XSD validation handler" + + def _gs1_validate(self, exchange_record, value=None): + settings = exchange_record.type_id.get_settings() or {} + schema_path = settings.get("gs1", {}).get("schema_path") + if not schema_path: + return None + content = value + if content is None: + content = exchange_record._get_file_content() + if isinstance(content, bytes): + content = content.decode() + error = self.env["edi.gs1.xml"].validate(schema_path, content) + if error: + raise EDIValidationError(error) + return None + + def input_validate(self, exchange_record, value=None, **kw): + return self._gs1_validate(exchange_record, value=value) + + def output_validate(self, exchange_record, value=None, **kw): + return self._gs1_validate(exchange_record, value=value) + + +class EdiGs1HandlerApplicationReceiptAck(models.AbstractModel): + """Process GS1 applicationReceiptAcknowledgement. + + Wired on the input ack exchange type via ``process_model_id``. + """ + + _name = "edi.gs1.handler.application.receipt.ack" + _inherit = ["edi.oca.handler.process", "edi.gs1.input.mixin"] + _description = "GS1 process applicationReceiptAcknowledgement" + + def process(self, exchange_record, **kw): + data = self._parse(exchange_record) + self._process_data(data, exchange_record) + return self.env._("applicationReceiptAcknowledgement processed") + + def _process_data(self, data, exchange_record): + if not self.is_ok(data): + # TODO: give a proper message based on the real state + # options: ERROR, RECEIVED, WARNING + raise exceptions.ValidationError(self.env._("Exchange not received")) + + def is_ok(self, data): + return self._get_status(data) == "RECEIVED" + + def _get_status(self, data): + try: + ack = data["applicationReceiptAcknowledgement"][0] + except IndexError as exc: + # Maybe raise an EDIValidationError? + raise exceptions.ValidationError( + self.env._("applicationReceiptAcknowledgement element not found!") + ) from exc + status_wrapper = ack["applicationResponseDocumentLevel"][0] + # options: ERROR, RECEIVED, WARNING + return status_wrapper["applicationResponseStatusCode"] diff --git a/edi_gs1_oca/models/edi_gs1_input.py b/edi_gs1_oca/models/edi_gs1_input.py new file mode 100644 index 000000000..710c3de5d --- /dev/null +++ b/edi_gs1_oca/models/edi_gs1_input.py @@ -0,0 +1,28 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import models + + +class EdiGs1InputMixin(models.AbstractModel): + """Common GS1 input processing mixin. + + Replaces the old ``edi.gs1.input.mixin`` component. Concrete processors + inherit this mixin together with ``edi.oca.handler.process``. + """ + + _name = "edi.gs1.input.mixin" + _description = "GS1 EDI input mixin" + + def _get_schema_path(self, exchange_record): + settings = exchange_record.type_id.get_settings() or {} + return settings.get("gs1", {}).get("schema_path") + + def _parse(self, exchange_record): + schema_path = self._get_schema_path(exchange_record) + content = exchange_record._get_file_content() + return self.env["edi.gs1.xml"].parse_xml(schema_path, content) + + def _process_data(self, data, exchange_record): + raise NotImplementedError() diff --git a/edi_gs1_oca/models/edi_gs1_output.py b/edi_gs1_oca/models/edi_gs1_output.py new file mode 100644 index 000000000..61308416f --- /dev/null +++ b/edi_gs1_oca/models/edi_gs1_output.py @@ -0,0 +1,74 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import datetime + +import pytz + +from odoo import fields, models +from odoo.tools import DotDict + +from ..utils import xml_purge_nswrapper + + +class EdiGs1OutputMixin(models.AbstractModel): + """Common GS1 output generation mixin. + + Replaces the old ``edi.output.info.mixin`` (info provider) and the + ``edi.exchange.template.output`` QWeb rendering, both dropped in the + new EDI OCA architecture. Concrete generators inherit this mixin + together with ``edi.oca.handler.generate``. + """ + + _name = "edi.gs1.output.mixin" + _description = "GS1 EDI output mixin" + + # --- info provider (ported from edi.info.provider.mixin) --- + def generate_info(self, exchange_record, **kw): + """Generate and return data for output info. + + :return: odoo.tools.DotDict + """ + return DotDict(self._generate_info(exchange_record, **kw)) + + def _generate_info(self, exchange_record, **kw): + raise NotImplementedError("You must provide `_generate_info`") + + @staticmethod + def _utc_now(): + return datetime.datetime.utcnow().isoformat() + + @staticmethod + def date_to_string(dt, utc=True): + if utc: + dt = dt.astimezone(pytz.UTC) + return fields.Date.to_string(dt) + + def _document_action_code(self, replace_existing=False): + # Brand new file -> "ADD", otherwise replacing an existing one + return "CHANGE_BY_REFRESH" if replace_existing else "ADD" + + # --- template rendering (ported from edi.exchange.template.output) --- + def _get_render_values(self, exchange_record, **kw): + """Collect values to render a GS1 QWeb template.""" + values = { + "exchange_record": exchange_record, + "record": exchange_record.record, + "backend": exchange_record.backend_id, + # Default identifier to the exchange record one + "instance_identifier": exchange_record.identifier, + "doc_type": None, + "utc_now": self._utc_now, + "date_to_string": self.date_to_string, + "render_edi_template": self._render_edi_template, + "info": {}, + } + values.update(kw) + return values + + def _render_edi_template(self, exchange_record, xmlid, **kw): + """Render a GS1 QWeb template and purge the `nswrapper` helpers.""" + values = self._get_render_values(exchange_record, **kw) + output = self.env["ir.qweb"]._render(xmlid, values) + return xml_purge_nswrapper(output) diff --git a/edi_gs1_oca/models/edi_gs1_xml.py b/edi_gs1_oca/models/edi_gs1_xml.py new file mode 100644 index 000000000..df244e593 --- /dev/null +++ b/edi_gs1_oca/models/edi_gs1_xml.py @@ -0,0 +1,63 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import io +from contextlib import closing + +import xmlschema + +from odoo import models, tools +from odoo.tools import DotDict + + +class EdiGs1Xml(models.AbstractModel): + """Validate and parse XML against a bundled XSD schema. + + Replaces the ``edi.xml`` component from the old ``edi_xml`` module + (dropped in the new EDI OCA architecture). Schema paths use the + ``module:relative/path.xsd`` notation. + """ + + _name = "edi.gs1.xml" + _description = "GS1 EDI XML helper" + + @staticmethod + def _resolve_schema_path(schema_path): + try: + mod_name, path = schema_path.split(":") + except ValueError as exc: + raise ValueError("Path must be in the form `module:path`") from exc + return tools.file_path(f"{mod_name}/{path}") + + def _get_schema(self, schema_path): + return xmlschema.XMLSchema(self._resolve_schema_path(schema_path)) + + def parse_xml(self, schema_path, file_content, **kw): + """Read XML content and return a data dict. + + :param schema_path: schema path as ``module:path`` + :param file_content: str of XML file + :return: odoo.tools.DotDict with final data + """ + schema = self._get_schema(schema_path) + with closing(io.StringIO(file_content)) as fd: + return DotDict(schema.to_dict(fd, **kw)) + + def validate(self, schema_path, xml_content, raise_on_fail=False): + """Validate XML content against the XSD schema. + + :param schema_path: schema path as ``module:path`` + :param xml_content: str containing xml data to validate + :param raise_on_fail: turn on/off validation error exception on fail + :return: + * None if validation is ok + * error string if `raise_on_fail` is False + """ + schema = self._get_schema(schema_path) + try: + return schema.validate(xml_content) + except xmlschema.validators.exceptions.XMLSchemaValidationError as err: + if raise_on_fail: + raise + return str(err) diff --git a/edi_gs1_oca/tests/common.py b/edi_gs1_oca/tests/common.py index 255e89a36..7c0b2bae5 100644 --- a/edi_gs1_oca/tests/common.py +++ b/edi_gs1_oca/tests/common.py @@ -6,14 +6,12 @@ import xmlunittest -from odoo.tests.common import tagged - -from odoo.addons.component.tests.common import SavepointComponentCase +from odoo.tests.common import TransactionCase, tagged @tagged("-at_install", "post_install") -class BaseTestCase(SavepointComponentCase, xmlunittest.XmlTestMixin): - _schema_path = "edi_gs1:static/schemas/sbdh/StandardBusinessDocumentHeader.xsd" +class BaseTestCase(TransactionCase, xmlunittest.XmlTestMixin): + _schema_path = "edi_gs1_oca:static/schemas/sbdh/StandardBusinessDocumentHeader.xsd" @classmethod def setUpClass(cls): @@ -21,9 +19,16 @@ def setUpClass(cls): cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) cls.backend = cls._get_backend() # Logistic Services Provider (LSP) - cls.lsp_partner = cls.env.ref("base.res_partner_3") + cls.lsp_partner = cls.env["res.partner"].create( + { + "name": "Test LSP", + "email": "lsp@example.com", + "phone": "+1-212-555-0001", + } + ) # Logistic Services Client (LSC) cls.lsc_partner = cls.env.ref("base.main_partner") + cls.lsc_partner.write({"email": "lsc@example.com", "phone": "+1-212-555-0002"}) cls.backend.lsp_partner_id = cls.lsp_partner cls.backend.lsc_partner_id = cls.lsc_partner @@ -36,14 +41,7 @@ def setUpClass(cls): @classmethod def _get_backend(cls): - return cls.env.ref("edi_gs1.edi_backend_gs1_default") - - def _dev_write_example_file(self, test_file, filename, content): - from pathlib import Path - - path = Path(test_file).parent / ("examples/test." + filename) - with open(path, "w") as out: - out.write(content) + return cls.env.ref("edi_gs1_oca.edi_backend_gs1_default") def flatten(self, txt): return "".join([x.strip() for x in txt.splitlines()]) @@ -53,12 +51,7 @@ def read_test_file(self, filename): with open(path) as thefile: return thefile.read() - _schema_path = "" - - def _get_xml_handler(self): - return self.backend._find_component( - self.backend._name, - ["edi.xml"], - work_ctx={"schema_path": self._schema_path}, - safe=False, + def _validate_xml(self, content, schema_path=None): + return self.env["edi.gs1.xml"].validate( + schema_path or self._schema_path, content ) diff --git a/edi_gs1_oca/tests/test_business_header.py b/edi_gs1_oca/tests/test_business_header.py index abee08ac1..7bfcfc54b 100644 --- a/edi_gs1_oca/tests/test_business_header.py +++ b/edi_gs1_oca/tests/test_business_header.py @@ -10,7 +10,7 @@ class BusinessHeaderTestCase(BaseTestCase): - _schema_path = "edi_gs1:static/schemas/sbdh/StandardBusinessDocumentHeader.xsd" + _schema_path = "edi_gs1_oca:static/schemas/sbdh/StandardBusinessDocumentHeader.xsd" @classmethod def setUpClass(cls): @@ -19,7 +19,7 @@ def setUpClass(cls): @classmethod def _setup_records(cls): - cls.bh_tmpl = cls.env.ref("edi_gs1.edi_exchange_template_business_header") + cls.output_handler = cls.env["edi.gs1.output.mixin"] vals = { "backend_id": cls.backend.id, "backend_type_id": cls.backend.backend_type_id.id, @@ -28,11 +28,13 @@ def _setup_records(cls): "code": "test_type_out1", "exchange_file_ext": "txt", "exchange_filename_pattern": "{record.ref}-{type.code}-{dt}", + # Output types require a send handler (edi_core_oca constraint). + "send_model_id": cls.env.ref("edi_core_oca.model_edi_oca_handler_noop").id, } cls.exc_type = cls.env["edi.exchange.type"].create(vals) - cls.related_record = cls.env.ref("base.partner_demo") + # Does not really matter which record we bind the exchange to + cls.related_record = cls.env["res.partner"].create({"name": "Test Record"}) vals = { - # Does not really matter which record we bind the exchange to "model": cls.related_record._name, "res_id": cls.related_record.id, "type_id": cls.exc_type.id, @@ -40,21 +42,23 @@ def _setup_records(cls): cls.exc_record = cls.backend.create_record("test_type_out1", vals) def test_template_render_values(self): - values = self.bh_tmpl._get_render_values(self.exc_record) + values = self.output_handler._get_render_values(self.exc_record) expected = { "backend": self.backend, "exchange_record": self.exc_record, "instance_identifier": self.exc_record.identifier, "record": self.related_record, - "template": self.bh_tmpl, } for k, v in expected.items(): self.assertEqual(values[k], v) @freeze_time("2020-07-08 07:30:00") def test_xml(self): - output = self.bh_tmpl.exchange_generate( - self.exc_record, sender=self.lsc_partner, receiver=self.lsp_partner + output = self.output_handler._render_edi_template( + self.exc_record, + "edi_gs1_oca.edi_exchange_business_header", + sender=self.lsc_partner, + receiver=self.lsp_partner, ) expected = """ @@ -94,5 +98,4 @@ def test_xml(self): date="2020-07-08T07:30:00", ) self.assertXmlEquivalentOutputs(self.flatten(output), self.flatten(expected)) - handler = self._get_xml_handler() - self.assertEqual(handler.validate(output), None) + self.assertEqual(self._validate_xml(output), None) diff --git a/edi_gs1_oca/utils.py b/edi_gs1_oca/utils.py new file mode 100644 index 000000000..e22e20014 --- /dev/null +++ b/edi_gs1_oca/utils.py @@ -0,0 +1,39 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from lxml import etree + + +def xml_purge_nswrapper(xml_content): + """Purge `nswrapper` elements. + + QWeb template does not allow parsing namespaced elements + without declaring namespaces on the root element. + + Hence, by default you cannot define smaller re-usable templates + if they have namespaced elements. + + The trick is to wrap your reusable template with `nswrapper` element + which holds the namespace for that particular sub template. + For instance: + + + + + + Then this method is going to purge these unwanted elements from the result. + """ + if not (xml_content and xml_content.strip()): + return xml_content + root = etree.XML(xml_content) + # deeper elements come after, keep the root element at the end (if any) + for nswrapper in reversed(root.xpath("//nswrapper")): + parent = nswrapper.getparent() + if parent is None: + return "".join( + etree.tostring(el, encoding="unicode") for el in nswrapper.getchildren() + ) + parent.extend(nswrapper.getchildren()) + parent.remove(nswrapper) + return etree.tostring(root, encoding="unicode") diff --git a/edi_gs1_oca/views/edi_backend.xml b/edi_gs1_oca/views/edi_backend.xml index 33aacd56a..528ceb46a 100644 --- a/edi_gs1_oca/views/edi_backend.xml +++ b/edi_gs1_oca/views/edi_backend.xml @@ -3,9 +3,9 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> - edi.backend.form (in edi_gs1) + edi.backend.form (in edi_gs1_oca) edi.backend - + diff --git a/edi_gs1_oca/views/res_partner.xml b/edi_gs1_oca/views/res_partner.xml index 5ba50adfc..a0656ba4a 100644 --- a/edi_gs1_oca/views/res_partner.xml +++ b/edi_gs1_oca/views/res_partner.xml @@ -3,7 +3,7 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> - res.partner.form (in edi_gs1) + res.partner.form (in edi_gs1_oca) res.partner diff --git a/edi_gs1_oca/views/uom_uom.xml b/edi_gs1_oca/views/uom_uom.xml index f37265ae1..460b4fd1b 100644 --- a/edi_gs1_oca/views/uom_uom.xml +++ b/edi_gs1_oca/views/uom_uom.xml @@ -3,12 +3,12 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> - uom.uom.form (in edi_gs1) + uom.uom.form (in edi_gs1_oca) uom.uom - + From 4dabf7a3d950a9afcf7fbb9d90f5b4e05ee6edd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Honor=C3=A9?= Date: Thu, 2 Jul 2026 15:11:25 +0200 Subject: [PATCH 32/63] [IMP] edi_gs1_oca: move to Beta development status --- edi_gs1_oca/README.rst | 9 ++------- edi_gs1_oca/__manifest__.py | 2 +- edi_gs1_oca/static/description/index.html | 8 +------- 3 files changed, 4 insertions(+), 15 deletions(-) diff --git a/edi_gs1_oca/README.rst b/edi_gs1_oca/README.rst index 75a3541b5..deeb0aec8 100644 --- a/edi_gs1_oca/README.rst +++ b/edi_gs1_oca/README.rst @@ -14,9 +14,9 @@ Base GS1 EDI !! source digest: sha256:a5ab15a8b1dea2fd4da5955af650a8db9296c1a96cb53d4d7d98188d059a14da !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status - :alt: Alpha + :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 @@ -43,11 +43,6 @@ GS1 schema elements. Namely: - StandardBusinessDocumentHeader -.. IMPORTANT:: - This is an alpha version, the data model and design can change at any time without warning. - Only for development or testing purpose, do not use in production. - `More details on development status `_ - **Table of contents** .. contents:: diff --git a/edi_gs1_oca/__manifest__.py b/edi_gs1_oca/__manifest__.py index 375824a45..24ada98ef 100644 --- a/edi_gs1_oca/__manifest__.py +++ b/edi_gs1_oca/__manifest__.py @@ -7,7 +7,7 @@ "summary": """ Base module for GS1 standard EDI exchange""", "version": "19.0.1.0.0", - "development_status": "Alpha", + "development_status": "Beta", "license": "AGPL-3", "website": "https://github.com/OCA/edi-framework", "author": "ACSONE,Odoo Community Association (OCA)", diff --git a/edi_gs1_oca/static/description/index.html b/edi_gs1_oca/static/description/index.html index 32db3d701..f3d540eee 100644 --- a/edi_gs1_oca/static/description/index.html +++ b/edi_gs1_oca/static/description/index.html @@ -374,7 +374,7 @@

Base GS1 EDI

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! source digest: sha256:a5ab15a8b1dea2fd4da5955af650a8db9296c1a96cb53d4d7d98188d059a14da !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

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

+

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

This is the base module for the implementation of the GS1 -The Global Language of Business standard.

This module contains methods to generate and parse XML files compliant @@ -386,12 +386,6 @@

Base GS1 EDI

  • StandardBusinessDocumentHeader
  • -
    -

    Important

    -

    This is an alpha version, the data model and design can change at any time without warning. -Only for development or testing purpose, do not use in production. -More details on development status

    -

    Table of contents

      From dc6b03330c5ce8fcbb14661462c66e6e08be6ec6 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Thu, 9 Jul 2020 11:12:04 +0200 Subject: [PATCH 33/63] Add edi_gs1_stock --- edi_gs1_stock/README.rst | 81 + edi_gs1_stock/__init__.py | 2 + edi_gs1_stock/__manifest__.py | 26 + edi_gs1_stock/components/__init__.py | 1 + .../components/inbound_instruction.py | 276 +++ .../inbound_instruction_edi_exchange_type.xml | 15 + .../inbound_instruction_output_template.xml | 40 + .../inbound_instruction_qweb_template.xml | 99 + edi_gs1_stock/models/__init__.py | 1 + edi_gs1_stock/models/stock_picking.py | 48 + edi_gs1_stock/readme/CONTRIBUTORS.rst | 1 + edi_gs1_stock/readme/DESCRIPTION.rst | 9 + .../.setuptools-odoo-make-default-ignore | 2 + edi_gs1_stock/setup/README | 2 + edi_gs1_stock/static/description/icon.png | Bin 0 -> 9455 bytes edi_gs1_stock/static/description/index.html | 429 ++++ .../schemas/gs1/ecom/WarehousingCommon.xsd | 280 +++ .../ecom/WarehousingInboundInstruction.xsd | 77 + .../ecom/WarehousingInboundNotification.xsd | 86 + .../static/schemas/gs1/ecom/eComCommon.xsd | 1858 +++++++++++++++++ .../schemas/gs1/shared/SharedCommon.xsd | 1506 +++++++++++++ .../static/schemas/sbdh/BasicTypes.xsd | 20 + .../static/schemas/sbdh/BusinessScope.xsd | 55 + .../schemas/sbdh/DocumentIdentification.xsd | 14 + .../static/schemas/sbdh/Manifest.xsd | 19 + edi_gs1_stock/static/schemas/sbdh/Partner.xsd | 26 + .../sbdh/StandardBusinessDocumentHeader.xsd | 25 + edi_gs1_stock/tests/__init__.py | 1 + .../tests/_test_inbound_instruction_OLD.py | 247 +++ .../WarehousingInboundInstruction.orig.xml | 114 + .../examples/test.inbound_instruction.xml | 103 + .../tests/test_inbound_instruction.py | 138 ++ edi_gs1_stock/views/stock_picking_view.xml | 21 + 33 files changed, 5622 insertions(+) create mode 100644 edi_gs1_stock/README.rst create mode 100644 edi_gs1_stock/__init__.py create mode 100644 edi_gs1_stock/__manifest__.py create mode 100644 edi_gs1_stock/components/__init__.py create mode 100644 edi_gs1_stock/components/inbound_instruction.py create mode 100644 edi_gs1_stock/data/inbound_instruction_edi_exchange_type.xml create mode 100644 edi_gs1_stock/data/inbound_instruction_output_template.xml create mode 100644 edi_gs1_stock/data/inbound_instruction_qweb_template.xml create mode 100644 edi_gs1_stock/models/__init__.py create mode 100644 edi_gs1_stock/models/stock_picking.py create mode 100644 edi_gs1_stock/readme/CONTRIBUTORS.rst create mode 100644 edi_gs1_stock/readme/DESCRIPTION.rst create mode 100644 edi_gs1_stock/setup/.setuptools-odoo-make-default-ignore create mode 100644 edi_gs1_stock/setup/README create mode 100644 edi_gs1_stock/static/description/icon.png create mode 100644 edi_gs1_stock/static/description/index.html create mode 100644 edi_gs1_stock/static/schemas/gs1/ecom/WarehousingCommon.xsd create mode 100644 edi_gs1_stock/static/schemas/gs1/ecom/WarehousingInboundInstruction.xsd create mode 100644 edi_gs1_stock/static/schemas/gs1/ecom/WarehousingInboundNotification.xsd create mode 100644 edi_gs1_stock/static/schemas/gs1/ecom/eComCommon.xsd create mode 100644 edi_gs1_stock/static/schemas/gs1/shared/SharedCommon.xsd create mode 100644 edi_gs1_stock/static/schemas/sbdh/BasicTypes.xsd create mode 100644 edi_gs1_stock/static/schemas/sbdh/BusinessScope.xsd create mode 100644 edi_gs1_stock/static/schemas/sbdh/DocumentIdentification.xsd create mode 100644 edi_gs1_stock/static/schemas/sbdh/Manifest.xsd create mode 100644 edi_gs1_stock/static/schemas/sbdh/Partner.xsd create mode 100644 edi_gs1_stock/static/schemas/sbdh/StandardBusinessDocumentHeader.xsd create mode 100644 edi_gs1_stock/tests/__init__.py create mode 100644 edi_gs1_stock/tests/_test_inbound_instruction_OLD.py create mode 100644 edi_gs1_stock/tests/examples/WarehousingInboundInstruction.orig.xml create mode 100644 edi_gs1_stock/tests/examples/test.inbound_instruction.xml create mode 100644 edi_gs1_stock/tests/test_inbound_instruction.py create mode 100644 edi_gs1_stock/views/stock_picking_view.xml diff --git a/edi_gs1_stock/README.rst b/edi_gs1_stock/README.rst new file mode 100644 index 000000000..6b704730a --- /dev/null +++ b/edi_gs1_stock/README.rst @@ -0,0 +1,81 @@ +================= +GS1 EDI for stock +================= + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Fedi-lightgray.png?logo=github + :target: https://github.com/OCA/edi/tree/13.0/gs1_stock + :alt: OCA/edi +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/edi-13-0/edi-13-0-gs1_stock + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/226/13.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +`GS1 -The Global Language of Business `_ +implementation for stock. + +Provides support to generate or process the following messages: + + * Warehouse Inbound Instruction [WIP] + * Warehouse Inbound Notification [TODO] + * Warehouse Outbound Instruction [TODO] + * Warehouse Outbound Notification [TODO] + +**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 smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* ACSONE + +Contributors +~~~~~~~~~~~~ + +* Simone Orsi + +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/edi `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/edi_gs1_stock/__init__.py b/edi_gs1_stock/__init__.py new file mode 100644 index 000000000..f24d3e242 --- /dev/null +++ b/edi_gs1_stock/__init__.py @@ -0,0 +1,2 @@ +from . import components +from . import models diff --git a/edi_gs1_stock/__manifest__.py b/edi_gs1_stock/__manifest__.py new file mode 100644 index 000000000..0a1b0b427 --- /dev/null +++ b/edi_gs1_stock/__manifest__.py @@ -0,0 +1,26 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "GS1 EDI for stock", + "summary": """ + Base module for GS1 standard EDI exchange related to stock. + """, + "version": "13.0.1.0.0", + "license": "AGPL-3", + "author": "ACSONE,Odoo Community Association (OCA)", + "depends": [ + "edi_gs1", + "stock", + "purchase_stock", + "delivery", + "edi_exchange_template", + ], + "data": [ + "views/stock_picking_view.xml", + "data/inbound_instruction_edi_exchange_type.xml", + "data/inbound_instruction_qweb_template.xml", + "data/inbound_instruction_output_template.xml", + ], +} diff --git a/edi_gs1_stock/components/__init__.py b/edi_gs1_stock/components/__init__.py new file mode 100644 index 000000000..f843ae475 --- /dev/null +++ b/edi_gs1_stock/components/__init__.py @@ -0,0 +1 @@ +from . import inbound_instruction diff --git a/edi_gs1_stock/components/inbound_instruction.py b/edi_gs1_stock/components/inbound_instruction.py new file mode 100644 index 000000000..c60097233 --- /dev/null +++ b/edi_gs1_stock/components/inbound_instruction.py @@ -0,0 +1,276 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +# from lxml import etree +# from odoo import tools +from odoo import _, exceptions +from odoo.tools import DotDict + +from odoo.addons.component.core import Component + + +class GS1InboundInstructionMessage(Component): + """Generate data for Inbound Instruction. + + + The Warehousing Inbound Instruction message enables a + + Logistic Services Client (LSC) + + to inform his + + Logistic Services Provider (LSP) + + that goods will be arriving. + + Warehousing Inbound Notification Message Definition + The Warehousing Inbound Notification message + enables a Logistic Services Provider (LSP) to inform + his Logistic Services Client (LSC) + on the status of goods received on behalf of the client. + """ + + _name = "gs1.output.inboundinstruction" + _inherit = [ + "edi.output.mixin", + ] + _usage = "gs1.warehousingInboundInstructionMessage" + # _xsd_schema_module = "gs1_stock" + # _xsd_schema_path = "static/schemas/gs1/ecom/WarehousingInboundInstruction.xsd" + + _work_context_validate_attrs = [ + "record", + "sender", + "receiver", + # instruction specific + "shipper", # shipper tag + "ls_buyer", # logisticServicesBuyer tag + "ls_seller", # logisticServicesSeller tag + ] + + def generate_info(self): + return DotDict(self._inbound_instruction()) + + def _inbound_instruction(self): + data = { + "creationDateTime": self._utc_now(), + # status code can stay always as it is if we don't send around copies + "documentStatusCode": "ORIGINAL", + "documentActionCode": self._document_action_code(), + # fmt: off + "warehousingInboundInstructionShipment": + self._inbound_instruction_shipment(), + # fmt: on + } + return {"warehousingInboundInstruction": data} + + @property + def replace_existing(self): + """Pass `replace_existing` to work context to change this value.""" + return getattr(self.work, "replace_existing", False) + + def _document_action_code(self): + # Brand new file + code = "ADD" + if self.replace_existing: + code = "CHANGE_BY_REFRESH" + return code + + def _entity_identification(self): + return self.record.name + + def _inbound_instruction_shipment(self): + data = {"shipmentIdentification": self._shipment_identification()} + for key, handler in self._inbound_instruction_shipment_elements().items(): + value = handler() + # Return empty dict or None in you handler to skip an element + if value: + data[key] = value + return data + + def _inbound_instruction_shipment_elements(self): + return { + "shipper": self._shipper, + "receiver": self._receiver, + "logisticUnit": self._logistic_unit, + "packageTotal": self._package_total, + "warehousingReceiptTypeCode": self._receipt_type_code, + "plannedReceipt": self._planned_receipt, + } + + def _shipment_identification(self): + return { + "additionalShipmentIdentification": { + "attrs": { + # fmt: off + "additionalShipmentIdentificationTypeCode": + "GOODS_RECEIVER_ASSIGNED" + # fmt: on + }, + "value": self.record.name, + } + } + + def _get_shipper_record(self): + # We should get the carrier here + return self.work.shipper + + def _shipper(self): + """The carrier of the shipment.""" + + record = self._get_shipper_record() + if not record.gln_code and not record.ref: + raise exceptions.ValidationError( + _("Either `gln_code` or `ref` is required for shipper: {}").format( + record.name + ) + ) + # `gln` is required in the schema. + # Depending on your LSP having a fake one and relying on + # `additionalPartyIdentification` can be enough. + data = {"gln": "".zfill(13)} + if record.gln_code: + data["gln"] = record.gln_code + if record.ref: + data["additionalPartyIdentification"] = { + "attrs": { + # fmt: off + "additionalPartyIdentificationTypeCode": + "BUYER_ASSIGNED_IDENTIFIER_FOR_A_PARTY" + # fmt: on + }, + "value": record.ref, + } + return data + + def _get_receiver_record(self): + return self.work.receiver + + def _receiver(self): + """Who receives the goods, generally the LSP.""" + record = self._get_receiver_record() + return { + "gln": record.gln_code, + } + + def _logistic_unit(self): + return { + # "sscc": "", + # TODO: pick this from uom or packaging info + # https://www.unece.org/ + # fileadmin/DAM/cefact/recommendations/rec21/rec21rev4_ecetrd309.pdf + # "packageTypeCode": "", + } + + def _package_total(self): + return DotDict( + { + # TODO: would be nice to have mapping based on product packaging + # but as in some case you simply use the same package types + # for all the shipments, then is up to integrator to provide + # proper values by overriding this method. + # https://www.gs1.se/en/our-standards/Technical-documentation/ + # code-lists/t0137-packaging-type-code/ + # TODO: get generic numbers from picking. + "packageTypeCode": "AF", + "totalPackageQuantity": "2", + "totalGrossWeight": { + "value": self.record.weight, + "attrs": {"measurementUnitCode": "KGM"}, + }, + } + ) + + def _receipt_type_code(self): + """TODO use picking priority? + + # cross-dock receipt. + # The instructed receipt is intended to be cross-docked. + "CROSS-DOCK_RECEIPT", + # priority receipt. + # The instructed receipt needs to be processed with high priority. + "PRIORITY_RECEIPT", + # regular receipt Normal receipt, no special actions required. + "REGULAR_RECEIPT", + # repair receipt. + # The instructed receipt relates to goods that were under repair. + "REPAIR_RECEIPT", + # return + # The instructed receipt is a return, for example a customer return or a recall. + "RETURNS", + """ + code = "REGULAR_RECEIPT" + return code + + def _planned_receipt(self): + # TODO: better info from? + return { + "logisticEventDateTime": { + "date": self.date_to_string(self.record.scheduled_date) or "", + }, + } + + def _inbound_instruction_shipment_items(self): + item_key = "warehousingInboundInstructionShipmentItem" + res = [] + for i, item in enumerate(self._get_shipment_items(), start=1): + res.append({item_key: self._shipment_item(item, i)}) + return res + + def _get_shipment_items(self): + return self.work.record.move_line_ids + + def _shipment_item(self, item, i=1): + qty = item.product_uom_qty + # TODO: get it from line uom + uom_code = "KGM" + # Watch out: order is important for XSD validation! + data = DotDict( + { + "lineItemNumber": i, + "transactionalTradeItem": self._shipment_item_trade_item(item), + } + ) + avp_list = self._shipment_item_avp_list(item) + if avp_list: + data["avpList"] = avp_list + data["plannedReceiptQuantity"] = { + "value": qty, + "attrs": {"measurementUnitCode": uom_code}, + } + return data + + def _shipment_item_trade_item(self, item): + # GTIN is required here. + # We assume you set it on product's default code. + # NOTE: GTIN must be 14 chars hence we fill the gap + # to make XSD validation happy in case it's empty. + # You are supposed to validate your barcode to match GTIN requirements. + return {"gtin": item.product_id.barcode or "".zfill(14)} + + def _shipment_item_avp_list(self, item): + """Hook to add avpList attributes. + + `avpList` can contain an arbitrary set of custom attributes. + Use `_make_avp_attribute()` to custom items and produce a list here. + + Eg: + [self._make_avp_attribute("vendorProductName", "Apple")] + + will generate an element like + + + Apple + + """ + return [] + + def _make_avp_attribute(self, attr_name, value): + """Generate `eComStringAttributeValuePairList` element.""" + return { + "eComStringAttributeValuePairList": { + "attrs": {"attributeName": attr_name}, + "value": value, + } + } diff --git a/edi_gs1_stock/data/inbound_instruction_edi_exchange_type.xml b/edi_gs1_stock/data/inbound_instruction_edi_exchange_type.xml new file mode 100644 index 000000000..3f1d90742 --- /dev/null +++ b/edi_gs1_stock/data/inbound_instruction_edi_exchange_type.xml @@ -0,0 +1,15 @@ + + + + + GS1 Inbound Instruction + gs1.warehousingInboundInstructionMessage + output + {record_name}-{type.code}-{dt} + xml + + + {type.exchange_filename_pattern}.ack + xml + + diff --git a/edi_gs1_stock/data/inbound_instruction_output_template.xml b/edi_gs1_stock/data/inbound_instruction_output_template.xml new file mode 100644 index 000000000..d11512c0a --- /dev/null +++ b/edi_gs1_stock/data/inbound_instruction_output_template.xml @@ -0,0 +1,40 @@ + + + + EDI Exchange - GS1 Inbound Instruction + gs1.warehousingInboundInstructionMessage + xml + + + +sender = backend.lsc_partner_id +receiver = backend.lsp_partner_id +logistic_service_seller = receiver +logistic_service_buyer = sender +# FIXME: this is probably wrong or not always true +buyer = sender +seller = record.purchase_id.partner_id +# TODO: get a default carrier somehow? +shipper = DotDict(dict(name="NO_CARRIER_SPECIFIED", gln_code="0" * 13, ref=None)) + +_result = { + "sender": sender, + "receiver": receiver, + "ls_seller": logistic_service_seller, + "ls_buyer": logistic_service_buyer, + "buyer": buyer, + "seller": seller, + "shipper": shipper, +} + +provider = get_info_provider(exchange_record, work_ctx=_result) +if provider: + _result["info"] = provider.generate_info() + +result = _result + + + diff --git a/edi_gs1_stock/data/inbound_instruction_qweb_template.xml b/edi_gs1_stock/data/inbound_instruction_qweb_template.xml new file mode 100644 index 000000000..7974ee439 --- /dev/null +++ b/edi_gs1_stock/data/inbound_instruction_qweb_template.xml @@ -0,0 +1,99 @@ + + + + diff --git a/edi_gs1_stock/models/__init__.py b/edi_gs1_stock/models/__init__.py new file mode 100644 index 000000000..ae4c27227 --- /dev/null +++ b/edi_gs1_stock/models/__init__.py @@ -0,0 +1 @@ +from . import stock_picking diff --git a/edi_gs1_stock/models/stock_picking.py b/edi_gs1_stock/models/stock_picking.py new file mode 100644 index 000000000..46d036f0c --- /dev/null +++ b/edi_gs1_stock/models/stock_picking.py @@ -0,0 +1,48 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging + +from odoo import api, models + +_logger = logging.getLogger(__name__) + + +# TODO: inherit from exchange consumer + + +class StockPicking(models.Model): + _inherit = "stock.picking" + + @api.model + def get_backend_by_delivery(self): + """Retrieve GS1 backend by given delivery order (stock.picking). + + You might have different LSP and pick up the right backend + based on the delivery order. + """ + # TODO: how do we handle this? + # We could have a wizard of some special fields to set by record + # which backend to use. + return self.env.ref("edi_gs1.default_gs1_backend") + + def send_wh_inbound_instruction(self, send=True): + """Generate an Inbound Instruction for given delivery and send it. + """ + delivery = self + edi_backend = self.get_backend_by_delivery() + type_code = "gs1.warehousingInboundInstructionMessage" + values = {"model": delivery._name, "res_id": delivery.id} + exchange_record = edi_backend.create_record(type_code, values) + edi_backend.generate_output(exchange_record) + + if "edi_exchange_send" in self.env.context: + send = self.env.context.get("edi_exchange_send") + if send: + edi_backend.exchange_send(exchange_record) + return exchange_record + + def action_send_wh_inbound_instruction(self): + # TODO: return action compat dict + return self.send_wh_inbound_instruction() diff --git a/edi_gs1_stock/readme/CONTRIBUTORS.rst b/edi_gs1_stock/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..f583948be --- /dev/null +++ b/edi_gs1_stock/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Simone Orsi diff --git a/edi_gs1_stock/readme/DESCRIPTION.rst b/edi_gs1_stock/readme/DESCRIPTION.rst new file mode 100644 index 000000000..f9459d688 --- /dev/null +++ b/edi_gs1_stock/readme/DESCRIPTION.rst @@ -0,0 +1,9 @@ +`GS1 -The Global Language of Business `_ +implementation for stock. + +Provides support to generate or process the following messages: + + * Warehouse Inbound Instruction [WIP] + * Warehouse Inbound Notification [TODO] + * Warehouse Outbound Instruction [TODO] + * Warehouse Outbound Notification [TODO] diff --git a/edi_gs1_stock/setup/.setuptools-odoo-make-default-ignore b/edi_gs1_stock/setup/.setuptools-odoo-make-default-ignore new file mode 100644 index 000000000..207e61533 --- /dev/null +++ b/edi_gs1_stock/setup/.setuptools-odoo-make-default-ignore @@ -0,0 +1,2 @@ +# addons listed in this file are ignored by +# setuptools-odoo-make-default (one addon per line) diff --git a/edi_gs1_stock/setup/README b/edi_gs1_stock/setup/README new file mode 100644 index 000000000..a63d633e8 --- /dev/null +++ b/edi_gs1_stock/setup/README @@ -0,0 +1,2 @@ +To learn more about this directory, please visit +https://pypi.python.org/pypi/setuptools-odoo diff --git a/edi_gs1_stock/static/description/icon.png b/edi_gs1_stock/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 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 z 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 Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 diff --git a/edi_gs1_stock/static/description/index.html b/edi_gs1_stock/static/description/index.html new file mode 100644 index 000000000..2cefa7a42 --- /dev/null +++ b/edi_gs1_stock/static/description/index.html @@ -0,0 +1,429 @@ + + + + + + +GS1 EDI for stock + + + +
      +

      GS1 EDI for stock

      + + +

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

      +

      GS1 -The Global Language of Business +implementation for stock.

      +

      Provides support to generate or process the following messages:

      +
      +
        +
      • Warehouse Inbound Instruction [WIP]
      • +
      • Warehouse Inbound Notification [TODO]
      • +
      • Warehouse Outbound Instruction [TODO]
      • +
      • Warehouse Outbound Notification [TODO]
      • +
      +
      +

      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 smashing it by providing a detailed and welcomed +feedback.

      +

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

      +
      +
      +

      Credits

      +
      +

      Authors

      +
        +
      • ACSONE
      • +
      +
      +
      +

      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/edi project on GitHub.

      +

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

      +
      +
      +
      + + diff --git a/edi_gs1_stock/static/schemas/gs1/ecom/WarehousingCommon.xsd b/edi_gs1_stock/static/schemas/gs1/ecom/WarehousingCommon.xsd new file mode 100644 index 000000000..2b2add5fb --- /dev/null +++ b/edi_gs1_stock/static/schemas/gs1/ecom/WarehousingCommon.xsd @@ -0,0 +1,280 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/edi_gs1_stock/static/schemas/gs1/ecom/WarehousingInboundInstruction.xsd b/edi_gs1_stock/static/schemas/gs1/ecom/WarehousingInboundInstruction.xsd new file mode 100644 index 000000000..eb4f90aaf --- /dev/null +++ b/edi_gs1_stock/static/schemas/gs1/ecom/WarehousingInboundInstruction.xsd @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/edi_gs1_stock/static/schemas/gs1/ecom/WarehousingInboundNotification.xsd b/edi_gs1_stock/static/schemas/gs1/ecom/WarehousingInboundNotification.xsd new file mode 100644 index 000000000..ad3811cc6 --- /dev/null +++ b/edi_gs1_stock/static/schemas/gs1/ecom/WarehousingInboundNotification.xsd @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/edi_gs1_stock/static/schemas/gs1/ecom/eComCommon.xsd b/edi_gs1_stock/static/schemas/gs1/ecom/eComCommon.xsd new file mode 100644 index 000000000..4ae5569dc --- /dev/null +++ b/edi_gs1_stock/static/schemas/gs1/ecom/eComCommon.xsd @@ -0,0 +1,1858 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/edi_gs1_stock/static/schemas/gs1/shared/SharedCommon.xsd b/edi_gs1_stock/static/schemas/gs1/shared/SharedCommon.xsd new file mode 100644 index 000000000..d51b853df --- /dev/null +++ b/edi_gs1_stock/static/schemas/gs1/shared/SharedCommon.xsd @@ -0,0 +1,1506 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/edi_gs1_stock/static/schemas/sbdh/BasicTypes.xsd b/edi_gs1_stock/static/schemas/sbdh/BasicTypes.xsd new file mode 100644 index 000000000..3718acfe5 --- /dev/null +++ b/edi_gs1_stock/static/schemas/sbdh/BasicTypes.xsd @@ -0,0 +1,20 @@ + + + + + + The MIME type as defined by IANA. Please refer to + http://www.iana.org/assignments/media-types/ for a list of types. + + + + + + + + ISO 639-2; 1998 representation of Language name. Refer to http://www.loc.gov/standards/iso639-2/iso639jac.html to get the latest version of the standard. + + + + + diff --git a/edi_gs1_stock/static/schemas/sbdh/BusinessScope.xsd b/edi_gs1_stock/static/schemas/sbdh/BusinessScope.xsd new file mode 100644 index 000000000..19ddd7ce0 --- /dev/null +++ b/edi_gs1_stock/static/schemas/sbdh/BusinessScope.xsd @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/edi_gs1_stock/static/schemas/sbdh/DocumentIdentification.xsd b/edi_gs1_stock/static/schemas/sbdh/DocumentIdentification.xsd new file mode 100644 index 000000000..5264a2e27 --- /dev/null +++ b/edi_gs1_stock/static/schemas/sbdh/DocumentIdentification.xsd @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/edi_gs1_stock/static/schemas/sbdh/Manifest.xsd b/edi_gs1_stock/static/schemas/sbdh/Manifest.xsd new file mode 100644 index 000000000..59bed11f1 --- /dev/null +++ b/edi_gs1_stock/static/schemas/sbdh/Manifest.xsd @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/edi_gs1_stock/static/schemas/sbdh/Partner.xsd b/edi_gs1_stock/static/schemas/sbdh/Partner.xsd new file mode 100644 index 000000000..ff4258da6 --- /dev/null +++ b/edi_gs1_stock/static/schemas/sbdh/Partner.xsd @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/edi_gs1_stock/static/schemas/sbdh/StandardBusinessDocumentHeader.xsd b/edi_gs1_stock/static/schemas/sbdh/StandardBusinessDocumentHeader.xsd new file mode 100644 index 000000000..b97fb45f4 --- /dev/null +++ b/edi_gs1_stock/static/schemas/sbdh/StandardBusinessDocumentHeader.xsd @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/edi_gs1_stock/tests/__init__.py b/edi_gs1_stock/tests/__init__.py new file mode 100644 index 000000000..22fbd84df --- /dev/null +++ b/edi_gs1_stock/tests/__init__.py @@ -0,0 +1 @@ +from . import test_inbound_instruction diff --git a/edi_gs1_stock/tests/_test_inbound_instruction_OLD.py b/edi_gs1_stock/tests/_test_inbound_instruction_OLD.py new file mode 100644 index 000000000..dafd2a71d --- /dev/null +++ b/edi_gs1_stock/tests/_test_inbound_instruction_OLD.py @@ -0,0 +1,247 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from uuid import uuid4 + +from freezegun import freeze_time + +from odoo import fields +from odoo.tests.common import Form + +from odoo.addons.edi_gs1.tests.common import BaseXMLTestCase + + +class DeliveryMixin(object): + @classmethod + def _create_purchase_order(cls, values, view=None): + """Create a purchase order + + :return: purchase order + """ + po = Form(cls.env["purchase.order"], view=view) + po.partner_ref = str(uuid4())[:6] + po.date_planned = fields.Date.today() + for k, v in values.items(): + setattr(po, k, v) + return po.save() + + @classmethod + def _create_purchase_order_line(cls, purchase_order, view=None, **kw): + """ + Create a purchase order line for give order + :return: line + """ + values = {} + values.update(kw) + po = Form(purchase_order, view=view) + with po.order_line.new() as po_line: + for k, v in values.items(): + setattr(po_line, k, v) + return po.save() + + +class InboundInstructionTestCaseBase(BaseXMLTestCase, DeliveryMixin): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) + # cls.type_out3 = cls._create_exchange_type( + # name="Template output 3", + # direction="output", + # code="test_type_out3", + # exchange_file_ext="xml", + # exchange_filename_pattern="{record.ref}-{type.code}-{dt}", + # ) + # model = cls.env["edi.exchange.template.output"] + # cls.tmpl_out3 = model.create( + # { + # "name": "Out 3", + # "type_id": cls.type_out3.id, + # "type": "qweb", + # "key": "edi_exchange.test_output3", + # "arch": """ + # + # {} + # + # """.format(WH_INBOUND_TMPL), + # } + # ) + # vals = { + # "model": cls.partner._name, + # "res_id": cls.partner.id, + # "type_id": cls.type_out3.id, + # } + # cls.record3 = cls.backend.create_record("test_type_out3", vals) + + @classmethod + def _setup_order(cls): + cls.product_a = cls.env.ref("product.product_product_4") + cls.product_a.barcode = "1" * 14 + cls.product_b = cls.env.ref("product.product_product_4b") + cls.product_b.barcode = "2" * 14 + cls.product_c = cls.env.ref("product.product_product_4c") + cls.product_c.barcode = "3" * 14 + cls.purchase = cls._create_purchase_order( + { + "partner_id": cls.env.ref("base.res_partner_10"), + "date_planned": "2020-07-12", + } + ) + lines = [ + {"product_id": cls.product_a, "product_qty": 300}, + {"product_id": cls.product_b, "product_qty": 200}, + {"product_id": cls.product_c, "product_qty": 100}, + ] + for line in lines: + cls._create_purchase_order_line(cls.purchase, **line) + + cls.purchase.button_approve() + cls.delivery_order = cls.purchase.picking_ids[0] + cls.carrier = cls.env.ref("base.res_partner_4") + cls.carrier.gln_code = "44".zfill(13) + cls.carrier.ref = "CARRIER#1" + + def _get_handler( + self, usage="gs1.warehousingInboundInstructionMessage", work_ctx=None, **kw + ): + default_work_ctx = dict( + record=self.delivery_order, + sender=self.lsc_partner, + receiver=self.lsp_partner, + shipper=self.carrier, + instance_identifier="WH-IN-TEST-01", + ls_buyer=self.lsc_partner, + ls_seller=self.lsp_partner, + ) + default_work_ctx.update(work_ctx or {}) + return self.backend._get_component(work_ctx=default_work_ctx, usage=usage, **kw) + + +class InboundInstructionTestCase(InboundInstructionTestCaseBase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls._setup_order() + + @freeze_time("2020-07-09 10:30:00") + def test_business_header_data(self): + handler = self._get_handler() + result = handler._business_header() + expected_doc_id = { + "@ns": "sh", + "Standard": {"@ns": "sh", "@value": "GS1"}, + "TypeVersion": {"@ns": "sh", "@value": "3.4"}, + "InstanceIdentifier": {"@ns": "sh", "@value": "WH-IN-TEST-01"}, + "Type": {"@ns": "sh", "@value": ""}, + "MultipleType": {"@ns": "sh", "@value": "false"}, + "CreationDateAndTime": {"@ns": "sh", "@value": "2020-07-09T10:30:00"}, + } + self.assertEqual( + result["StandardBusinessDocumentHeader"]["DocumentIdentification"], + expected_doc_id, + ) + + @freeze_time("2020-07-09 10:30:00") + def test_instruction_data(self): + handler = self._get_handler() + result = handler._inbound_instruction() + expected = { + "warehousingInboundInstruction": { + "creationDateTime": "2020-07-09T10:30:00", + "documentStatusCode": "ORIGINAL", + "documentActionCode": "ADD", + "warehousingInboundInstructionIdentification": { + "entityIdentification": self.delivery_order.name, + }, + "logisticServicesBuyer": {"gln": "0000000000002"}, + "logisticServicesSeller": {"gln": "0000000000001"}, + "warehousingInboundInstructionShipment": [ + { + "shipmentIdentification": { + "additionalShipmentIdentification": { + "@attrs": { + # fmt: off + "additionalShipmentIdentificationTypeCode": + "GOODS_RECEIVER_ASSIGNED" + # fmt: on + }, + "@value": self.delivery_order.name, + } + } + }, + { + "shipper": { + "gln": "0000000000044", + "additionalPartyIdentification": { + "@attrs": { + # fmt: off + "additionalPartyIdentificationTypeCode": + "BUYER_ASSIGNED_IDENTIFIER_FOR_A_PARTY" + # fmt: on + }, + "@value": "CARRIER#1", + }, + } + }, + {"receiver": {"gln": "0000000000001"}}, + { + "packageTotal": { + "packageTypeCode": "AF", + "totalPackageQuantity": "2", + "totalGrossWeight": { + "@value": self.delivery_order.weight, + "@attrs": {"measurementUnitCode": "KGM"}, + }, + } + }, + {"warehousingReceiptTypeCode": "REGULAR_RECEIPT"}, + { + "plannedReceipt": { + "logisticEventDateTime": {"date": "2020-07-12"} + } + }, + { + "warehousingInboundInstructionShipmentItem": { + "lineItemNumber": 1, + "transactionalTradeItem": {"gtin": "1" * 14}, + "plannedReceiptQuantity": { + "@value": 300.0, + "@attrs": {"measurementUnitCode": "KGM"}, + }, + } + }, + { + "warehousingInboundInstructionShipmentItem": { + "lineItemNumber": 2, + "transactionalTradeItem": {"gtin": "2" * 14}, + "plannedReceiptQuantity": { + "@value": 200.0, + "@attrs": {"measurementUnitCode": "KGM"}, + }, + } + }, + { + "warehousingInboundInstructionShipmentItem": { + "lineItemNumber": 3, + "transactionalTradeItem": {"gtin": "3" * 14}, + "plannedReceiptQuantity": { + "@value": 100.0, + "@attrs": {"measurementUnitCode": "KGM"}, + }, + } + }, + ], + } + } + self.assertEqual(result, expected) + + @freeze_time("2020-07-09 10:30:00") + def test_xml(self): + handler = self._get_handler() + result = handler.generate_xml() + self._dev_write_example_file(__file__, "inbound_instruction.xml", result) + self.assertFalse(handler.validate_schema(result, raise_on_fail=True)) + self.assertXmlDocument(result) + # self.assertXpathsExist(root, paths) + # self.assertXmlEquivalentOutputs(self.flatten(result), self.flatten(expected)) diff --git a/edi_gs1_stock/tests/examples/WarehousingInboundInstruction.orig.xml b/edi_gs1_stock/tests/examples/WarehousingInboundInstruction.orig.xml new file mode 100644 index 000000000..b9c7ec141 --- /dev/null +++ b/edi_gs1_stock/tests/examples/WarehousingInboundInstruction.orig.xml @@ -0,0 +1,114 @@ + + + + 1.0 + + + + John Doe + John_Doe@purchasing.XYZretailer.com + +1-212-555-1213 + +1-212-555-2122 + Buyer + + + + + + Mary Smith + Mary_Smith@widgets.com + +1-312-555-1214 + +1-312-555-2125 + Seller + + + + GS1 + 3.4 + 100002 + + false + 2006-01-10T12:00:01.000-05:00 + + + + 2012-07-11T12:15:00.000-05:00 + ORIGINAL + ADD + + INSREC00123 + + + 2222222000004 + + + 3333333000001 + + + + 11111110000834053 + + + 1111111000007 + + + 3333333000018 + + + 311111110000123455 + AF + + + 311111110000123554 + AF + + + AF + 2 + + PRIORITY_RECEIPT + + + 2012-07-12 + 10:00:00 + 2012-07-12 + 11:00:00 + + + + + LENMAN + + 12345 + DRIVERS_LICENSE + + + + 31 + 11-BB-AA + + + + 1 + + 02222222003333 + + + 311111110000123455 + 20 + + + 311111110000123554 + 20 + + 40 + + + + diff --git a/edi_gs1_stock/tests/examples/test.inbound_instruction.xml b/edi_gs1_stock/tests/examples/test.inbound_instruction.xml new file mode 100644 index 000000000..6dedaccec --- /dev/null +++ b/edi_gs1_stock/tests/examples/test.inbound_instruction.xml @@ -0,0 +1,103 @@ + + + 1.0 + + + + My Company (San Francisco) + info@yourcompany.example.com + +1 (650) 691-3277 + Buyer + + + + + + Gemini Furniture + gemini.furniture39@example.com + (941)-284-4875 + Seller + + + + GS1 + 3.4 + WH-IN-TEST-01 + + false + 2020-07-09T10:30:00 + + + + 2020-07-09T10:30:00 + ORIGINAL + ADD + + WH/IN/00014 + + + 0000000000002 + + + 0000000000001 + + + + WH/IN/00014 + + + 0000000000044 + CARRIER#1 + + + 0000000000001 + + + AF + 2 + 6.0 + + REGULAR_RECEIPT + + + 2020-07-12 + + + + 1 + + 11111111111111 + + 300.0 + + + 2 + + 22222222222222 + + 200.0 + + + 3 + + 33333333333333 + + 100.0 + + + + diff --git a/edi_gs1_stock/tests/test_inbound_instruction.py b/edi_gs1_stock/tests/test_inbound_instruction.py new file mode 100644 index 000000000..1ad53de3c --- /dev/null +++ b/edi_gs1_stock/tests/test_inbound_instruction.py @@ -0,0 +1,138 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import base64 +from uuid import uuid4 + +from freezegun import freeze_time + +from odoo import fields +from odoo.tests.common import Form + +from odoo.addons.edi_gs1.tests.common import BaseTestCase + + +class DeliveryMixin(object): + @classmethod + def _create_purchase_order(cls, values, view=None): + """Create a purchase order + + :return: purchase order + """ + po = Form(cls.env["purchase.order"], view=view) + po.partner_ref = str(uuid4())[:6] + po.date_planned = fields.Date.today() + for k, v in values.items(): + setattr(po, k, v) + return po.save() + + @classmethod + def _create_purchase_order_line(cls, purchase_order, view=None, **kw): + """ + Create a purchase order line for give order + :return: line + """ + values = {} + values.update(kw) + po = Form(purchase_order, view=view) + with po.order_line.new() as po_line: + for k, v in values.items(): + setattr(po_line, k, v) + return po.save() + + +class InboundInstructionTestCaseBase(BaseTestCase, DeliveryMixin): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) + + @classmethod + def _setup_order(cls): + cls.product_a = cls.env.ref("product.product_product_4") + cls.product_a.barcode = "1" * 14 + cls.product_b = cls.env.ref("product.product_product_4b") + cls.product_b.barcode = "2" * 14 + cls.product_c = cls.env.ref("product.product_product_4c") + cls.product_c.barcode = "3" * 14 + cls.purchase = cls._create_purchase_order( + { + "partner_id": cls.env.ref("base.res_partner_10"), + "date_planned": "2020-07-12", + } + ) + lines = [ + {"product_id": cls.product_a, "product_qty": 300}, + {"product_id": cls.product_b, "product_qty": 200}, + {"product_id": cls.product_c, "product_qty": 100}, + ] + for line in lines: + cls._create_purchase_order_line(cls.purchase, **line) + + cls.purchase.button_approve() + cls.delivery = cls.purchase.picking_ids[0] + cls.carrier = cls.env.ref("base.res_partner_4") + cls.carrier.gln_code = "44".zfill(13) + cls.carrier.ref = "CARRIER#1" + + +class InboundInstructionTestCase(InboundInstructionTestCaseBase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls._setup_order() + cls.exc_type = cls.env.ref("gs1_stock.edi_exchange_type_inbound_instruction") + cls.exc_tmpl = cls.env.ref( + "gs1_stock.edi_exchange_template_inbound_instruction" + ) + vals = { + "model": cls.delivery._name, + "res_id": cls.delivery.id, + "type_id": cls.exc_type.id, + } + cls.record = cls.backend.create_record(cls.exc_type.code, vals) + + def test_get_template(self): + template = self.backend._get_template(self.record) + self.assertEqual(template, self.exc_tmpl) + self.assertEqual( + template.template_id.key, "gs1_stock.edi_exchange_inbound_instruction" + ) + + def test_get_info(self): + values = self.exc_tmpl._get_render_values(self.record) + expected = [ + ("sender", self.backend.lsc_partner_id), + ("receiver", self.backend.lsp_partner_id), + ("ls_buyer", self.backend.lsc_partner_id), + ("ls_seller", self.backend.lsp_partner_id), + ("buyer", self.backend.lsc_partner_id), + ("seller", self.purchase.partner_id), + ( + "shipper", + { + "gln_code": "0000000000000", + "name": "NO_CARRIER_SPECIFIED", + "ref": None, + }, + ), + ] + for k, v in expected: + self.assertEqual(values[k], v) + # info = values["info"] + # expected_info = None + # TODO + # import pdb;pdb.set_trace() + pass + + @freeze_time("2020-07-09 10:30:00") + def test_xml(self): + record = self.delivery.with_context( + edi_exchange_send=False + ).action_send_wh_inbound_instruction() + file_content = base64.b64decode(record.exchange_file).decode().strip() + # FIXME: do proper validation + self.assertTrue( + file_content.startswith(" + + + + stock.picking.form (in gs1_stock) + stock.picking + + + + +
      +
      +
      +
      +
      From fd25d66ff6c8a8bba55277f7d467ae71ebf718dd Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Thu, 26 Nov 2020 08:59:09 +0100 Subject: [PATCH 34/63] edi_gs1_stock: adapt to edi core changes --- edi_gs1_stock/components/inbound_instruction.py | 3 +-- .../data/inbound_instruction_edi_exchange_type.xml | 4 ++-- .../data/inbound_instruction_output_template.xml | 3 ++- edi_gs1_stock/models/stock_picking.py | 4 ++-- edi_gs1_stock/tests/test_inbound_instruction.py | 10 ++++++---- 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/edi_gs1_stock/components/inbound_instruction.py b/edi_gs1_stock/components/inbound_instruction.py index c60097233..1f2dd2d50 100644 --- a/edi_gs1_stock/components/inbound_instruction.py +++ b/edi_gs1_stock/components/inbound_instruction.py @@ -13,7 +13,6 @@ class GS1InboundInstructionMessage(Component): """Generate data for Inbound Instruction. - The Warehousing Inbound Instruction message enables a Logistic Services Client (LSC) @@ -35,7 +34,7 @@ class GS1InboundInstructionMessage(Component): _inherit = [ "edi.output.mixin", ] - _usage = "gs1.warehousingInboundInstructionMessage" + _usage = "edi.output.gs1.warehousingInboundInstructionMessage.info" # _xsd_schema_module = "gs1_stock" # _xsd_schema_path = "static/schemas/gs1/ecom/WarehousingInboundInstruction.xsd" diff --git a/edi_gs1_stock/data/inbound_instruction_edi_exchange_type.xml b/edi_gs1_stock/data/inbound_instruction_edi_exchange_type.xml index 3f1d90742..03cdee1ca 100644 --- a/edi_gs1_stock/data/inbound_instruction_edi_exchange_type.xml +++ b/edi_gs1_stock/data/inbound_instruction_edi_exchange_type.xml @@ -1,9 +1,9 @@ - + GS1 Inbound Instruction - gs1.warehousingInboundInstructionMessage + warehousingInboundInstructionMessage output {record_name}-{type.code}-{dt} xml diff --git a/edi_gs1_stock/data/inbound_instruction_output_template.xml b/edi_gs1_stock/data/inbound_instruction_output_template.xml index d11512c0a..f6a8a1648 100644 --- a/edi_gs1_stock/data/inbound_instruction_output_template.xml +++ b/edi_gs1_stock/data/inbound_instruction_output_template.xml @@ -5,8 +5,9 @@ model="edi.exchange.template.output" > EDI Exchange - GS1 Inbound Instruction - gs1.warehousingInboundInstructionMessage + edi.output.gs1.warehousingInboundInstructionMessage xml + diff --git a/edi_gs1_stock/models/stock_picking.py b/edi_gs1_stock/models/stock_picking.py index 46d036f0c..369aca60d 100644 --- a/edi_gs1_stock/models/stock_picking.py +++ b/edi_gs1_stock/models/stock_picking.py @@ -25,14 +25,14 @@ def get_backend_by_delivery(self): # TODO: how do we handle this? # We could have a wizard of some special fields to set by record # which backend to use. - return self.env.ref("edi_gs1.default_gs1_backend") + return self.env.ref("edi_gs1.edi_backend_gs1_default") def send_wh_inbound_instruction(self, send=True): """Generate an Inbound Instruction for given delivery and send it. """ delivery = self edi_backend = self.get_backend_by_delivery() - type_code = "gs1.warehousingInboundInstructionMessage" + type_code = "warehousingInboundInstructionMessage" values = {"model": delivery._name, "res_id": delivery.id} exchange_record = edi_backend.create_record(type_code, values) edi_backend.generate_output(exchange_record) diff --git a/edi_gs1_stock/tests/test_inbound_instruction.py b/edi_gs1_stock/tests/test_inbound_instruction.py index 1ad53de3c..4da2a55b2 100644 --- a/edi_gs1_stock/tests/test_inbound_instruction.py +++ b/edi_gs1_stock/tests/test_inbound_instruction.py @@ -82,9 +82,11 @@ class InboundInstructionTestCase(InboundInstructionTestCaseBase): def setUpClass(cls): super().setUpClass() cls._setup_order() - cls.exc_type = cls.env.ref("gs1_stock.edi_exchange_type_inbound_instruction") + cls.exc_type = cls.env.ref( + "edi_gs1_stock.edi_exchange_type_inbound_instruction" + ) cls.exc_tmpl = cls.env.ref( - "gs1_stock.edi_exchange_template_inbound_instruction" + "edi_gs1_stock.edi_exchange_template_inbound_instruction" ) vals = { "model": cls.delivery._name, @@ -94,10 +96,10 @@ def setUpClass(cls): cls.record = cls.backend.create_record(cls.exc_type.code, vals) def test_get_template(self): - template = self.backend._get_template(self.record) + template = self.backend._get_output_template(self.record) self.assertEqual(template, self.exc_tmpl) self.assertEqual( - template.template_id.key, "gs1_stock.edi_exchange_inbound_instruction" + template.template_id.key, "edi_gs1_stock.edi_exchange_inbound_instruction" ) def test_get_info(self): From 8f48839d4c7e3bb4c8dad4020bfffb1d259ad648 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Fri, 27 Nov 2020 15:17:12 +0100 Subject: [PATCH 35/63] edi_gs1_stock: fix inbound_instruction --- .../components/inbound_instruction.py | 22 +---- .../inbound_instruction_output_template.xml | 4 +- .../inbound_instruction_qweb_template.xml | 93 +++++++++++-------- .../tests/test_inbound_instruction.py | 90 ++++++++++++++++-- 4 files changed, 145 insertions(+), 64 deletions(-) diff --git a/edi_gs1_stock/components/inbound_instruction.py b/edi_gs1_stock/components/inbound_instruction.py index 1f2dd2d50..d5e7858b2 100644 --- a/edi_gs1_stock/components/inbound_instruction.py +++ b/edi_gs1_stock/components/inbound_instruction.py @@ -34,7 +34,7 @@ class GS1InboundInstructionMessage(Component): _inherit = [ "edi.output.mixin", ] - _usage = "edi.output.gs1.warehousingInboundInstructionMessage.info" + _usage = "edi.output.generate.gs1.warehousingInboundInstructionMessage.info" # _xsd_schema_module = "gs1_stock" # _xsd_schema_path = "static/schemas/gs1/ecom/WarehousingInboundInstruction.xsd" @@ -91,11 +91,11 @@ def _inbound_instruction_shipment(self): def _inbound_instruction_shipment_elements(self): return { "shipper": self._shipper, - "receiver": self._receiver, "logisticUnit": self._logistic_unit, "packageTotal": self._package_total, "warehousingReceiptTypeCode": self._receipt_type_code, "plannedReceipt": self._planned_receipt, + "_shipment_items": self._inbound_instruction_shipment_items, } def _shipment_identification(self): @@ -117,7 +117,6 @@ def _get_shipper_record(self): def _shipper(self): """The carrier of the shipment.""" - record = self._get_shipper_record() if not record.gln_code and not record.ref: raise exceptions.ValidationError( @@ -128,9 +127,9 @@ def _shipper(self): # `gln` is required in the schema. # Depending on your LSP having a fake one and relying on # `additionalPartyIdentification` can be enough. - data = {"gln": "".zfill(13)} + data = {"gln_code": "".zfill(13)} if record.gln_code: - data["gln"] = record.gln_code + data["gln_code"] = record.gln_code if record.ref: data["additionalPartyIdentification"] = { "attrs": { @@ -143,16 +142,6 @@ def _shipper(self): } return data - def _get_receiver_record(self): - return self.work.receiver - - def _receiver(self): - """Who receives the goods, generally the LSP.""" - record = self._get_receiver_record() - return { - "gln": record.gln_code, - } - def _logistic_unit(self): return { # "sscc": "", @@ -211,10 +200,9 @@ def _planned_receipt(self): } def _inbound_instruction_shipment_items(self): - item_key = "warehousingInboundInstructionShipmentItem" res = [] for i, item in enumerate(self._get_shipment_items(), start=1): - res.append({item_key: self._shipment_item(item, i)}) + res.append(self._shipment_item(item, i)) return res def _get_shipment_items(self): diff --git a/edi_gs1_stock/data/inbound_instruction_output_template.xml b/edi_gs1_stock/data/inbound_instruction_output_template.xml index f6a8a1648..48b29e859 100644 --- a/edi_gs1_stock/data/inbound_instruction_output_template.xml +++ b/edi_gs1_stock/data/inbound_instruction_output_template.xml @@ -5,7 +5,9 @@ model="edi.exchange.template.output" > EDI Exchange - GS1 Inbound Instruction - edi.output.gs1.warehousingInboundInstructionMessage + edi.output.generate.gs1.warehousingInboundInstructionMessage xml diff --git a/edi_gs1_stock/data/inbound_instruction_qweb_template.xml b/edi_gs1_stock/data/inbound_instruction_qweb_template.xml index 7974ee439..bad2a8742 100644 --- a/edi_gs1_stock/data/inbound_instruction_qweb_template.xml +++ b/edi_gs1_stock/data/inbound_instruction_qweb_template.xml @@ -12,15 +12,21 @@ + + - 2020-07-09T10:30:00 + ORIGINAL ADD + WH/IN/00014 + t-esc="exchange_record.id" + >EDI/123456789 @@ -31,31 +37,33 @@ WH/IN/00014 - + 0000000000044 CARRIER#1 0000000000001 - + AF 2 6.0 2020-07-12 - - 1 - - 11111111111111 - - 300.0 - - - 2 - - 22222222222222 - - 200.0 - - - 3 - - 33333333333333 - - 100.0 - + + + + + 1 + + 11111111111111 + 102150001001000010003200010311101101 + + + + TRUE + + + 300.0 + + diff --git a/edi_gs1_stock/tests/test_inbound_instruction.py b/edi_gs1_stock/tests/test_inbound_instruction.py index 4da2a55b2..9313bf74c 100644 --- a/edi_gs1_stock/tests/test_inbound_instruction.py +++ b/edi_gs1_stock/tests/test_inbound_instruction.py @@ -73,7 +73,7 @@ def _setup_order(cls): cls.purchase.button_approve() cls.delivery = cls.purchase.picking_ids[0] cls.carrier = cls.env.ref("base.res_partner_4") - cls.carrier.gln_code = "44".zfill(13) + cls.carrier.gln_code = "123".zfill(13) cls.carrier.ref = "CARRIER#1" @@ -102,7 +102,7 @@ def test_get_template(self): template.template_id.key, "edi_gs1_stock.edi_exchange_inbound_instruction" ) - def test_get_info(self): + def test_render_values(self): values = self.exc_tmpl._get_render_values(self.record) expected = [ ("sender", self.backend.lsc_partner_id), @@ -122,11 +122,87 @@ def test_get_info(self): ] for k, v in expected: self.assertEqual(values[k], v) - # info = values["info"] - # expected_info = None - # TODO - # import pdb;pdb.set_trace() - pass + + # Detailed test below + self.assertTrue(values["info"]) + + def test_info_provider_bad_work_ctx(self): + with self.assertRaises(AttributeError) as err: + self.exc_tmpl._get_info_provider(self.record) + self.assertEqual( + str(err.exception), "`sender` is required for this component!" + ) + + @freeze_time("2020-07-09 10:30:00") + def test_info_provider_data(self): + values = self.exc_tmpl._get_render_values(self.record, shipper=self.carrier) + provider = self.exc_tmpl._get_info_provider(self.record, work_ctx=values) + expected_shipment = { + "shipmentIdentification": { + "additionalShipmentIdentification": { + "attrs": { + # fmt: off + "additionalShipmentIdentificationTypeCode": + "GOODS_RECEIVER_ASSIGNED" + # fmt: on + }, + "value": self.delivery.name, + } + }, + "shipper": { + "gln_code": "0000000000123", + "additionalPartyIdentification": { + "attrs": { + # fmt: off + "additionalPartyIdentificationTypeCode": + "BUYER_ASSIGNED_IDENTIFIER_FOR_A_PARTY" + # fmt: on + }, + "value": "CARRIER#1", + }, + }, + "packageTotal": { + "packageTypeCode": "AF", + "totalPackageQuantity": "2", + "totalGrossWeight": { + "value": self.delivery.weight, + "attrs": {"measurementUnitCode": "KGM"}, + }, + }, + "warehousingReceiptTypeCode": "REGULAR_RECEIPT", + "plannedReceipt": {"logisticEventDateTime": {"date": "2020-07-12"}}, + "_shipment_items": [ + { + "lineItemNumber": 1, + "transactionalTradeItem": {"gtin": "1" * 14}, + "plannedReceiptQuantity": { + "value": 300.0, + "attrs": {"measurementUnitCode": "KGM"}, + }, + }, + { + "lineItemNumber": 2, + "transactionalTradeItem": {"gtin": "2" * 14}, + "plannedReceiptQuantity": { + "value": 200.0, + "attrs": {"measurementUnitCode": "KGM"}, + }, + }, + { + "lineItemNumber": 3, + "transactionalTradeItem": {"gtin": "3" * 14}, + "plannedReceiptQuantity": { + "value": 100.0, + "attrs": {"measurementUnitCode": "KGM"}, + }, + }, + ], + } + info = provider.generate_info().warehousingInboundInstruction + for k, v in expected_shipment.items(): + self.assertEqual( + info.warehousingInboundInstructionShipment[k], v, f"{k} does not match" + ) @freeze_time("2020-07-09 10:30:00") def test_xml(self): From dd0ec34d73303d0a43e24b9e66068a7e019f2178 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Wed, 2 Dec 2020 18:08:28 +0100 Subject: [PATCH 36/63] edi_gs1_stock: inbound info based on output mixin --- edi_gs1_stock/components/__init__.py | 1 + .../components/inbound_instruction.py | 199 +----------------- edi_gs1_stock/components/output_mixin.py | 169 +++++++++++++++ 3 files changed, 180 insertions(+), 189 deletions(-) create mode 100644 edi_gs1_stock/components/output_mixin.py diff --git a/edi_gs1_stock/components/__init__.py b/edi_gs1_stock/components/__init__.py index f843ae475..92df94d17 100644 --- a/edi_gs1_stock/components/__init__.py +++ b/edi_gs1_stock/components/__init__.py @@ -1 +1,2 @@ +from . import output_mixin from . import inbound_instruction diff --git a/edi_gs1_stock/components/inbound_instruction.py b/edi_gs1_stock/components/inbound_instruction.py index d5e7858b2..44fddb317 100644 --- a/edi_gs1_stock/components/inbound_instruction.py +++ b/edi_gs1_stock/components/inbound_instruction.py @@ -2,11 +2,6 @@ # @author: Simone Orsi # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -# from lxml import etree -# from odoo import tools -from odoo import _, exceptions -from odoo.tools import DotDict - from odoo.addons.component.core import Component @@ -32,26 +27,11 @@ class GS1InboundInstructionMessage(Component): _name = "gs1.output.inboundinstruction" _inherit = [ - "edi.output.mixin", + "edi.gs1.output.shipment.mixin", ] _usage = "edi.output.generate.gs1.warehousingInboundInstructionMessage.info" - # _xsd_schema_module = "gs1_stock" - # _xsd_schema_path = "static/schemas/gs1/ecom/WarehousingInboundInstruction.xsd" - - _work_context_validate_attrs = [ - "record", - "sender", - "receiver", - # instruction specific - "shipper", # shipper tag - "ls_buyer", # logisticServicesBuyer tag - "ls_seller", # logisticServicesSeller tag - ] - def generate_info(self): - return DotDict(self._inbound_instruction()) - - def _inbound_instruction(self): + def _generate_info(self): data = { "creationDateTime": self._utc_now(), # status code can stay always as it is if we don't send around copies @@ -59,119 +39,23 @@ def _inbound_instruction(self): "documentActionCode": self._document_action_code(), # fmt: off "warehousingInboundInstructionShipment": - self._inbound_instruction_shipment(), + self._shipment_info(), # fmt: on } return {"warehousingInboundInstruction": data} - @property - def replace_existing(self): - """Pass `replace_existing` to work context to change this value.""" - return getattr(self.work, "replace_existing", False) - - def _document_action_code(self): - # Brand new file - code = "ADD" - if self.replace_existing: - code = "CHANGE_BY_REFRESH" - return code - - def _entity_identification(self): - return self.record.name - - def _inbound_instruction_shipment(self): - data = {"shipmentIdentification": self._shipment_identification()} - for key, handler in self._inbound_instruction_shipment_elements().items(): - value = handler() - # Return empty dict or None in you handler to skip an element - if value: - data[key] = value - return data - - def _inbound_instruction_shipment_elements(self): - return { - "shipper": self._shipper, - "logisticUnit": self._logistic_unit, - "packageTotal": self._package_total, - "warehousingReceiptTypeCode": self._receipt_type_code, - "plannedReceipt": self._planned_receipt, - "_shipment_items": self._inbound_instruction_shipment_items, - } - - def _shipment_identification(self): - return { - "additionalShipmentIdentification": { - "attrs": { - # fmt: off - "additionalShipmentIdentificationTypeCode": - "GOODS_RECEIVER_ASSIGNED" - # fmt: on - }, - "value": self.record.name, - } - } - - def _get_shipper_record(self): - # We should get the carrier here - return self.work.shipper - - def _shipper(self): - """The carrier of the shipment.""" - record = self._get_shipper_record() - if not record.gln_code and not record.ref: - raise exceptions.ValidationError( - _("Either `gln_code` or `ref` is required for shipper: {}").format( - record.name - ) - ) - # `gln` is required in the schema. - # Depending on your LSP having a fake one and relying on - # `additionalPartyIdentification` can be enough. - data = {"gln_code": "".zfill(13)} - if record.gln_code: - data["gln_code"] = record.gln_code - if record.ref: - data["additionalPartyIdentification"] = { - "attrs": { - # fmt: off - "additionalPartyIdentificationTypeCode": - "BUYER_ASSIGNED_IDENTIFIER_FOR_A_PARTY" - # fmt: on - }, - "value": record.ref, - } - return data - - def _logistic_unit(self): - return { - # "sscc": "", - # TODO: pick this from uom or packaging info - # https://www.unece.org/ - # fileadmin/DAM/cefact/recommendations/rec21/rec21rev4_ecetrd309.pdf - # "packageTypeCode": "", - } - - def _package_total(self): - return DotDict( + def _shipment_info_elements(self): + res = super()._shipment_info_elements() + res.update( { - # TODO: would be nice to have mapping based on product packaging - # but as in some case you simply use the same package types - # for all the shipments, then is up to integrator to provide - # proper values by overriding this method. - # https://www.gs1.se/en/our-standards/Technical-documentation/ - # code-lists/t0137-packaging-type-code/ - # TODO: get generic numbers from picking. - "packageTypeCode": "AF", - "totalPackageQuantity": "2", - "totalGrossWeight": { - "value": self.record.weight, - "attrs": {"measurementUnitCode": "KGM"}, - }, + "warehousingReceiptTypeCode": self._receipt_type_code, + "plannedReceipt": self._planned_receipt, } ) + return res def _receipt_type_code(self): - """TODO use picking priority? + """TODO # cross-dock receipt. # The instructed receipt is intended to be cross-docked. @@ -198,66 +82,3 @@ def _planned_receipt(self): "date": self.date_to_string(self.record.scheduled_date) or "", }, } - - def _inbound_instruction_shipment_items(self): - res = [] - for i, item in enumerate(self._get_shipment_items(), start=1): - res.append(self._shipment_item(item, i)) - return res - - def _get_shipment_items(self): - return self.work.record.move_line_ids - - def _shipment_item(self, item, i=1): - qty = item.product_uom_qty - # TODO: get it from line uom - uom_code = "KGM" - # Watch out: order is important for XSD validation! - data = DotDict( - { - "lineItemNumber": i, - "transactionalTradeItem": self._shipment_item_trade_item(item), - } - ) - avp_list = self._shipment_item_avp_list(item) - if avp_list: - data["avpList"] = avp_list - data["plannedReceiptQuantity"] = { - "value": qty, - "attrs": {"measurementUnitCode": uom_code}, - } - return data - - def _shipment_item_trade_item(self, item): - # GTIN is required here. - # We assume you set it on product's default code. - # NOTE: GTIN must be 14 chars hence we fill the gap - # to make XSD validation happy in case it's empty. - # You are supposed to validate your barcode to match GTIN requirements. - return {"gtin": item.product_id.barcode or "".zfill(14)} - - def _shipment_item_avp_list(self, item): - """Hook to add avpList attributes. - - `avpList` can contain an arbitrary set of custom attributes. - Use `_make_avp_attribute()` to custom items and produce a list here. - - Eg: - [self._make_avp_attribute("vendorProductName", "Apple")] - - will generate an element like - - - Apple - - """ - return [] - - def _make_avp_attribute(self, attr_name, value): - """Generate `eComStringAttributeValuePairList` element.""" - return { - "eComStringAttributeValuePairList": { - "attrs": {"attributeName": attr_name}, - "value": value, - } - } diff --git a/edi_gs1_stock/components/output_mixin.py b/edi_gs1_stock/components/output_mixin.py new file mode 100644 index 000000000..9c7916350 --- /dev/null +++ b/edi_gs1_stock/components/output_mixin.py @@ -0,0 +1,169 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import _, exceptions +from odoo.tools import DotDict + +from odoo.addons.component.core import AbstractComponent + + +class GS1OutputShipmentMessageMixin(AbstractComponent): + """Common gs1 output shipment mixin. + """ + + _name = "edi.gs1.output.shipment.mixin" + _inherit = [ + "edi.gs1.output.mixin", + ] + + @property + def _work_context_validate_attrs(self): + return super()._work_context_validate_attrs + [ + # instruction specific + "shipper", # shipper tag + "ls_buyer", # logisticServicesBuyer tag + "ls_seller", # logisticServicesSeller tag + ] + + def _shipment_info(self): + data = {"shipmentIdentification": self._shipment_identification()} + for key, handler in self._shipment_info_elements().items(): + value = handler() + # Return empty dict or None in you handler to skip an element + if value: + data[key] = value + return data + + def _shipment_identification(self): + return { + "additionalShipmentIdentification": { + "attrs": { + # fmt: off + "additionalShipmentIdentificationTypeCode": + "GOODS_RECEIVER_ASSIGNED" + # fmt: on + }, + "value": self.record.name, + } + } + + def _shipment_info_elements(self): + return { + "shipper": self._shipper, + "packageTotal": self._package_total, + "_shipment_items": self._shipment_items, + } + + def _get_shipper_record(self): + # We should get the carrier here + return self.work.shipper + + def _shipper(self): + """The carrier of the shipment.""" + record = self._get_shipper_record() + if not record.gln_code and not record.ref: + raise exceptions.ValidationError( + _("Either `gln_code` or `ref` is required for shipper: {}").format( + record.name + ) + ) + # `gln` is required in the schema. + # Depending on your LSP having a fake one and relying on + # `additionalPartyIdentification` can be enough. + data = {"gln_code": "".zfill(13)} + if record.gln_code: + data["gln_code"] = record.gln_code + if record.ref: + data["additionalPartyIdentification"] = { + "attrs": { + # fmt: off + "additionalPartyIdentificationTypeCode": + "BUYER_ASSIGNED_IDENTIFIER_FOR_A_PARTY" + # fmt: on + }, + "value": record.ref, + } + return data + + def _package_total(self): + return DotDict( + { + # TODO: would be nice to have mapping based on product packaging + # but as in some case you simply use the same package types + # for all the shipments, then is up to integrator to provide + # proper values by overriding this method. + # https://www.gs1.se/en/our-standards/Technical-documentation/ + # code-lists/t0137-packaging-type-code/ + # TODO: get generic numbers from picking. + "packageTypeCode": "AF", + "totalPackageQuantity": "2", + "totalGrossWeight": { + "value": self.record.weight, + "attrs": {"measurementUnitCode": "KGM"}, + }, + } + ) + + def _shipment_items(self): + res = [] + for i, item in enumerate(self._get_shipment_items(), start=1): + res.append(self._shipment_item(item, i)) + return res + + def _get_shipment_items(self): + return self.work.record.move_line_ids + + def _shipment_item(self, item, i=1): + qty = item.product_uom_qty + # TODO: get it from line uom + uom_code = "KGM" + # Watch out: order is important for XSD validation! + data = DotDict( + { + "lineItemNumber": i, + "transactionalTradeItem": self._shipment_item_trade_item(item), + } + ) + avp_list = self._shipment_item_avp_list(item) + if avp_list: + data["avpList"] = avp_list + data["plannedReceiptQuantity"] = { + "value": qty, + "attrs": {"measurementUnitCode": uom_code}, + } + return data + + def _shipment_item_trade_item(self, item): + # GTIN is required here. + # We assume you set it on product's default code. + # NOTE: GTIN must be 14 chars hence we fill the gap + # to make XSD validation happy in case it's empty. + # You are supposed to validate your barcode to match GTIN requirements. + return {"gtin": item.product_id.barcode or "".zfill(14)} + + def _shipment_item_avp_list(self, item): + """Hook to add avpList attributes. + + `avpList` can contain an arbitrary set of custom attributes. + Use `_make_avp_attribute()` to custom items and produce a list here. + + Eg: + [self._make_avp_attribute("vendorProductName", "Apple")] + + will generate an element like + + + Apple + + """ + return [] + + def _make_avp_attribute(self, attr_name, value): + """Generate `eComStringAttributeValuePairList` element.""" + return { + "eComStringAttributeValuePairList": { + "attrs": {"attributeName": attr_name}, + "value": value, + } + } From dc1d2997b2d7aa21af9b1225b506e96cbf3665fe Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Wed, 2 Dec 2020 18:09:50 +0100 Subject: [PATCH 37/63] edi_gs1_stock: inbound instruction fix identification --- edi_gs1_stock/data/inbound_instruction_qweb_template.xml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/edi_gs1_stock/data/inbound_instruction_qweb_template.xml b/edi_gs1_stock/data/inbound_instruction_qweb_template.xml index bad2a8742..8b5732131 100644 --- a/edi_gs1_stock/data/inbound_instruction_qweb_template.xml +++ b/edi_gs1_stock/data/inbound_instruction_qweb_template.xml @@ -23,10 +23,9 @@ ORIGINAL ADD - EDI/123456789 + t-esc="record.name" + >WH/IN/123456789 From 818aaaa63fcef036749b0f1de977ad2a4c971eb8 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Wed, 2 Dec 2020 18:11:06 +0100 Subject: [PATCH 38/63] edi_gs1_stock: inbound instruction use ack type --- edi_gs1_stock/__manifest__.py | 2 +- ...struction_edi_exchange_type.xml => edi_exchange_type.xml} | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) rename edi_gs1_stock/data/{inbound_instruction_edi_exchange_type.xml => edi_exchange_type.xml} (71%) diff --git a/edi_gs1_stock/__manifest__.py b/edi_gs1_stock/__manifest__.py index 0a1b0b427..721489e2c 100644 --- a/edi_gs1_stock/__manifest__.py +++ b/edi_gs1_stock/__manifest__.py @@ -19,7 +19,7 @@ ], "data": [ "views/stock_picking_view.xml", - "data/inbound_instruction_edi_exchange_type.xml", + "data/edi_exchange_type.xml", "data/inbound_instruction_qweb_template.xml", "data/inbound_instruction_output_template.xml", ], diff --git a/edi_gs1_stock/data/inbound_instruction_edi_exchange_type.xml b/edi_gs1_stock/data/edi_exchange_type.xml similarity index 71% rename from edi_gs1_stock/data/inbound_instruction_edi_exchange_type.xml rename to edi_gs1_stock/data/edi_exchange_type.xml index 03cdee1ca..96b622cdc 100644 --- a/edi_gs1_stock/data/inbound_instruction_edi_exchange_type.xml +++ b/edi_gs1_stock/data/edi_exchange_type.xml @@ -7,9 +7,6 @@ output {record_name}-{type.code}-{dt} xml - - - {type.exchange_filename_pattern}.ack - xml + From 13050f90eed7c2bf4a9a94e0edb04d99adcf3010 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Wed, 2 Dec 2020 18:43:55 +0100 Subject: [PATCH 39/63] edi_gs1: draft outbound instruction --- edi_gs1_stock/__manifest__.py | 2 + edi_gs1_stock/components/__init__.py | 1 + .../components/outbound_instruction.py | 80 ++++++ edi_gs1_stock/components/output_mixin.py | 6 +- edi_gs1_stock/data/edi_exchange_type.xml | 29 ++ .../inbound_instruction_qweb_template.xml | 4 +- .../outbound_instruction_output_template.xml | 43 +++ .../outbound_instruction_qweb_template.xml | 144 ++++++++++ edi_gs1_stock/models/stock_picking.py | 23 +- edi_gs1_stock/tests/__init__.py | 1 + .../tests/_test_inbound_instruction_OLD.py | 247 ------------------ edi_gs1_stock/tests/common.py | 74 ++++++ .../tests/test_inbound_instruction.py | 82 +----- .../tests/test_outbound_instruction.py | 146 +++++++++++ edi_gs1_stock/views/stock_picking_view.xml | 9 +- 15 files changed, 563 insertions(+), 328 deletions(-) create mode 100644 edi_gs1_stock/components/outbound_instruction.py create mode 100644 edi_gs1_stock/data/outbound_instruction_output_template.xml create mode 100644 edi_gs1_stock/data/outbound_instruction_qweb_template.xml delete mode 100644 edi_gs1_stock/tests/_test_inbound_instruction_OLD.py create mode 100644 edi_gs1_stock/tests/common.py create mode 100644 edi_gs1_stock/tests/test_outbound_instruction.py diff --git a/edi_gs1_stock/__manifest__.py b/edi_gs1_stock/__manifest__.py index 721489e2c..02ca1f8b0 100644 --- a/edi_gs1_stock/__manifest__.py +++ b/edi_gs1_stock/__manifest__.py @@ -22,5 +22,7 @@ "data/edi_exchange_type.xml", "data/inbound_instruction_qweb_template.xml", "data/inbound_instruction_output_template.xml", + "data/outbound_instruction_qweb_template.xml", + "data/outbound_instruction_output_template.xml", ], } diff --git a/edi_gs1_stock/components/__init__.py b/edi_gs1_stock/components/__init__.py index 92df94d17..cf5ea66e0 100644 --- a/edi_gs1_stock/components/__init__.py +++ b/edi_gs1_stock/components/__init__.py @@ -1,2 +1,3 @@ from . import output_mixin from . import inbound_instruction +from . import outbound_instruction diff --git a/edi_gs1_stock/components/outbound_instruction.py b/edi_gs1_stock/components/outbound_instruction.py new file mode 100644 index 000000000..531bd3063 --- /dev/null +++ b/edi_gs1_stock/components/outbound_instruction.py @@ -0,0 +1,80 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.addons.component.core import Component + + +class GS1OutboundInstructionMessage(Component): + """Generate data for Outbound Instruction. + + The Warehousing Outbound Instruction message enables a + + Logistic Services Client (LSC) + + to inform his + + Logistic Services Provider (LSP) + + that goods will be shipped. + + Warehousing Outbound Notification Message Definition + The Warehousing Outbound Notification message + enables a Logistic Services Provider (LSP) to inform + his Logistic Services Client (LSC) + on the status of goods received on behalf of the client. + """ + + _name = "gs1.output.outboundinstruction" + _inherit = [ + "edi.gs1.output.shipment.mixin", + ] + _usage = "edi.output.generate.gs1.warehousingOutboundInstructionMessage.info" + + def _generate_info(self): + data = { + "creationDateTime": self._utc_now(), + # status code can stay always as it is if we don't send around copies + "documentStatusCode": "ORIGINAL", + "documentActionCode": self._document_action_code(), + # fmt: off + "warehousingOutboundInstructionShipment": + self._shipment_info(), + # fmt: on + } + return {"warehousingOutboundInstruction": data} + + def _shipment_info_elements(self): + res = super()._shipment_info_elements() + res.update( + { + "warehousingDespatchTypeCode": self._despatch_type_code, + "plannedDespatch": self._planned_despatch, + } + ) + return res + + def _despatch_type_code(self): + """TODO get it from picking or mapping configuration or leace + + CROSS-DOCKED_SHIPMENT + Cross-docked shipment + One on one cross-dock of an incoming cross-dock shipment. + WAREHOUSE_CROSS-DOCK_COMBINATION + Warehouse cross-dock combination + combination of goods picked from stock + and goods taken from cross-docked receipts. + WAREHOUSE_SHIPMENT + Warehouse shipment. + All goods are picked from stock. + """ + code = "WAREHOUSE_SHIPMENT" + return code + + def _planned_despatch(self): + # TODO: better info from? + return { + "logisticEventDateTime": { + "date": self.date_to_string(self.record.scheduled_date) or "", + }, + } diff --git a/edi_gs1_stock/components/output_mixin.py b/edi_gs1_stock/components/output_mixin.py index 9c7916350..e2fb4d288 100644 --- a/edi_gs1_stock/components/output_mixin.py +++ b/edi_gs1_stock/components/output_mixin.py @@ -128,7 +128,11 @@ def _shipment_item(self, item, i=1): avp_list = self._shipment_item_avp_list(item) if avp_list: data["avpList"] = avp_list - data["plannedReceiptQuantity"] = { + # NOTE: `_plannedQty` does not match the final key + # which vary depending on the message + # (eg: plannedReceiptQuantity, plannedDespatchQuantity) + # You should handle it properly in your template. + data["_plannedQty"] = { "value": qty, "attrs": {"measurementUnitCode": uom_code}, } diff --git a/edi_gs1_stock/data/edi_exchange_type.xml b/edi_gs1_stock/data/edi_exchange_type.xml index 96b622cdc..4cbdf9eef 100644 --- a/edi_gs1_stock/data/edi_exchange_type.xml +++ b/edi_gs1_stock/data/edi_exchange_type.xml @@ -9,4 +9,33 @@ xml + + + + + GS1 Outbound Instruction + warehousingOutboundInstructionMessage + output + {record_name}-{type.code}-{dt} + xml + + + + diff --git a/edi_gs1_stock/data/inbound_instruction_qweb_template.xml b/edi_gs1_stock/data/inbound_instruction_qweb_template.xml index 8b5732131..4f812b1bf 100644 --- a/edi_gs1_stock/data/inbound_instruction_qweb_template.xml +++ b/edi_gs1_stock/data/inbound_instruction_qweb_template.xml @@ -100,8 +100,8 @@ 300.0 diff --git a/edi_gs1_stock/data/outbound_instruction_output_template.xml b/edi_gs1_stock/data/outbound_instruction_output_template.xml new file mode 100644 index 000000000..b9b274304 --- /dev/null +++ b/edi_gs1_stock/data/outbound_instruction_output_template.xml @@ -0,0 +1,43 @@ + + + + EDI Exchange - GS1 Outbound Instruction + edi.output.generate.gs1.warehousingOutboundInstructionMessage + xml + + + + +sender = backend.lsc_partner_id +receiver = backend.lsp_partner_id +logistic_service_seller = receiver +logistic_service_buyer = sender +# FIXME: this is probably wrong or not always true +buyer = sender +seller = record.purchase_id.partner_id +# TODO: get a default carrier somehow? +shipper = DotDict(dict(name="NO_CARRIER_SPECIFIED", gln_code="0" * 13, ref=None)) + +_result = { + "sender": sender, + "receiver": receiver, + "ls_seller": logistic_service_seller, + "ls_buyer": logistic_service_buyer, + "buyer": buyer, + "seller": seller, + "shipper": shipper, +} + +provider = get_info_provider(exchange_record, work_ctx=_result) +if provider: + _result["info"] = provider.generate_info() + +result = _result + + + diff --git a/edi_gs1_stock/data/outbound_instruction_qweb_template.xml b/edi_gs1_stock/data/outbound_instruction_qweb_template.xml new file mode 100644 index 000000000..4befc7046 --- /dev/null +++ b/edi_gs1_stock/data/outbound_instruction_qweb_template.xml @@ -0,0 +1,144 @@ + + + + diff --git a/edi_gs1_stock/models/stock_picking.py b/edi_gs1_stock/models/stock_picking.py index 369aca60d..54348f89f 100644 --- a/edi_gs1_stock/models/stock_picking.py +++ b/edi_gs1_stock/models/stock_picking.py @@ -27,7 +27,8 @@ def get_backend_by_delivery(self): # which backend to use. return self.env.ref("edi_gs1.edi_backend_gs1_default") - def send_wh_inbound_instruction(self, send=True): + # TODO: check if sending is required + def send_wh_inbound_instruction(self, send=False): """Generate an Inbound Instruction for given delivery and send it. """ delivery = self @@ -46,3 +47,23 @@ def send_wh_inbound_instruction(self, send=True): def action_send_wh_inbound_instruction(self): # TODO: return action compat dict return self.send_wh_inbound_instruction() + + def send_wh_outbound_instruction(self, send=False): + """Generate an Outbound Instruction for given delivery and send it. + """ + delivery = self + edi_backend = self.get_backend_by_delivery() + type_code = "warehousingOutboundInstructionMessage" + values = {"model": delivery._name, "res_id": delivery.id} + exchange_record = edi_backend.create_record(type_code, values) + edi_backend.generate_output(exchange_record) + + if "edi_exchange_send" in self.env.context: + send = self.env.context.get("edi_exchange_send") + if send: + edi_backend.exchange_send(exchange_record) + return exchange_record + + def action_send_wh_outbound_instruction(self): + # TODO: return action compat dict + return self.send_wh_outbound_instruction() diff --git a/edi_gs1_stock/tests/__init__.py b/edi_gs1_stock/tests/__init__.py index 22fbd84df..2bfea355d 100644 --- a/edi_gs1_stock/tests/__init__.py +++ b/edi_gs1_stock/tests/__init__.py @@ -1 +1,2 @@ from . import test_inbound_instruction +from . import test_outbound_instruction diff --git a/edi_gs1_stock/tests/_test_inbound_instruction_OLD.py b/edi_gs1_stock/tests/_test_inbound_instruction_OLD.py deleted file mode 100644 index dafd2a71d..000000000 --- a/edi_gs1_stock/tests/_test_inbound_instruction_OLD.py +++ /dev/null @@ -1,247 +0,0 @@ -# Copyright 2020 ACSONE -# @author: Simone Orsi -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from uuid import uuid4 - -from freezegun import freeze_time - -from odoo import fields -from odoo.tests.common import Form - -from odoo.addons.edi_gs1.tests.common import BaseXMLTestCase - - -class DeliveryMixin(object): - @classmethod - def _create_purchase_order(cls, values, view=None): - """Create a purchase order - - :return: purchase order - """ - po = Form(cls.env["purchase.order"], view=view) - po.partner_ref = str(uuid4())[:6] - po.date_planned = fields.Date.today() - for k, v in values.items(): - setattr(po, k, v) - return po.save() - - @classmethod - def _create_purchase_order_line(cls, purchase_order, view=None, **kw): - """ - Create a purchase order line for give order - :return: line - """ - values = {} - values.update(kw) - po = Form(purchase_order, view=view) - with po.order_line.new() as po_line: - for k, v in values.items(): - setattr(po_line, k, v) - return po.save() - - -class InboundInstructionTestCaseBase(BaseXMLTestCase, DeliveryMixin): - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) - # cls.type_out3 = cls._create_exchange_type( - # name="Template output 3", - # direction="output", - # code="test_type_out3", - # exchange_file_ext="xml", - # exchange_filename_pattern="{record.ref}-{type.code}-{dt}", - # ) - # model = cls.env["edi.exchange.template.output"] - # cls.tmpl_out3 = model.create( - # { - # "name": "Out 3", - # "type_id": cls.type_out3.id, - # "type": "qweb", - # "key": "edi_exchange.test_output3", - # "arch": """ - # - # {} - # - # """.format(WH_INBOUND_TMPL), - # } - # ) - # vals = { - # "model": cls.partner._name, - # "res_id": cls.partner.id, - # "type_id": cls.type_out3.id, - # } - # cls.record3 = cls.backend.create_record("test_type_out3", vals) - - @classmethod - def _setup_order(cls): - cls.product_a = cls.env.ref("product.product_product_4") - cls.product_a.barcode = "1" * 14 - cls.product_b = cls.env.ref("product.product_product_4b") - cls.product_b.barcode = "2" * 14 - cls.product_c = cls.env.ref("product.product_product_4c") - cls.product_c.barcode = "3" * 14 - cls.purchase = cls._create_purchase_order( - { - "partner_id": cls.env.ref("base.res_partner_10"), - "date_planned": "2020-07-12", - } - ) - lines = [ - {"product_id": cls.product_a, "product_qty": 300}, - {"product_id": cls.product_b, "product_qty": 200}, - {"product_id": cls.product_c, "product_qty": 100}, - ] - for line in lines: - cls._create_purchase_order_line(cls.purchase, **line) - - cls.purchase.button_approve() - cls.delivery_order = cls.purchase.picking_ids[0] - cls.carrier = cls.env.ref("base.res_partner_4") - cls.carrier.gln_code = "44".zfill(13) - cls.carrier.ref = "CARRIER#1" - - def _get_handler( - self, usage="gs1.warehousingInboundInstructionMessage", work_ctx=None, **kw - ): - default_work_ctx = dict( - record=self.delivery_order, - sender=self.lsc_partner, - receiver=self.lsp_partner, - shipper=self.carrier, - instance_identifier="WH-IN-TEST-01", - ls_buyer=self.lsc_partner, - ls_seller=self.lsp_partner, - ) - default_work_ctx.update(work_ctx or {}) - return self.backend._get_component(work_ctx=default_work_ctx, usage=usage, **kw) - - -class InboundInstructionTestCase(InboundInstructionTestCaseBase): - @classmethod - def setUpClass(cls): - super().setUpClass() - cls._setup_order() - - @freeze_time("2020-07-09 10:30:00") - def test_business_header_data(self): - handler = self._get_handler() - result = handler._business_header() - expected_doc_id = { - "@ns": "sh", - "Standard": {"@ns": "sh", "@value": "GS1"}, - "TypeVersion": {"@ns": "sh", "@value": "3.4"}, - "InstanceIdentifier": {"@ns": "sh", "@value": "WH-IN-TEST-01"}, - "Type": {"@ns": "sh", "@value": ""}, - "MultipleType": {"@ns": "sh", "@value": "false"}, - "CreationDateAndTime": {"@ns": "sh", "@value": "2020-07-09T10:30:00"}, - } - self.assertEqual( - result["StandardBusinessDocumentHeader"]["DocumentIdentification"], - expected_doc_id, - ) - - @freeze_time("2020-07-09 10:30:00") - def test_instruction_data(self): - handler = self._get_handler() - result = handler._inbound_instruction() - expected = { - "warehousingInboundInstruction": { - "creationDateTime": "2020-07-09T10:30:00", - "documentStatusCode": "ORIGINAL", - "documentActionCode": "ADD", - "warehousingInboundInstructionIdentification": { - "entityIdentification": self.delivery_order.name, - }, - "logisticServicesBuyer": {"gln": "0000000000002"}, - "logisticServicesSeller": {"gln": "0000000000001"}, - "warehousingInboundInstructionShipment": [ - { - "shipmentIdentification": { - "additionalShipmentIdentification": { - "@attrs": { - # fmt: off - "additionalShipmentIdentificationTypeCode": - "GOODS_RECEIVER_ASSIGNED" - # fmt: on - }, - "@value": self.delivery_order.name, - } - } - }, - { - "shipper": { - "gln": "0000000000044", - "additionalPartyIdentification": { - "@attrs": { - # fmt: off - "additionalPartyIdentificationTypeCode": - "BUYER_ASSIGNED_IDENTIFIER_FOR_A_PARTY" - # fmt: on - }, - "@value": "CARRIER#1", - }, - } - }, - {"receiver": {"gln": "0000000000001"}}, - { - "packageTotal": { - "packageTypeCode": "AF", - "totalPackageQuantity": "2", - "totalGrossWeight": { - "@value": self.delivery_order.weight, - "@attrs": {"measurementUnitCode": "KGM"}, - }, - } - }, - {"warehousingReceiptTypeCode": "REGULAR_RECEIPT"}, - { - "plannedReceipt": { - "logisticEventDateTime": {"date": "2020-07-12"} - } - }, - { - "warehousingInboundInstructionShipmentItem": { - "lineItemNumber": 1, - "transactionalTradeItem": {"gtin": "1" * 14}, - "plannedReceiptQuantity": { - "@value": 300.0, - "@attrs": {"measurementUnitCode": "KGM"}, - }, - } - }, - { - "warehousingInboundInstructionShipmentItem": { - "lineItemNumber": 2, - "transactionalTradeItem": {"gtin": "2" * 14}, - "plannedReceiptQuantity": { - "@value": 200.0, - "@attrs": {"measurementUnitCode": "KGM"}, - }, - } - }, - { - "warehousingInboundInstructionShipmentItem": { - "lineItemNumber": 3, - "transactionalTradeItem": {"gtin": "3" * 14}, - "plannedReceiptQuantity": { - "@value": 100.0, - "@attrs": {"measurementUnitCode": "KGM"}, - }, - } - }, - ], - } - } - self.assertEqual(result, expected) - - @freeze_time("2020-07-09 10:30:00") - def test_xml(self): - handler = self._get_handler() - result = handler.generate_xml() - self._dev_write_example_file(__file__, "inbound_instruction.xml", result) - self.assertFalse(handler.validate_schema(result, raise_on_fail=True)) - self.assertXmlDocument(result) - # self.assertXpathsExist(root, paths) - # self.assertXmlEquivalentOutputs(self.flatten(result), self.flatten(expected)) diff --git a/edi_gs1_stock/tests/common.py b/edi_gs1_stock/tests/common.py new file mode 100644 index 000000000..0a9aaf28b --- /dev/null +++ b/edi_gs1_stock/tests/common.py @@ -0,0 +1,74 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from uuid import uuid4 + +from odoo import fields +from odoo.tests.common import Form + +from odoo.addons.edi_gs1.tests.common import BaseTestCase + + +class DeliveryMixin(object): + @classmethod + def _create_purchase_order(cls, values, view=None): + """Create a purchase order + + :return: purchase order + """ + po = Form(cls.env["purchase.order"], view=view) + po.partner_ref = str(uuid4())[:6] + po.date_planned = fields.Date.today() + for k, v in values.items(): + setattr(po, k, v) + return po.save() + + @classmethod + def _create_purchase_order_line(cls, purchase_order, view=None, **kw): + """ + Create a purchase order line for give order + :return: line + """ + values = {} + values.update(kw) + po = Form(purchase_order, view=view) + with po.order_line.new() as po_line: + for k, v in values.items(): + setattr(po_line, k, v) + return po.save() + + +class ShipmentTestCaseBase(BaseTestCase, DeliveryMixin): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) + + @classmethod + def _setup_order(cls): + cls.product_a = cls.env.ref("product.product_product_4") + cls.product_a.barcode = "1" * 14 + cls.product_b = cls.env.ref("product.product_product_4b") + cls.product_b.barcode = "2" * 14 + cls.product_c = cls.env.ref("product.product_product_4c") + cls.product_c.barcode = "3" * 14 + cls.purchase = cls._create_purchase_order( + { + "partner_id": cls.env.ref("base.res_partner_10"), + "date_planned": "2020-07-12", + } + ) + lines = [ + {"product_id": cls.product_a, "product_qty": 300}, + {"product_id": cls.product_b, "product_qty": 200}, + {"product_id": cls.product_c, "product_qty": 100}, + ] + for line in lines: + cls._create_purchase_order_line(cls.purchase, **line) + + cls.purchase.button_approve() + cls.delivery = cls.purchase.picking_ids[0] + cls.carrier = cls.env.ref("base.res_partner_4") + cls.carrier.gln_code = "123".zfill(13) + cls.carrier.ref = "CARRIER#1" diff --git a/edi_gs1_stock/tests/test_inbound_instruction.py b/edi_gs1_stock/tests/test_inbound_instruction.py index 9313bf74c..784208654 100644 --- a/edi_gs1_stock/tests/test_inbound_instruction.py +++ b/edi_gs1_stock/tests/test_inbound_instruction.py @@ -2,82 +2,12 @@ # @author: Simone Orsi # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -import base64 -from uuid import uuid4 - from freezegun import freeze_time -from odoo import fields -from odoo.tests.common import Form - -from odoo.addons.edi_gs1.tests.common import BaseTestCase - - -class DeliveryMixin(object): - @classmethod - def _create_purchase_order(cls, values, view=None): - """Create a purchase order - - :return: purchase order - """ - po = Form(cls.env["purchase.order"], view=view) - po.partner_ref = str(uuid4())[:6] - po.date_planned = fields.Date.today() - for k, v in values.items(): - setattr(po, k, v) - return po.save() - - @classmethod - def _create_purchase_order_line(cls, purchase_order, view=None, **kw): - """ - Create a purchase order line for give order - :return: line - """ - values = {} - values.update(kw) - po = Form(purchase_order, view=view) - with po.order_line.new() as po_line: - for k, v in values.items(): - setattr(po_line, k, v) - return po.save() - - -class InboundInstructionTestCaseBase(BaseTestCase, DeliveryMixin): - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) - - @classmethod - def _setup_order(cls): - cls.product_a = cls.env.ref("product.product_product_4") - cls.product_a.barcode = "1" * 14 - cls.product_b = cls.env.ref("product.product_product_4b") - cls.product_b.barcode = "2" * 14 - cls.product_c = cls.env.ref("product.product_product_4c") - cls.product_c.barcode = "3" * 14 - cls.purchase = cls._create_purchase_order( - { - "partner_id": cls.env.ref("base.res_partner_10"), - "date_planned": "2020-07-12", - } - ) - lines = [ - {"product_id": cls.product_a, "product_qty": 300}, - {"product_id": cls.product_b, "product_qty": 200}, - {"product_id": cls.product_c, "product_qty": 100}, - ] - for line in lines: - cls._create_purchase_order_line(cls.purchase, **line) - - cls.purchase.button_approve() - cls.delivery = cls.purchase.picking_ids[0] - cls.carrier = cls.env.ref("base.res_partner_4") - cls.carrier.gln_code = "123".zfill(13) - cls.carrier.ref = "CARRIER#1" +from .common import ShipmentTestCaseBase -class InboundInstructionTestCase(InboundInstructionTestCaseBase): +class InboundInstructionTestCase(ShipmentTestCaseBase): @classmethod def setUpClass(cls): super().setUpClass() @@ -175,7 +105,7 @@ def test_info_provider_data(self): { "lineItemNumber": 1, "transactionalTradeItem": {"gtin": "1" * 14}, - "plannedReceiptQuantity": { + "_plannedQty": { "value": 300.0, "attrs": {"measurementUnitCode": "KGM"}, }, @@ -183,7 +113,7 @@ def test_info_provider_data(self): { "lineItemNumber": 2, "transactionalTradeItem": {"gtin": "2" * 14}, - "plannedReceiptQuantity": { + "_plannedQty": { "value": 200.0, "attrs": {"measurementUnitCode": "KGM"}, }, @@ -191,7 +121,7 @@ def test_info_provider_data(self): { "lineItemNumber": 3, "transactionalTradeItem": {"gtin": "3" * 14}, - "plannedReceiptQuantity": { + "_plannedQty": { "value": 100.0, "attrs": {"measurementUnitCode": "KGM"}, }, @@ -209,7 +139,7 @@ def test_xml(self): record = self.delivery.with_context( edi_exchange_send=False ).action_send_wh_inbound_instruction() - file_content = base64.b64decode(record.exchange_file).decode().strip() + file_content = record._get_file_content() # FIXME: do proper validation self.assertTrue( file_content.startswith(" +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from freezegun import freeze_time + +from .common import ShipmentTestCaseBase + + +class OutboundInstructionTestCase(ShipmentTestCaseBase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls._setup_order() + cls.exc_type = cls.env.ref( + "edi_gs1_stock.edi_exchange_type_outbound_instruction" + ) + cls.exc_tmpl = cls.env.ref( + "edi_gs1_stock.edi_exchange_template_outbound_instruction" + ) + vals = { + "model": cls.delivery._name, + "res_id": cls.delivery.id, + "type_id": cls.exc_type.id, + } + cls.record = cls.backend.create_record(cls.exc_type.code, vals) + + def test_get_template(self): + template = self.backend._get_output_template(self.record) + self.assertEqual(template, self.exc_tmpl) + self.assertEqual( + template.template_id.key, "edi_gs1_stock.edi_exchange_outbound_instruction" + ) + + def test_render_values(self): + values = self.exc_tmpl._get_render_values(self.record) + expected = [ + ("sender", self.backend.lsc_partner_id), + ("receiver", self.backend.lsp_partner_id), + ("ls_buyer", self.backend.lsc_partner_id), + ("ls_seller", self.backend.lsp_partner_id), + ("buyer", self.backend.lsc_partner_id), + ("seller", self.purchase.partner_id), + ( + "shipper", + { + "gln_code": "0000000000000", + "name": "NO_CARRIER_SPECIFIED", + "ref": None, + }, + ), + ] + for k, v in expected: + self.assertEqual(values[k], v) + + # Detailed test below + self.assertTrue(values["info"]) + + def test_info_provider_bad_work_ctx(self): + with self.assertRaises(AttributeError) as err: + self.exc_tmpl._get_info_provider(self.record) + self.assertEqual( + str(err.exception), "`sender` is required for this component!" + ) + + @freeze_time("2020-07-09 10:30:00") + def test_info_provider_data(self): + values = self.exc_tmpl._get_render_values(self.record, shipper=self.carrier) + provider = self.exc_tmpl._get_info_provider(self.record, work_ctx=values) + expected_shipment = { + "shipmentIdentification": { + "additionalShipmentIdentification": { + "attrs": { + # fmt: off + "additionalShipmentIdentificationTypeCode": + "GOODS_RECEIVER_ASSIGNED" + # fmt: on + }, + "value": self.delivery.name, + } + }, + "shipper": { + "gln_code": "0000000000123", + "additionalPartyIdentification": { + "attrs": { + # fmt: off + "additionalPartyIdentificationTypeCode": + "BUYER_ASSIGNED_IDENTIFIER_FOR_A_PARTY" + # fmt: on + }, + "value": "CARRIER#1", + }, + }, + "packageTotal": { + "packageTypeCode": "AF", + "totalPackageQuantity": "2", + "totalGrossWeight": { + "value": self.delivery.weight, + "attrs": {"measurementUnitCode": "KGM"}, + }, + }, + "warehousingDespatchTypeCode": "WAREHOUSE_SHIPMENT", + "plannedDespatch": {"logisticEventDateTime": {"date": "2020-07-12"}}, + "_shipment_items": [ + { + "lineItemNumber": 1, + "transactionalTradeItem": {"gtin": "1" * 14}, + "_plannedQty": { + "value": 300.0, + "attrs": {"measurementUnitCode": "KGM"}, + }, + }, + { + "lineItemNumber": 2, + "transactionalTradeItem": {"gtin": "2" * 14}, + "_plannedQty": { + "value": 200.0, + "attrs": {"measurementUnitCode": "KGM"}, + }, + }, + { + "lineItemNumber": 3, + "transactionalTradeItem": {"gtin": "3" * 14}, + "_plannedQty": { + "value": 100.0, + "attrs": {"measurementUnitCode": "KGM"}, + }, + }, + ], + } + info = provider.generate_info().warehousingOutboundInstruction + for k, v in expected_shipment.items(): + self.assertEqual( + info.warehousingOutboundInstructionShipment[k], v, f"{k} does not match" + ) + + @freeze_time("2020-07-09 10:30:00") + def test_xml(self): + record = self.delivery.with_context( + edi_exchange_send=False + ).action_send_wh_outbound_instruction() + file_content = record._get_file_content() + # FIXME: do proper validation + self.assertTrue( + file_content.startswith(" - +
      From fbe8aaba01bf3022a5986383e5c57029148dc3eb Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Thu, 3 Dec 2020 11:08:55 +0100 Subject: [PATCH 40/63] edi_gs1_stock: add xml validation config --- edi_gs1_stock/data/edi_exchange_type.xml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/edi_gs1_stock/data/edi_exchange_type.xml b/edi_gs1_stock/data/edi_exchange_type.xml index 4cbdf9eef..666da4a2d 100644 --- a/edi_gs1_stock/data/edi_exchange_type.xml +++ b/edi_gs1_stock/data/edi_exchange_type.xml @@ -8,6 +8,13 @@ {record_name}-{type.code}-{dt} xml + +components: + validate: + usage: edi.xml + work_ctx: + schema_path: edi_gs1:static/schemas/gs1/ecom/WarehousingInboundInstruction.xsd + warehousingInboundInstruction output {record_name}-{type.code}-{dt} @@ -20,16 +21,28 @@ components: schema_path: edi_gs1:static/schemas/gs1/ecom/WarehousingInboundInstruction.xsd - - + +components: + validate: + usage: edi.xml + work_ctx: + schema_path: edi_gs1:static/schemas/gs1/ecom/WarehousingInboundNotification.xsd + process: + usage: gs1.process.warehousingInboundNotification + work_ctx: + schema_path: edi_gs1:static/schemas/gs1/ecom/WarehousingInboundNotification.xsd + + + GS1 Outbound Instruction diff --git a/edi_gs1_stock/tests/examples/WarehousingInboundInstruction.orig.xml b/edi_gs1_stock/tests/examples/WarehousingInboundInstruction.orig.xml deleted file mode 100644 index b9c7ec141..000000000 --- a/edi_gs1_stock/tests/examples/WarehousingInboundInstruction.orig.xml +++ /dev/null @@ -1,114 +0,0 @@ - - - - 1.0 - - - - John Doe - John_Doe@purchasing.XYZretailer.com - +1-212-555-1213 - +1-212-555-2122 - Buyer - - - - - - Mary Smith - Mary_Smith@widgets.com - +1-312-555-1214 - +1-312-555-2125 - Seller - - - - GS1 - 3.4 - 100002 - - false - 2006-01-10T12:00:01.000-05:00 - - - - 2012-07-11T12:15:00.000-05:00 - ORIGINAL - ADD - - INSREC00123 - - - 2222222000004 - - - 3333333000001 - - - - 11111110000834053 - - - 1111111000007 - - - 3333333000018 - - - 311111110000123455 - AF - - - 311111110000123554 - AF - - - AF - 2 - - PRIORITY_RECEIPT - - - 2012-07-12 - 10:00:00 - 2012-07-12 - 11:00:00 - - - - - LENMAN - - 12345 - DRIVERS_LICENSE - - - - 31 - 11-BB-AA - - - - 1 - - 02222222003333 - - - 311111110000123455 - 20 - - - 311111110000123554 - 20 - - 40 - - - - diff --git a/edi_gs1_stock/tests/examples/test.inbound_instruction.xml b/edi_gs1_stock/tests/examples/test.inbound_instruction.xml deleted file mode 100644 index 6dedaccec..000000000 --- a/edi_gs1_stock/tests/examples/test.inbound_instruction.xml +++ /dev/null @@ -1,103 +0,0 @@ - - - 1.0 - - - - My Company (San Francisco) - info@yourcompany.example.com - +1 (650) 691-3277 - Buyer - - - - - - Gemini Furniture - gemini.furniture39@example.com - (941)-284-4875 - Seller - - - - GS1 - 3.4 - WH-IN-TEST-01 - - false - 2020-07-09T10:30:00 - - - - 2020-07-09T10:30:00 - ORIGINAL - ADD - - WH/IN/00014 - - - 0000000000002 - - - 0000000000001 - - - - WH/IN/00014 - - - 0000000000044 - CARRIER#1 - - - 0000000000001 - - - AF - 2 - 6.0 - - REGULAR_RECEIPT - - - 2020-07-12 - - - - 1 - - 11111111111111 - - 300.0 - - - 2 - - 22222222222222 - - 200.0 - - - 3 - - 33333333333333 - - 100.0 - - - - diff --git a/edi_gs1_stock/tests/test_inbound_notification.py b/edi_gs1_stock/tests/test_inbound_notification.py new file mode 100644 index 000000000..9f70381ea --- /dev/null +++ b/edi_gs1_stock/tests/test_inbound_notification.py @@ -0,0 +1,110 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from freezegun import freeze_time + +from .common import ShipmentTestCaseBase + + +class InboundInstructionTestCase(ShipmentTestCaseBase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls._setup_order() + cls.exc_type = cls.env.ref( + "edi_gs1_stock.edi_exchange_type_inbound_notification" + ) + cls.exc_tmpl = cls.env.ref( + "edi_gs1_stock.edi_exchange_template_inbound_notification" + ) + vals = { + "model": cls.delivery._name, + "res_id": cls.delivery.id, + "type_id": cls.exc_type.id, + "edi_exchange_state": "input_received", + # TODO: set xml value + } + cls.record = cls.backend.create_record(cls.exc_type.code, vals) + + @freeze_time("2020-07-09 10:30:00") + def test_info_provider_data(self): + values = self.exc_tmpl._get_render_values(self.record, shipper=self.carrier) + provider = self.exc_tmpl._get_info_provider(self.record, work_ctx=values) + expected_shipment = { + "shipmentIdentification": { + "additionalShipmentIdentification": { + "attrs": { + # fmt: off + "additionalShipmentIdentificationTypeCode": + "GOODS_RECEIVER_ASSIGNED" + # fmt: on + }, + "value": self.delivery.name, + } + }, + "shipper": { + "gln_code": "0000000000123", + "additionalPartyIdentification": { + "attrs": { + # fmt: off + "additionalPartyIdentificationTypeCode": + "BUYER_ASSIGNED_IDENTIFIER_FOR_A_PARTY" + # fmt: on + }, + "value": "CARRIER#1", + }, + }, + "packageTotal": { + "packageTypeCode": "AF", + "totalPackageQuantity": "2", + "totalGrossWeight": { + "value": self.delivery.weight, + "attrs": {"measurementUnitCode": "KGM"}, + }, + }, + "warehousingReceiptTypeCode": "REGULAR_RECEIPT", + "plannedReceipt": {"logisticEventDateTime": {"date": "2020-07-12"}}, + "_shipment_items": [ + { + "lineItemNumber": 1, + "transactionalTradeItem": {"gtin": "1" * 14}, + "_plannedQty": { + "value": 300.0, + "attrs": {"measurementUnitCode": "KGM"}, + }, + }, + { + "lineItemNumber": 2, + "transactionalTradeItem": {"gtin": "2" * 14}, + "_plannedQty": { + "value": 200.0, + "attrs": {"measurementUnitCode": "KGM"}, + }, + }, + { + "lineItemNumber": 3, + "transactionalTradeItem": {"gtin": "3" * 14}, + "_plannedQty": { + "value": 100.0, + "attrs": {"measurementUnitCode": "KGM"}, + }, + }, + ], + } + info = provider.generate_info().warehousingInboundnotification + for k, v in expected_shipment.items(): + self.assertEqual( + info.warehousingInboundnotificationShipment[k], v, f"{k} does not match" + ) + + @freeze_time("2020-07-09 10:30:00") + def test_xml(self): + record = self.delivery.with_context( + edi_exchange_send=False + ).action_send_wh_inbound_notification() + file_content = record._get_file_content() + # FIXME: do proper validation + self.assertTrue( + file_content.startswith(" Date: Wed, 10 Mar 2021 08:21:39 +0100 Subject: [PATCH 44/63] edi_gs1_stock: change inbound instruction component name --- edi_gs1_stock/components/inbound_instruction.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/edi_gs1_stock/components/inbound_instruction.py b/edi_gs1_stock/components/inbound_instruction.py index fb6757f65..383f4acc2 100644 --- a/edi_gs1_stock/components/inbound_instruction.py +++ b/edi_gs1_stock/components/inbound_instruction.py @@ -17,15 +17,9 @@ class GS1InboundInstructionMessage(Component): Logistic Services Provider (LSP) that goods will be arriving. - - Warehousing Inbound Notification Message Definition - The Warehousing Inbound Notification message - enables a Logistic Services Provider (LSP) to inform - his Logistic Services Client (LSC) - on the status of goods received on behalf of the client. """ - _name = "gs1.output.inboundinstruction" + _name = "gs1.output.inbound.instruction" _inherit = [ "edi.gs1.output.shipment.mixin", ] From e3e1463b5b2db3841a5a0188a0677f09857e4f12 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Wed, 10 Mar 2021 08:22:04 +0100 Subject: [PATCH 45/63] edi_gs1_stock: fix inbound notif test class name --- edi_gs1_stock/tests/test_inbound_notification.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edi_gs1_stock/tests/test_inbound_notification.py b/edi_gs1_stock/tests/test_inbound_notification.py index 9f70381ea..4e3972784 100644 --- a/edi_gs1_stock/tests/test_inbound_notification.py +++ b/edi_gs1_stock/tests/test_inbound_notification.py @@ -7,7 +7,7 @@ from .common import ShipmentTestCaseBase -class InboundInstructionTestCase(ShipmentTestCaseBase): +class InboundNotificationTestCase(ShipmentTestCaseBase): @classmethod def setUpClass(cls): super().setUpClass() From 9db97f820b2c7269ed2f1a6c5ef71bb1237e101c Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Wed, 10 Mar 2021 08:24:26 +0100 Subject: [PATCH 46/63] edi_gs1_stock: draft notification components --- .../components/inbound_notification.py | 31 +++++++++++++++++++ .../components/outbound_notification.py | 31 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 edi_gs1_stock/components/inbound_notification.py create mode 100644 edi_gs1_stock/components/outbound_notification.py diff --git a/edi_gs1_stock/components/inbound_notification.py b/edi_gs1_stock/components/inbound_notification.py new file mode 100644 index 000000000..64d0da830 --- /dev/null +++ b/edi_gs1_stock/components/inbound_notification.py @@ -0,0 +1,31 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.addons.component.core import Component + + +class GS1InboundNotificationMessage(Component): + """Process data for Inbound Notification. + + The Warehousing Inbound Notification message + enables a + + Logistic Services Provider (LSP) + + to inform his + + Logistic Services Client (LSC) + + on the status of goods received on behalf of the client. + """ + + _name = "gs1.process.inbound.notification" + _inherit = [ + "edi.gs1.input.shipment.mixin", + ] + _usage = "gs1.process.warehousingInboundNotification" + + def _process_data(self, data): + # TODO + raise NotImplementedError() diff --git a/edi_gs1_stock/components/outbound_notification.py b/edi_gs1_stock/components/outbound_notification.py new file mode 100644 index 000000000..610019071 --- /dev/null +++ b/edi_gs1_stock/components/outbound_notification.py @@ -0,0 +1,31 @@ +# Copyright 2020 ACSONE +# @author: Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.addons.component.core import Component + + +class GS1OutboundNotificationMessage(Component): + """Process data for Outbound Notification. + + The Warehousing Outbound Notification message + enables a + + Logistic Services Provider (LSP) + + to inform his + + Logistic Services Client (LSC) + + on the status of goods received on behalf of the client. + """ + + _name = "gs1.process.outbound.notification" + _inherit = [ + "edi.gs1.input.shipment.mixin", + ] + _usage = "gs1.process.warehousingOutboundNotification" + + def _process_data(self, data): + # TODO + raise NotImplementedError() From fab25726f1135a87ea57eb6454c3e9da823c8f64 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Mon, 15 Mar 2021 10:17:16 +0100 Subject: [PATCH 47/63] edi_gs1_stock: TEMP disable tests for inbound notification --- .../tests/test_inbound_notification.py | 205 +++++++++--------- 1 file changed, 104 insertions(+), 101 deletions(-) diff --git a/edi_gs1_stock/tests/test_inbound_notification.py b/edi_gs1_stock/tests/test_inbound_notification.py index 4e3972784..2fd3fcf9d 100644 --- a/edi_gs1_stock/tests/test_inbound_notification.py +++ b/edi_gs1_stock/tests/test_inbound_notification.py @@ -2,109 +2,112 @@ # @author: Simone Orsi # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from freezegun import freeze_time +# from freezegun import freeze_time -from .common import ShipmentTestCaseBase +# from .common import ShipmentTestCaseBase +# TODO: add inbound notification handling -class InboundNotificationTestCase(ShipmentTestCaseBase): - @classmethod - def setUpClass(cls): - super().setUpClass() - cls._setup_order() - cls.exc_type = cls.env.ref( - "edi_gs1_stock.edi_exchange_type_inbound_notification" - ) - cls.exc_tmpl = cls.env.ref( - "edi_gs1_stock.edi_exchange_template_inbound_notification" - ) - vals = { - "model": cls.delivery._name, - "res_id": cls.delivery.id, - "type_id": cls.exc_type.id, - "edi_exchange_state": "input_received", - # TODO: set xml value - } - cls.record = cls.backend.create_record(cls.exc_type.code, vals) - @freeze_time("2020-07-09 10:30:00") - def test_info_provider_data(self): - values = self.exc_tmpl._get_render_values(self.record, shipper=self.carrier) - provider = self.exc_tmpl._get_info_provider(self.record, work_ctx=values) - expected_shipment = { - "shipmentIdentification": { - "additionalShipmentIdentification": { - "attrs": { - # fmt: off - "additionalShipmentIdentificationTypeCode": - "GOODS_RECEIVER_ASSIGNED" - # fmt: on - }, - "value": self.delivery.name, - } - }, - "shipper": { - "gln_code": "0000000000123", - "additionalPartyIdentification": { - "attrs": { - # fmt: off - "additionalPartyIdentificationTypeCode": - "BUYER_ASSIGNED_IDENTIFIER_FOR_A_PARTY" - # fmt: on - }, - "value": "CARRIER#1", - }, - }, - "packageTotal": { - "packageTypeCode": "AF", - "totalPackageQuantity": "2", - "totalGrossWeight": { - "value": self.delivery.weight, - "attrs": {"measurementUnitCode": "KGM"}, - }, - }, - "warehousingReceiptTypeCode": "REGULAR_RECEIPT", - "plannedReceipt": {"logisticEventDateTime": {"date": "2020-07-12"}}, - "_shipment_items": [ - { - "lineItemNumber": 1, - "transactionalTradeItem": {"gtin": "1" * 14}, - "_plannedQty": { - "value": 300.0, - "attrs": {"measurementUnitCode": "KGM"}, - }, - }, - { - "lineItemNumber": 2, - "transactionalTradeItem": {"gtin": "2" * 14}, - "_plannedQty": { - "value": 200.0, - "attrs": {"measurementUnitCode": "KGM"}, - }, - }, - { - "lineItemNumber": 3, - "transactionalTradeItem": {"gtin": "3" * 14}, - "_plannedQty": { - "value": 100.0, - "attrs": {"measurementUnitCode": "KGM"}, - }, - }, - ], - } - info = provider.generate_info().warehousingInboundnotification - for k, v in expected_shipment.items(): - self.assertEqual( - info.warehousingInboundnotificationShipment[k], v, f"{k} does not match" - ) +# class InboundNotificationTestCase(ShipmentTestCaseBase): +# @classmethod +# def setUpClass(cls): +# super().setUpClass() +# cls._setup_order() +# cls.exc_type = cls.env.ref( +# "edi_gs1_stock.edi_exchange_type_inbound_notification" +# ) +# cls.exc_tmpl = cls.env.ref( +# "edi_gs1_stock.edi_exchange_template_inbound_notification" +# ) +# vals = { +# "model": cls.delivery._name, +# "res_id": cls.delivery.id, +# "type_id": cls.exc_type.id, +# "edi_exchange_state": "input_received", +# # TODO: set xml value +# } +# cls.record = cls.backend.create_record(cls.exc_type.code, vals) - @freeze_time("2020-07-09 10:30:00") - def test_xml(self): - record = self.delivery.with_context( - edi_exchange_send=False - ).action_send_wh_inbound_notification() - file_content = record._get_file_content() - # FIXME: do proper validation - self.assertTrue( - file_content.startswith(" Date: Mon, 15 Mar 2021 10:01:21 +0100 Subject: [PATCH 48/63] edi_gs1: adapt tests to core changes --- edi_gs1_stock/tests/test_inbound_instruction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edi_gs1_stock/tests/test_inbound_instruction.py b/edi_gs1_stock/tests/test_inbound_instruction.py index a4958e7d8..2f28f2151 100644 --- a/edi_gs1_stock/tests/test_inbound_instruction.py +++ b/edi_gs1_stock/tests/test_inbound_instruction.py @@ -29,7 +29,7 @@ def setUpClass(cls): cls.record = cls.backend.create_record(cls.exc_type.code, vals) def test_get_template(self): - template = self.backend._get_output_template(self.record) + template = self.backend._get_template(self.record, "output", "generate") self.assertEqual(template, self.exc_tmpl) self.assertEqual( template.template_id.key, "edi_gs1_stock.edi_exchange_inbound_instruction" From af6ed66e36d78b42ef1bee788434a2781ffd2e39 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Mon, 15 Mar 2021 10:16:29 +0100 Subject: [PATCH 49/63] edi_gs1_stock: fix inbound/outbound instruction tests --- edi_gs1_stock/tests/test_inbound_instruction.py | 5 ++--- edi_gs1_stock/tests/test_outbound_instruction.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/edi_gs1_stock/tests/test_inbound_instruction.py b/edi_gs1_stock/tests/test_inbound_instruction.py index 2f28f2151..07bc3521e 100644 --- a/edi_gs1_stock/tests/test_inbound_instruction.py +++ b/edi_gs1_stock/tests/test_inbound_instruction.py @@ -8,9 +8,6 @@ class InboundInstructionTestCase(ShipmentTestCaseBase): - - _schema_path = "edi_gs1:static/schemas/gs1/ecom/WarehousingInboundInstruction.xsd" - @classmethod def setUpClass(cls): super().setUpClass() @@ -137,6 +134,8 @@ def test_info_provider_data(self): info.warehousingInboundInstructionShipment[k], v, f"{k} does not match" ) + _schema_path = "edi_gs1:static/schemas/gs1/ecom/WarehousingInboundInstruction.xsd" + @freeze_time("2020-07-09 10:30:00") def test_xml(self): record = self.delivery.with_context( diff --git a/edi_gs1_stock/tests/test_outbound_instruction.py b/edi_gs1_stock/tests/test_outbound_instruction.py index b38abbb5d..1a5124b91 100644 --- a/edi_gs1_stock/tests/test_outbound_instruction.py +++ b/edi_gs1_stock/tests/test_outbound_instruction.py @@ -29,7 +29,7 @@ def setUpClass(cls): cls.record = cls.backend.create_record(cls.exc_type.code, vals) def test_get_template(self): - template = self.backend._get_output_template(self.record) + template = self.backend._get_template(self.record, "output", "generate") self.assertEqual(template, self.exc_tmpl) self.assertEqual( template.template_id.key, "edi_gs1_stock.edi_exchange_outbound_instruction" From fcf5c4e742c2e8a3a42a855255b084e2ecf2f7e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Honor=C3=A9?= Date: Wed, 17 Mar 2021 11:44:24 +0100 Subject: [PATCH 50/63] [UPD] edi_gs1_stock: update outbound instruction qweb to add contact details + simulate a fake res.partner record into template --- .../components/outbound_instruction.py | 2 +- .../inbound_instruction_output_template.xml | 5 ++++- .../outbound_instruction_output_template.xml | 16 ++++++++++----- .../outbound_instruction_qweb_template.xml | 20 ++++++++++--------- .../tests/test_inbound_instruction.py | 9 +-------- .../tests/test_outbound_instruction.py | 17 +++++----------- 6 files changed, 33 insertions(+), 36 deletions(-) diff --git a/edi_gs1_stock/components/outbound_instruction.py b/edi_gs1_stock/components/outbound_instruction.py index b4e66342d..0f73b7e8e 100644 --- a/edi_gs1_stock/components/outbound_instruction.py +++ b/edi_gs1_stock/components/outbound_instruction.py @@ -25,7 +25,7 @@ class GS1OutboundInstructionMessage(Component): on the status of goods received on behalf of the client. """ - _name = "gs1.output.outboundinstruction" + _name = "gs1.output.outbound.instruction" _inherit = [ "edi.gs1.output.shipment.mixin", ] diff --git a/edi_gs1_stock/data/inbound_instruction_output_template.xml b/edi_gs1_stock/data/inbound_instruction_output_template.xml index b7e196d3e..f3621b36a 100644 --- a/edi_gs1_stock/data/inbound_instruction_output_template.xml +++ b/edi_gs1_stock/data/inbound_instruction_output_template.xml @@ -19,7 +19,10 @@ logistic_service_buyer = sender buyer = sender seller = exchange_record.record.purchase_id.partner_id # TODO: get a default carrier somehow? -shipper = DotDict(dict(name="NO_CARRIER_SPECIFIED", gln_code="0" * 13, ref=None)) +shipper = env['res.partner'].new({ + 'name': "NO_CARRIER_SPECIFIED", + 'gln_code': "0" * 13, +}) _result = { "sender": sender, diff --git a/edi_gs1_stock/data/outbound_instruction_output_template.xml b/edi_gs1_stock/data/outbound_instruction_output_template.xml index 22ca57ad6..462f6a3ae 100644 --- a/edi_gs1_stock/data/outbound_instruction_output_template.xml +++ b/edi_gs1_stock/data/outbound_instruction_output_template.xml @@ -12,14 +12,19 @@ sender = backend.lsc_partner_id -receiver = backend.lsp_partner_id -logistic_service_seller = receiver +receiver = exchange_record.record.partner_id +shipto = exchange_record.record.partner_id +logistic_service_seller = backend.lsp_partner_id logistic_service_buyer = sender # FIXME: this is probably wrong or not always true -buyer = sender -seller = exchange_record.record.purchase_id.partner_id +buyer = exchange_record.record.partner_id +# TODO: Pick the SO partner customer. +seller = backend.lsc_partner_id # TODO: get a default carrier somehow? -shipper = DotDict(dict(name="NO_CARRIER_SPECIFIED", gln_code="0" * 13, ref=None)) +shipper = env['res.partner'].new({ + 'name': "NO_CARRIER_SPECIFIED", + 'gln_code': "0" * 13, +}) _result = { "sender": sender, @@ -29,6 +34,7 @@ _result = { "buyer": buyer, "seller": seller, "shipper": shipper, + "shipto": shipto, } provider = get_info_provider(exchange_record, work_ctx=_result) diff --git a/edi_gs1_stock/data/outbound_instruction_qweb_template.xml b/edi_gs1_stock/data/outbound_instruction_qweb_template.xml index 4befc7046..b3d2d46e5 100644 --- a/edi_gs1_stock/data/outbound_instruction_qweb_template.xml +++ b/edi_gs1_stock/data/outbound_instruction_qweb_template.xml @@ -48,18 +48,20 @@ - 0000000000044 - CARRIER#1 + + + - - 0000000000001 + + + - + + + + + ??? ??? diff --git a/edi_gs1_stock/tests/test_inbound_instruction.py b/edi_gs1_stock/tests/test_inbound_instruction.py index 07bc3521e..f5a8e61f7 100644 --- a/edi_gs1_stock/tests/test_inbound_instruction.py +++ b/edi_gs1_stock/tests/test_inbound_instruction.py @@ -41,20 +41,13 @@ def test_render_values(self): ("ls_seller", self.backend.lsp_partner_id), ("buyer", self.backend.lsc_partner_id), ("seller", self.purchase.partner_id), - ( - "shipper", - { - "gln_code": "0000000000000", - "name": "NO_CARRIER_SPECIFIED", - "ref": None, - }, - ), ] for k, v in expected: self.assertEqual(values[k], v) # Detailed test below self.assertTrue(values["info"]) + self.assertTrue(values["shipper"]) def test_info_provider_bad_work_ctx(self): with self.assertRaises(AttributeError) as err: diff --git a/edi_gs1_stock/tests/test_outbound_instruction.py b/edi_gs1_stock/tests/test_outbound_instruction.py index 1a5124b91..77f60dbb2 100644 --- a/edi_gs1_stock/tests/test_outbound_instruction.py +++ b/edi_gs1_stock/tests/test_outbound_instruction.py @@ -39,25 +39,18 @@ def test_render_values(self): values = self.exc_tmpl._get_render_values(self.record) expected = [ ("sender", self.backend.lsc_partner_id), - ("receiver", self.backend.lsp_partner_id), + ("receiver", self.delivery.partner_id), ("ls_buyer", self.backend.lsc_partner_id), ("ls_seller", self.backend.lsp_partner_id), - ("buyer", self.backend.lsc_partner_id), - ("seller", self.purchase.partner_id), - ( - "shipper", - { - "gln_code": "0000000000000", - "name": "NO_CARRIER_SPECIFIED", - "ref": None, - }, - ), + ("buyer", self.delivery.partner_id), + ("seller", self.backend.lsc_partner_id), ] for k, v in expected: - self.assertEqual(values[k], v) + self.assertEqual(values[k], v, f"{k} is wrong") # Detailed test below self.assertTrue(values["info"]) + self.assertTrue(values["shipper"]) def test_info_provider_bad_work_ctx(self): with self.assertRaises(AttributeError) as err: From f0a21086b4dd1155647fb8e0b3a495a50b850663 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Honor=C3=A9?= Date: Wed, 31 Mar 2021 10:45:19 +0200 Subject: [PATCH 51/63] [IMP] edi_gs1_stock: pre-commit --- edi_gs1_stock/components/input_mixin.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/edi_gs1_stock/components/input_mixin.py b/edi_gs1_stock/components/input_mixin.py index cf3f28946..cb666aa44 100644 --- a/edi_gs1_stock/components/input_mixin.py +++ b/edi_gs1_stock/components/input_mixin.py @@ -2,9 +2,6 @@ # @author: Simone Orsi # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import _, exceptions -from odoo.tools import DotDict - from odoo.addons.component.core import AbstractComponent @@ -21,4 +18,3 @@ def _process_data(self, data): import pdb pdb.set_trace() - pass From fe0a88a86e388bba4d4ac04c30687b1af0139c57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Honor=C3=A9?= Date: Wed, 31 Mar 2021 10:48:31 +0200 Subject: [PATCH 52/63] [UPD] edi_gs1_stock: Outbound instruction Update outbound instruction + related components + adapations also in Inbound instruction + adapt tests --- .../components/outbound_instruction.py | 15 ++- edi_gs1_stock/components/output_mixin.py | 52 ++++++---- .../inbound_instruction_qweb_template.xml | 31 +++--- .../outbound_instruction_qweb_template.xml | 94 ++++++++++++++----- edi_gs1_stock/models/stock_picking.py | 4 +- .../tests/test_inbound_instruction.py | 24 +++-- .../tests/test_outbound_instruction.py | 26 +++-- 7 files changed, 171 insertions(+), 75 deletions(-) diff --git a/edi_gs1_stock/components/outbound_instruction.py b/edi_gs1_stock/components/outbound_instruction.py index 0f73b7e8e..6b034ae24 100644 --- a/edi_gs1_stock/components/outbound_instruction.py +++ b/edi_gs1_stock/components/outbound_instruction.py @@ -73,8 +73,13 @@ def _despatch_type_code(self): def _planned_despatch(self): # TODO: better info from? - return { - "logisticEventDateTime": { - "date": self.date_to_string(self.record.scheduled_date) or "", - }, - } + values = {} + if self.record.scheduled_date: + values.update( + { + "logisticEventPeriod": { + "beginDate": self.date_to_string(self.record.scheduled_date), + }, + } + ) + return values diff --git a/edi_gs1_stock/components/output_mixin.py b/edi_gs1_stock/components/output_mixin.py index 86b95b7f4..0b518e7a8 100644 --- a/edi_gs1_stock/components/output_mixin.py +++ b/edi_gs1_stock/components/output_mixin.py @@ -87,23 +87,25 @@ def _shipper(self): return data def _package_total(self): - return DotDict( - { - # TODO: would be nice to have mapping based on product packaging - # but as in some case you simply use the same package types - # for all the shipments, then is up to integrator to provide - # proper values by overriding this method. - # https://www.gs1.se/en/our-standards/Technical-documentation/ - # code-lists/t0137-packaging-type-code/ - # TODO: get generic numbers from picking. - "packageTypeCode": "AF", - "totalPackageQuantity": "2", - "totalGrossWeight": { - "value": self.record.weight, - "attrs": {"measurementUnitCode": "KGM"}, - }, - } - ) + return [ + DotDict( + { + # TODO: would be nice to have mapping based on product packaging + # but as in some case you simply use the same package types + # for all the shipments, then is up to integrator to provide + # proper values by overriding this method. + # https://www.gs1.se/en/our-standards/Technical-documentation/ + # code-lists/t0137-packaging-type-code/ + # TODO: get generic numbers from picking. + "packageTypeCode": "AF", + "totalPackageQuantity": "2", + "totalGrossWeight": { + "value": self.record.weight, + "attrs": {"measurementUnitCode": "KGM"}, + }, + } + ), + ] def _shipment_items(self): res = [] @@ -119,10 +121,15 @@ def _shipment_item(self, item, i=1): # TODO: get it from line uom uom_code = "KGM" # Watch out: order is important for XSD validation! + inventory_fee_tax_status = [ + DotDict(v) for v in self._inventory_duty_fee_tax_status(item) + ] data = DotDict( { "lineItemNumber": i, "transactionalTradeItem": self._shipment_item_trade_item(item), + "note": self._shipment_item_note(item), + "inventoryDutyFeeTaxStatus": inventory_fee_tax_status, } ) avp_list = self._shipment_item_avp_list(item) @@ -138,6 +145,17 @@ def _shipment_item(self, item, i=1): } return data + def _inventory_duty_fee_tax_status(self, item): + """ + + :param item: + :return: list of dict + """ + return [] + + def _shipment_item_note(self, item): + return {} + def _shipment_item_trade_item(self, item): # GTIN is required here. # We assume you set it on product's default code. diff --git a/edi_gs1_stock/data/inbound_instruction_qweb_template.xml b/edi_gs1_stock/data/inbound_instruction_qweb_template.xml index 4f812b1bf..d5ade954a 100644 --- a/edi_gs1_stock/data/inbound_instruction_qweb_template.xml +++ b/edi_gs1_stock/data/inbound_instruction_qweb_template.xml @@ -53,18 +53,20 @@ 0000000000001 - - AF - 2 - 6.0 - + + + AF + 2 + 6.0 + + REGULAR_RECEIPT @@ -82,6 +84,11 @@ 1 + 11111111111111 https://possible.url/to/delivery-document - - AF - 2 - 6.0 - + + + AF + 2 + 6.0 + + 11 WAREHOUSE_SHIPMENT + t-esc="shipment_info.warehousingDespatchTypeCode" + /> PRIORITY_DESPATCH - - - 2020-12-02 - + + + + 1 Maybe use for some long description of the product + t-if="ship_item.note and ship_item.note.value" + t-att="ship_item.note.attrs" + t-esc="ship_item.note.value" + /> 11111111111111 102150001001000010003200010311101101 + + + + + + + + + + + + + + + Date: Fri, 16 Apr 2021 10:59:42 +0200 Subject: [PATCH 53/63] Pre-commit --- edi_gs1_stock/__manifest__.py | 1 + edi_gs1_stock/components/input_mixin.py | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/edi_gs1_stock/__manifest__.py b/edi_gs1_stock/__manifest__.py index 02ca1f8b0..eaa7819d0 100644 --- a/edi_gs1_stock/__manifest__.py +++ b/edi_gs1_stock/__manifest__.py @@ -10,6 +10,7 @@ "version": "13.0.1.0.0", "license": "AGPL-3", "author": "ACSONE,Odoo Community Association (OCA)", + "website": "https://github.com/OCA/edi", "depends": [ "edi_gs1", "stock", diff --git a/edi_gs1_stock/components/input_mixin.py b/edi_gs1_stock/components/input_mixin.py index cb666aa44..83ece263b 100644 --- a/edi_gs1_stock/components/input_mixin.py +++ b/edi_gs1_stock/components/input_mixin.py @@ -15,6 +15,5 @@ class GS1InputShipmentMessageMixin(AbstractComponent): ] def _process_data(self, data): - import pdb - - pdb.set_trace() + # TODO + pass From 9b7537284a91e7a9471310f56f273cee6f0d0a29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Honor=C3=A9?= Date: Fri, 2 Jul 2021 14:25:52 +0200 Subject: [PATCH 54/63] [ADD] edi_gs1_stock: enable Warehouse Outbound Notification --- edi_gs1_stock/components/__init__.py | 2 ++ edi_gs1_stock/data/edi_exchange_type.xml | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/edi_gs1_stock/components/__init__.py b/edi_gs1_stock/components/__init__.py index cf5ea66e0..6f4dbbee2 100644 --- a/edi_gs1_stock/components/__init__.py +++ b/edi_gs1_stock/components/__init__.py @@ -1,3 +1,5 @@ from . import output_mixin +from . import input_mixin from . import inbound_instruction from . import outbound_instruction +from . import outbound_notification diff --git a/edi_gs1_stock/data/edi_exchange_type.xml b/edi_gs1_stock/data/edi_exchange_type.xml index 79d0865e0..8941efd2b 100644 --- a/edi_gs1_stock/data/edi_exchange_type.xml +++ b/edi_gs1_stock/data/edi_exchange_type.xml @@ -63,8 +63,7 @@ components: schema_path: edi_gs1:static/schemas/gs1/ecom/WarehousingOutboundInstruction.xsd - - + +components: + validate: + usage: edi.xml + work_ctx: + schema_path: edi_gs1:static/schemas/gs1/ecom/WarehousingOutboundNotification.xsd + process: + usage: gs1.process.warehousingOutboundNotification + work_ctx: + schema_path: edi_gs1:static/schemas/gs1/ecom/WarehousingOutboundNotification.xsd + + From f304146da211ac72d4521d20d5f2592274ad9ad8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Honor=C3=A9?= Date: Fri, 9 Jul 2021 15:03:24 +0200 Subject: [PATCH 55/63] [FIX] edi: fix due to refactor --- edi_gs1_stock/__manifest__.py | 1 + edi_gs1_stock/models/__init__.py | 2 ++ edi_gs1_stock/models/stock_picking.py | 29 +++++++++------------------ 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/edi_gs1_stock/__manifest__.py b/edi_gs1_stock/__manifest__.py index eaa7819d0..b6a0abd9d 100644 --- a/edi_gs1_stock/__manifest__.py +++ b/edi_gs1_stock/__manifest__.py @@ -19,6 +19,7 @@ "edi_exchange_template", ], "data": [ + "views/res_config_settings.xml", "views/stock_picking_view.xml", "data/edi_exchange_type.xml", "data/inbound_instruction_qweb_template.xml", diff --git a/edi_gs1_stock/models/__init__.py b/edi_gs1_stock/models/__init__.py index ae4c27227..e6c1f2d92 100644 --- a/edi_gs1_stock/models/__init__.py +++ b/edi_gs1_stock/models/__init__.py @@ -1 +1,3 @@ +from . import res_company +from . import res_config_settings from . import stock_picking diff --git a/edi_gs1_stock/models/stock_picking.py b/edi_gs1_stock/models/stock_picking.py index 2fa3d7b96..4fb8c3731 100644 --- a/edi_gs1_stock/models/stock_picking.py +++ b/edi_gs1_stock/models/stock_picking.py @@ -27,23 +27,24 @@ def get_backend_by_delivery(self): # which backend to use. return self.env.ref("edi_gs1.edi_backend_gs1_default") - # TODO: check if sending is required - def send_wh_inbound_instruction(self, send=False): - """Generate an Inbound Instruction for given delivery and send it. - """ + def _common_instruction(self, send, type_code): delivery = self edi_backend = self.get_backend_by_delivery() - type_code = "warehousingInboundInstruction" values = {"model": delivery._name, "res_id": delivery.id} exchange_record = edi_backend.create_record(type_code, values) edi_backend.exchange_generate(exchange_record) - - if "edi_exchange_send" in self.env.context: - send = self.env.context.get("edi_exchange_send") + send = self.env.context.get("edi_exchange_send", send) if send: edi_backend.exchange_send(exchange_record) return exchange_record + # TODO: check if sending is required + def send_wh_inbound_instruction(self, send=False): + """Generate an Inbound Instruction for given delivery and send it. + """ + type_code = "warehousingInboundInstruction" + return self._common_instruction(send, type_code) + def action_send_wh_inbound_instruction(self): # TODO: return action compat dict return self.send_wh_inbound_instruction() @@ -51,18 +52,8 @@ def action_send_wh_inbound_instruction(self): def send_wh_outbound_instruction(self, send=False): """Generate an Outbound Instruction for given delivery and send it. """ - delivery = self - edi_backend = self.get_backend_by_delivery() type_code = "warehousingOutboundInstruction" - values = {"model": delivery._name, "res_id": delivery.id} - exchange_record = edi_backend.create_record(type_code, values) - edi_backend.exchange_generate(exchange_record) - - if "edi_exchange_send" in self.env.context: - send = self.env.context.get("edi_exchange_send") - if send: - edi_backend.exchange_send(exchange_record) - return exchange_record + return self._common_instruction(send, type_code) def action_send_wh_outbound_instruction(self): # TODO: return action compat dict From 877f60623179483fa59e7b47b38dbe0fbeae82a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Honor=C3=A9?= Date: Tue, 18 Oct 2022 14:12:38 +0200 Subject: [PATCH 56/63] [ADD] Add configuration --- edi_gs1_stock/models/res_company.py | 19 +++++++++ edi_gs1_stock/models/res_config_settings.py | 20 ++++++++++ edi_gs1_stock/views/res_config_settings.xml | 43 +++++++++++++++++++++ 3 files changed, 82 insertions(+) create mode 100644 edi_gs1_stock/models/res_company.py create mode 100644 edi_gs1_stock/models/res_config_settings.py create mode 100644 edi_gs1_stock/views/res_config_settings.xml diff --git a/edi_gs1_stock/models/res_company.py b/edi_gs1_stock/models/res_company.py new file mode 100644 index 000000000..c34da4d86 --- /dev/null +++ b/edi_gs1_stock/models/res_company.py @@ -0,0 +1,19 @@ +# Copyright 2022 ACSONE SA/NV () +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from odoo import fields, models + + +class ResCompany(models.Model): + _inherit = 'res.company' + + gs1_main_uom_id = fields.Many2one( + comodel_name="uom.uom", + string="GS1 main UoM", + help="Main UoM for GS1 files" + ) + + gs1_second_uom_id = fields.Many2one( + comodel_name="uom.uom", + string="GS1 second UoM", + help="Second UoM for GS1 files" + ) diff --git a/edi_gs1_stock/models/res_config_settings.py b/edi_gs1_stock/models/res_config_settings.py new file mode 100644 index 000000000..e0c040103 --- /dev/null +++ b/edi_gs1_stock/models/res_config_settings.py @@ -0,0 +1,20 @@ +# Copyright 2022 ACSONE SA/NV () +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = 'res.config.settings' + + gs1_main_uom_id = fields.Many2one( + comodel_name="uom.uom", + help="Main UoM for GS1 files", + related="company_id.gs1_main_uom_id", + readonly=False + ) + gs1_second_uom_id = fields.Many2one( + comodel_name="uom.uom", + help="Second UoM for GS1 files", + related="company_id.gs1_second_uom_id", + readonly=False + ) diff --git a/edi_gs1_stock/views/res_config_settings.xml b/edi_gs1_stock/views/res_config_settings.xml new file mode 100644 index 000000000..047ed2f96 --- /dev/null +++ b/edi_gs1_stock/views/res_config_settings.xml @@ -0,0 +1,43 @@ + + + + + res.config.settings.form (in edi_gs1_stock) + res.config.settings + + + + +

      GS1

      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      From da4cb576c9ee21656d90908671fb9014d8a15e5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Honor=C3=A9?= Date: Wed, 19 Oct 2022 11:54:36 +0200 Subject: [PATCH 57/63] [INT] pre-commit --- edi_gs1_stock/models/res_company.py | 10 ++---- edi_gs1_stock/models/res_config_settings.py | 6 ++-- edi_gs1_stock/views/res_config_settings.xml | 38 ++++++++++++++------- 3 files changed, 32 insertions(+), 22 deletions(-) diff --git a/edi_gs1_stock/models/res_company.py b/edi_gs1_stock/models/res_company.py index c34da4d86..a8d4b95e0 100644 --- a/edi_gs1_stock/models/res_company.py +++ b/edi_gs1_stock/models/res_company.py @@ -4,16 +4,12 @@ class ResCompany(models.Model): - _inherit = 'res.company' + _inherit = "res.company" gs1_main_uom_id = fields.Many2one( - comodel_name="uom.uom", - string="GS1 main UoM", - help="Main UoM for GS1 files" + comodel_name="uom.uom", string="GS1 main UoM", help="Main UoM for GS1 files" ) gs1_second_uom_id = fields.Many2one( - comodel_name="uom.uom", - string="GS1 second UoM", - help="Second UoM for GS1 files" + comodel_name="uom.uom", string="GS1 second UoM", help="Second UoM for GS1 files" ) diff --git a/edi_gs1_stock/models/res_config_settings.py b/edi_gs1_stock/models/res_config_settings.py index e0c040103..5ca428ec7 100644 --- a/edi_gs1_stock/models/res_config_settings.py +++ b/edi_gs1_stock/models/res_config_settings.py @@ -4,17 +4,17 @@ class ResConfigSettings(models.TransientModel): - _inherit = 'res.config.settings' + _inherit = "res.config.settings" gs1_main_uom_id = fields.Many2one( comodel_name="uom.uom", help="Main UoM for GS1 files", related="company_id.gs1_main_uom_id", - readonly=False + readonly=False, ) gs1_second_uom_id = fields.Many2one( comodel_name="uom.uom", help="Second UoM for GS1 files", related="company_id.gs1_second_uom_id", - readonly=False + readonly=False, ) diff --git a/edi_gs1_stock/views/res_config_settings.xml b/edi_gs1_stock/views/res_config_settings.xml index 047ed2f96..0af15a031 100644 --- a/edi_gs1_stock/views/res_config_settings.xml +++ b/edi_gs1_stock/views/res_config_settings.xml @@ -1,38 +1,52 @@ - + res.config.settings.form (in edi_gs1_stock) res.config.settings - - + +

      GS1

      -
      +
      -
      -
      From 2beb9b6e6482d3c882eb06f1dda4d4b10391dd39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Honor=C3=A9?= Date: Thu, 24 Nov 2022 17:31:01 +0100 Subject: [PATCH 58/63] [GS1] Generate the Warehouse Outbound Notification ACK --- .../outbound_instruction_output_template.xml | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/edi_gs1_stock/data/outbound_instruction_output_template.xml b/edi_gs1_stock/data/outbound_instruction_output_template.xml index 462f6a3ae..64bc5001d 100644 --- a/edi_gs1_stock/data/outbound_instruction_output_template.xml +++ b/edi_gs1_stock/data/outbound_instruction_output_template.xml @@ -37,6 +37,51 @@ _result = { "shipto": shipto, } +provider = get_info_provider(exchange_record, work_ctx=_result) +if provider: + _result["info"] = provider.generate_info() + +result = _result + + + + + EDI Exchange - GS1 Outbound Notification ACK + gs1.generate.warehousingOutboundInstructionAck + xml + + + + +sender = backend.lsc_partner_id +receiver = exchange_record.record.partner_id +shipto = exchange_record.record.partner_id +logistic_service_seller = backend.lsp_partner_id +logistic_service_buyer = sender +# FIXME: this is probably wrong or not always true +buyer = exchange_record.record.partner_id +# TODO: Pick the SO partner customer. +seller = backend.lsc_partner_id +# TODO: get a default carrier somehow? +shipper = env['res.partner'].new({ + 'name': "NO_CARRIER_SPECIFIED", + 'gln_code': "0" * 13, +}) + +_result = { + "sender": sender, + "receiver": receiver, + "ls_seller": logistic_service_seller, + "ls_buyer": logistic_service_buyer, + "buyer": buyer, + "seller": seller, + "shipper": shipper, + "shipto": shipto, +} + provider = get_info_provider(exchange_record, work_ctx=_result) if provider: _result["info"] = provider.generate_info() From 8467afccf4a0919e6cdc3c4c2349d57d95ef6d86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Honor=C3=A9?= Date: Tue, 13 Dec 2022 19:39:20 +0100 Subject: [PATCH 59/63] [GS1] Add shipper on WOI + add inherit point on config view + fix template --- edi_gs1_stock/data/inbound_instruction_output_template.xml | 6 +----- .../data/outbound_instruction_output_template.xml | 5 +---- edi_gs1_stock/data/outbound_instruction_qweb_template.xml | 1 + edi_gs1_stock/views/res_config_settings.xml | 7 +++++-- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/edi_gs1_stock/data/inbound_instruction_output_template.xml b/edi_gs1_stock/data/inbound_instruction_output_template.xml index f3621b36a..829540c08 100644 --- a/edi_gs1_stock/data/inbound_instruction_output_template.xml +++ b/edi_gs1_stock/data/inbound_instruction_output_template.xml @@ -18,11 +18,7 @@ logistic_service_buyer = sender # FIXME: this is probably wrong or not always true buyer = sender seller = exchange_record.record.purchase_id.partner_id -# TODO: get a default carrier somehow? -shipper = env['res.partner'].new({ - 'name': "NO_CARRIER_SPECIFIED", - 'gln_code': "0" * 13, -}) +shipper = exchange_record.record.carrier_id.partner_id _result = { "sender": sender, diff --git a/edi_gs1_stock/data/outbound_instruction_output_template.xml b/edi_gs1_stock/data/outbound_instruction_output_template.xml index 64bc5001d..1e0de9cc5 100644 --- a/edi_gs1_stock/data/outbound_instruction_output_template.xml +++ b/edi_gs1_stock/data/outbound_instruction_output_template.xml @@ -21,10 +21,7 @@ buyer = exchange_record.record.partner_id # TODO: Pick the SO partner customer. seller = backend.lsc_partner_id # TODO: get a default carrier somehow? -shipper = env['res.partner'].new({ - 'name': "NO_CARRIER_SPECIFIED", - 'gln_code': "0" * 13, -}) +shipper = exchange_record.record.carrier_id.partner_id _result = { "sender": sender, diff --git a/edi_gs1_stock/data/outbound_instruction_qweb_template.xml b/edi_gs1_stock/data/outbound_instruction_qweb_template.xml index 8e06125d9..76e46e755 100644 --- a/edi_gs1_stock/data/outbound_instruction_qweb_template.xml +++ b/edi_gs1_stock/data/outbound_instruction_qweb_template.xml @@ -84,6 +84,7 @@ t-esc="package_total.totalPackageQuantity" >2 6.0 diff --git a/edi_gs1_stock/views/res_config_settings.xml b/edi_gs1_stock/views/res_config_settings.xml index 0af15a031..6bbb7d76f 100644 --- a/edi_gs1_stock/views/res_config_settings.xml +++ b/edi_gs1_stock/views/res_config_settings.xml @@ -10,8 +10,11 @@

      GS1

      -
      -
      +
      +
    +
    +

    Important

    +

    This is an alpha version, the data model and design can change at any time without warning. +Only for development or testing purpose, do not use in production. +More details on development status

    +

    Table of contents

      diff --git a/edi_gs1_stock_oca/static/schemas/gs1/ecom/WarehousingCommon.xsd b/edi_gs1_stock_oca/static/schemas/gs1/ecom/WarehousingCommon.xsd deleted file mode 100644 index 2b2add5fb..000000000 --- a/edi_gs1_stock_oca/static/schemas/gs1/ecom/WarehousingCommon.xsd +++ /dev/null @@ -1,280 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/edi_gs1_stock_oca/static/schemas/gs1/ecom/WarehousingInboundInstruction.xsd b/edi_gs1_stock_oca/static/schemas/gs1/ecom/WarehousingInboundInstruction.xsd deleted file mode 100644 index eb4f90aaf..000000000 --- a/edi_gs1_stock_oca/static/schemas/gs1/ecom/WarehousingInboundInstruction.xsd +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/edi_gs1_stock_oca/static/schemas/gs1/ecom/WarehousingInboundNotification.xsd b/edi_gs1_stock_oca/static/schemas/gs1/ecom/WarehousingInboundNotification.xsd deleted file mode 100644 index ad3811cc6..000000000 --- a/edi_gs1_stock_oca/static/schemas/gs1/ecom/WarehousingInboundNotification.xsd +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/edi_gs1_stock_oca/static/schemas/gs1/ecom/eComCommon.xsd b/edi_gs1_stock_oca/static/schemas/gs1/ecom/eComCommon.xsd deleted file mode 100644 index 4ae5569dc..000000000 --- a/edi_gs1_stock_oca/static/schemas/gs1/ecom/eComCommon.xsd +++ /dev/null @@ -1,1858 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/edi_gs1_stock_oca/static/schemas/gs1/shared/SharedCommon.xsd b/edi_gs1_stock_oca/static/schemas/gs1/shared/SharedCommon.xsd deleted file mode 100644 index d51b853df..000000000 --- a/edi_gs1_stock_oca/static/schemas/gs1/shared/SharedCommon.xsd +++ /dev/null @@ -1,1506 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/edi_gs1_stock_oca/static/schemas/sbdh/BasicTypes.xsd b/edi_gs1_stock_oca/static/schemas/sbdh/BasicTypes.xsd deleted file mode 100644 index 3718acfe5..000000000 --- a/edi_gs1_stock_oca/static/schemas/sbdh/BasicTypes.xsd +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - The MIME type as defined by IANA. Please refer to - http://www.iana.org/assignments/media-types/ for a list of types. - - - - - - - - ISO 639-2; 1998 representation of Language name. Refer to http://www.loc.gov/standards/iso639-2/iso639jac.html to get the latest version of the standard. - - - - - diff --git a/edi_gs1_stock_oca/static/schemas/sbdh/BusinessScope.xsd b/edi_gs1_stock_oca/static/schemas/sbdh/BusinessScope.xsd deleted file mode 100644 index 19ddd7ce0..000000000 --- a/edi_gs1_stock_oca/static/schemas/sbdh/BusinessScope.xsd +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/edi_gs1_stock_oca/static/schemas/sbdh/DocumentIdentification.xsd b/edi_gs1_stock_oca/static/schemas/sbdh/DocumentIdentification.xsd deleted file mode 100644 index 5264a2e27..000000000 --- a/edi_gs1_stock_oca/static/schemas/sbdh/DocumentIdentification.xsd +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/edi_gs1_stock_oca/static/schemas/sbdh/Manifest.xsd b/edi_gs1_stock_oca/static/schemas/sbdh/Manifest.xsd deleted file mode 100644 index 59bed11f1..000000000 --- a/edi_gs1_stock_oca/static/schemas/sbdh/Manifest.xsd +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/edi_gs1_stock_oca/static/schemas/sbdh/Partner.xsd b/edi_gs1_stock_oca/static/schemas/sbdh/Partner.xsd deleted file mode 100644 index ff4258da6..000000000 --- a/edi_gs1_stock_oca/static/schemas/sbdh/Partner.xsd +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/edi_gs1_stock_oca/static/schemas/sbdh/StandardBusinessDocumentHeader.xsd b/edi_gs1_stock_oca/static/schemas/sbdh/StandardBusinessDocumentHeader.xsd deleted file mode 100644 index b97fb45f4..000000000 --- a/edi_gs1_stock_oca/static/schemas/sbdh/StandardBusinessDocumentHeader.xsd +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/edi_gs1_stock_oca/tests/common.py b/edi_gs1_stock_oca/tests/common.py index d7115e71b..802e47400 100644 --- a/edi_gs1_stock_oca/tests/common.py +++ b/edi_gs1_stock_oca/tests/common.py @@ -5,9 +5,9 @@ from uuid import uuid4 from odoo import fields -from odoo.tests.common import Form +from odoo.tests import Form -from odoo.addons.edi_gs1.tests.common import BaseTestCase +from odoo.addons.edi_gs1_oca.tests.common import BaseTestCase class DeliveryMixin: @@ -45,17 +45,27 @@ def setUpClass(cls): super().setUpClass() cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) + @classmethod + def _create_product(cls, name, barcode): + return cls.env["product.product"].create( + { + "name": name, + "is_storable": True, + "type": "consu", + "barcode": barcode, + "weight": 1.0, + } + ) + @classmethod def _setup_order(cls): - cls.product_a = cls.env.ref("product.product_product_4") - cls.product_a.barcode = "1" * 14 - cls.product_b = cls.env.ref("product.product_product_4b") - cls.product_b.barcode = "2" * 14 - cls.product_c = cls.env.ref("product.product_product_4c") - cls.product_c.barcode = "3" * 14 + cls.product_a = cls._create_product("GS1 Product A", "1" * 14) + cls.product_b = cls._create_product("GS1 Product B", "2" * 14) + cls.product_c = cls._create_product("GS1 Product C", "3" * 14) + cls.vendor = cls.env["res.partner"].create({"name": "GS1 Vendor"}) cls.purchase = cls._create_purchase_order( { - "partner_id": cls.env.ref("base.res_partner_10"), + "partner_id": cls.vendor, "date_planned": "2020-07-12", } ) @@ -69,6 +79,11 @@ def _setup_order(cls): cls.purchase.button_approve() cls.delivery = cls.purchase.picking_ids[0] - cls.carrier = cls.env.ref("base.res_partner_4") - cls.carrier.gln_code = "123".zfill(13) - cls.carrier.ref = "CARRIER#1" + cls.delivery.scheduled_date = "2020-07-12 12:00:00" + cls.carrier = cls.env["res.partner"].create( + { + "name": "GS1 Carrier", + "gln_code": "123".zfill(13), + "ref": "CARRIER#1", + } + ) diff --git a/edi_gs1_stock_oca/tests/test_inbound_instruction.py b/edi_gs1_stock_oca/tests/test_inbound_instruction.py index abc044902..6601b2c03 100644 --- a/edi_gs1_stock_oca/tests/test_inbound_instruction.py +++ b/edi_gs1_stock_oca/tests/test_inbound_instruction.py @@ -8,16 +8,18 @@ class InboundInstructionTestCase(ShipmentTestCaseBase): + _schema_path = ( + "edi_gs1_oca:static/schemas/gs1/ecom/WarehousingInboundInstruction.xsd" + ) + @classmethod def setUpClass(cls): super().setUpClass() cls._setup_order() cls.exc_type = cls.env.ref( - "edi_gs1_stock.edi_exchange_type_inbound_instruction" - ) - cls.exc_tmpl = cls.env.ref( - "edi_gs1_stock.edi_exchange_template_inbound_instruction" + "edi_gs1_stock_oca.edi_exchange_type_inbound_instruction" ) + cls.handler = cls.env["edi.gs1.generate.inbound.instruction"] vals = { "model": cls.delivery._name, "res_id": cls.delivery.id, @@ -25,15 +27,14 @@ def setUpClass(cls): } cls.record = cls.backend.create_record(cls.exc_type.code, vals) - def test_get_template(self): - template = self.backend._get_template(self.record, "output", "generate") - self.assertEqual(template, self.exc_tmpl) + def test_generator_wired(self): self.assertEqual( - template.template_id.key, "edi_gs1_stock.edi_exchange_inbound_instruction" + self.exc_type.generate_model_id.model, + "edi.gs1.generate.inbound.instruction", ) - def test_render_values(self): - values = self.exc_tmpl._get_render_values(self.record) + def test_work_ctx(self): + values = self.handler._get_work_ctx(self.record) expected = [ ("sender", self.backend.lsc_partner_id), ("receiver", self.backend.lsp_partner_id), @@ -44,28 +45,18 @@ def test_render_values(self): ] for k, v in expected: self.assertEqual(values[k], v) - - # Detailed test below - self.assertTrue(values["info"]) self.assertTrue(values["shipper"]) - def test_info_provider_bad_work_ctx(self): - with self.assertRaises(AttributeError) as err: - self.exc_tmpl._get_info_provider(self.record) - self.assertEqual( - str(err.exception), "`sender` is required for this component!" - ) - @freeze_time("2020-07-09 10:30:00") def test_info_provider_data(self): - values = self.exc_tmpl._get_render_values(self.record, shipper=self.carrier) - provider = self.exc_tmpl._get_info_provider(self.record, work_ctx=values) + work_ctx = self.handler._get_work_ctx(self.record) + work_ctx["shipper"] = self.carrier expected_shipment = { "shipmentIdentification": { "additionalShipmentIdentification": { "attrs": { # fmt: off - "additionalShipmentIdentificationTypeCode": "GOODS_RECEIVER_ASSIGNED" + "additionalShipmentIdentificationTypeCode": "GOODS_RECEIVER_ASSIGNED" # noqa: E501 # fmt: on }, "value": self.delivery.name, @@ -76,7 +67,7 @@ def test_info_provider_data(self): "additionalPartyIdentification": { "attrs": { # fmt: off - "additionalPartyIdentificationTypeCode": "BUYER_ASSIGNED_IDENTIFIER_FOR_A_PARTY" + "additionalPartyIdentificationTypeCode": "BUYER_ASSIGNED_IDENTIFIER_FOR_A_PARTY" # noqa: E501 # fmt: on }, "value": "CARRIER#1", @@ -127,19 +118,18 @@ def test_info_provider_data(self): }, ], } - info = provider.generate_info().warehousingInboundInstruction + info = self.handler.generate_info( + self.record, **work_ctx + ).warehousingInboundInstruction for k, v in expected_shipment.items(): self.assertEqual( info.warehousingInboundInstructionShipment[k], v, f"{k} does not match" ) - _schema_path = "edi_gs1:static/schemas/gs1/ecom/WarehousingInboundInstruction.xsd" - @freeze_time("2020-07-09 10:30:00") def test_xml(self): record = self.delivery.with_context( edi_exchange_send=False ).action_send_wh_inbound_instruction() file_content = record._get_file_content() - handler = self._get_xml_handler() - self.assertEqual(handler.validate(file_content), None) + self.assertEqual(self._validate_xml(file_content), None) diff --git a/edi_gs1_stock_oca/tests/test_outbound_instruction.py b/edi_gs1_stock_oca/tests/test_outbound_instruction.py index 18442aa75..0a272d8c6 100644 --- a/edi_gs1_stock_oca/tests/test_outbound_instruction.py +++ b/edi_gs1_stock_oca/tests/test_outbound_instruction.py @@ -8,18 +8,18 @@ class OutboundInstructionTestCase(ShipmentTestCaseBase): - _schema_path = "edi_gs1:static/schemas/gs1/ecom/WarehousingOutboundInstruction.xsd" + _schema_path = ( + "edi_gs1_oca:static/schemas/gs1/ecom/WarehousingOutboundInstruction.xsd" + ) @classmethod def setUpClass(cls): super().setUpClass() cls._setup_order() cls.exc_type = cls.env.ref( - "edi_gs1_stock.edi_exchange_type_outbound_instruction" - ) - cls.exc_tmpl = cls.env.ref( - "edi_gs1_stock.edi_exchange_template_outbound_instruction" + "edi_gs1_stock_oca.edi_exchange_type_outbound_instruction" ) + cls.handler = cls.env["edi.gs1.generate.outbound.instruction"] vals = { "model": cls.delivery._name, "res_id": cls.delivery.id, @@ -27,15 +27,14 @@ def setUpClass(cls): } cls.record = cls.backend.create_record(cls.exc_type.code, vals) - def test_get_template(self): - template = self.backend._get_template(self.record, "output", "generate") - self.assertEqual(template, self.exc_tmpl) + def test_generator_wired(self): self.assertEqual( - template.template_id.key, "edi_gs1_stock.edi_exchange_outbound_instruction" + self.exc_type.generate_model_id.model, + "edi.gs1.generate.outbound.instruction", ) - def test_render_values(self): - values = self.exc_tmpl._get_render_values(self.record) + def test_work_ctx(self): + values = self.handler._get_work_ctx(self.record) expected = [ ("sender", self.backend.lsc_partner_id), ("receiver", self.delivery.partner_id), @@ -46,28 +45,18 @@ def test_render_values(self): ] for k, v in expected: self.assertEqual(values[k], v, f"{k} is wrong") - - # Detailed test below - self.assertTrue(values["info"]) self.assertTrue(values["shipper"]) - def test_info_provider_bad_work_ctx(self): - with self.assertRaises(AttributeError) as err: - self.exc_tmpl._get_info_provider(self.record) - self.assertEqual( - str(err.exception), "`sender` is required for this component!" - ) - @freeze_time("2020-07-09 10:30:00") def test_info_provider_data(self): - values = self.exc_tmpl._get_render_values(self.record, shipper=self.carrier) - provider = self.exc_tmpl._get_info_provider(self.record, work_ctx=values) + work_ctx = self.handler._get_work_ctx(self.record) + work_ctx["shipper"] = self.carrier expected_shipment = { "shipmentIdentification": { "additionalShipmentIdentification": { "attrs": { # fmt: off - "additionalShipmentIdentificationTypeCode": "GOODS_RECEIVER_ASSIGNED" + "additionalShipmentIdentificationTypeCode": "GOODS_RECEIVER_ASSIGNED" # noqa: E501 # fmt: on }, "value": self.delivery.name, @@ -78,7 +67,7 @@ def test_info_provider_data(self): "additionalPartyIdentification": { "attrs": { # fmt: off - "additionalPartyIdentificationTypeCode": "BUYER_ASSIGNED_IDENTIFIER_FOR_A_PARTY" + "additionalPartyIdentificationTypeCode": "BUYER_ASSIGNED_IDENTIFIER_FOR_A_PARTY" # noqa: E501 # fmt: on }, "value": "CARRIER#1", @@ -129,7 +118,9 @@ def test_info_provider_data(self): }, ], } - info = provider.generate_info().warehousingOutboundInstruction + info = self.handler.generate_info( + self.record, **work_ctx + ).warehousingOutboundInstruction for k, v in expected_shipment.items(): self.assertEqual( info.warehousingOutboundInstructionShipment[k], v, f"{k} does not match" @@ -141,5 +132,4 @@ def test_xml(self): edi_exchange_send=False ).action_send_wh_outbound_instruction() file_content = record._get_file_content() - handler = self._get_xml_handler() - self.assertEqual(handler.validate(file_content), None) + self.assertEqual(self._validate_xml(file_content), None) diff --git a/edi_gs1_stock_oca/views/res_config_settings.xml b/edi_gs1_stock_oca/views/res_config_settings.xml index 6bbb7d76f..71b6b5ba6 100644 --- a/edi_gs1_stock_oca/views/res_config_settings.xml +++ b/edi_gs1_stock_oca/views/res_config_settings.xml @@ -8,52 +8,25 @@ - -

      GS1

      -
      -
      + + -
      -
      -
      -
      -
      -
      -
      + + + + + + From d784d166e66e4b369f9c5cb364c3af99f9e9577b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Honor=C3=A9?= Date: Tue, 30 Jun 2026 11:01:20 +0200 Subject: [PATCH 63/63] [IMP] edi_gs1_stock_oca: move to Beta development status --- edi_gs1_stock_oca/README.rst | 9 ++------- edi_gs1_stock_oca/__manifest__.py | 2 +- edi_gs1_stock_oca/static/description/index.html | 8 +------- 3 files changed, 4 insertions(+), 15 deletions(-) diff --git a/edi_gs1_stock_oca/README.rst b/edi_gs1_stock_oca/README.rst index ff5748fb3..4f01425c8 100644 --- a/edi_gs1_stock_oca/README.rst +++ b/edi_gs1_stock_oca/README.rst @@ -14,9 +14,9 @@ GS1 EDI for stock !! source digest: sha256:58f873403235289f5c44a5f029c949b43b56fe09a12486f8c6b7763d06acc5f0 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status - :alt: Alpha + :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 @@ -42,11 +42,6 @@ Provides support to generate or process the following messages: - Warehouse Outbound Instruction [TODO] - Warehouse Outbound Notification [TODO] -.. IMPORTANT:: - This is an alpha version, the data model and design can change at any time without warning. - Only for development or testing purpose, do not use in production. - `More details on development status `_ - **Table of contents** .. contents:: diff --git a/edi_gs1_stock_oca/__manifest__.py b/edi_gs1_stock_oca/__manifest__.py index e184b9e41..a37bfb4a3 100644 --- a/edi_gs1_stock_oca/__manifest__.py +++ b/edi_gs1_stock_oca/__manifest__.py @@ -8,7 +8,7 @@ Base module for GS1 standard EDI exchange related to stock. """, "version": "19.0.1.0.0", - "development_status": "Alpha", + "development_status": "Beta", "license": "AGPL-3", "author": "ACSONE,Odoo Community Association (OCA)", "website": "https://github.com/OCA/edi-framework", diff --git a/edi_gs1_stock_oca/static/description/index.html b/edi_gs1_stock_oca/static/description/index.html index 59114346e..f7acc42d5 100644 --- a/edi_gs1_stock_oca/static/description/index.html +++ b/edi_gs1_stock_oca/static/description/index.html @@ -374,7 +374,7 @@

      GS1 EDI for stock

      !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! source digest: sha256:58f873403235289f5c44a5f029c949b43b56fe09a12486f8c6b7763d06acc5f0 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

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

      +

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

      GS1 -The Global Language of Business implementation for stock.

      Provides support to generate or process the following messages:

      @@ -386,12 +386,6 @@

      GS1 EDI for stock

    • Warehouse Outbound Notification [TODO]
    -
    -

    Important

    -

    This is an alpha version, the data model and design can change at any time without warning. -Only for development or testing purpose, do not use in production. -More details on development status

    -

    Table of contents