diff --git a/edi_sale_oca/README.rst b/edi_sale_oca/README.rst new file mode 100644 index 000000000..6516d3ef5 --- /dev/null +++ b/edi_sale_oca/README.rst @@ -0,0 +1,108 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + +========= +EDI Sales +========= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:9a5bcb3a36371db790dd492551d19d7210d51802c8bd92e3c5dc0ea6a5710920 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/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--framework-lightgray.png?logo=github + :target: https://github.com/OCA/edi-framework/tree/19.0/edi_sale_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-framework-19-0/edi-framework-19-0-edi_sale_oca + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/edi-framework&target_branch=19.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +Handle sale orders via EDI. + +This is a base module to plug sales processes with the EDI framework. + +To handle inbound/outbound sale orders, you need to use +edi_sale_input_oca or edi_sale_output_oca modules, or create your own +modules. + +.. 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 to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Camptocamp + +Contributors +------------ + +- Simone Orsi +- Duong (Tran Quoc) +- Thien (Vo Hong) +- Italo Lopes + +Other credits +------------- + +The migration of this module from 14.0 to 16.0 was financially supported +by Camptocamp. + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-simahawk| image:: https://github.com/simahawk.png?size=40px + :target: https://github.com/simahawk + :alt: simahawk + +Current `maintainer `__: + +|maintainer-simahawk| + +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_sale_oca/__init__.py b/edi_sale_oca/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/edi_sale_oca/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/edi_sale_oca/__manifest__.py b/edi_sale_oca/__manifest__.py new file mode 100644 index 000000000..68af8fefe --- /dev/null +++ b/edi_sale_oca/__manifest__.py @@ -0,0 +1,31 @@ +# Copyright 2022 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "EDI Sales", + "summary": """ + Configuration and special behaviors for EDI on sales. + """, + "version": "19.0.1.0.0", + "development_status": "Alpha", + "license": "AGPL-3", + "author": "Camptocamp,Odoo Community Association (OCA)", + "maintainers": ["simahawk"], + "website": "https://github.com/OCA/edi-framework", + "depends": [ + "edi_core_oca", + "edi_record_metadata_oca", + "sale", + ], + "data": [ + "data/edi_configuration.xml", + "views/res_partner.xml", + "views/sale_order.xml", + "views/edi_exchange_record.xml", + ], + "demo": [ + "demo/edi_backend.xml", + "demo/edi_exchange_type.xml", + "demo/edi_configuration.xml", + ], +} diff --git a/edi_sale_oca/data/edi_configuration.xml b/edi_sale_oca/data/edi_configuration.xml new file mode 100644 index 000000000..976829aeb --- /dev/null +++ b/edi_sale_oca/data/edi_configuration.xml @@ -0,0 +1,13 @@ + + + + + On SO state change + on_edi_sale_order_state_change + Trigger when a sale order state changes + + + diff --git a/edi_sale_oca/demo/edi_backend.xml b/edi_sale_oca/demo/edi_backend.xml new file mode 100644 index 000000000..dde2c115e --- /dev/null +++ b/edi_sale_oca/demo/edi_backend.xml @@ -0,0 +1,11 @@ + + + + SALE DEMO + sale_demo + + + SALE DEMO + + + diff --git a/edi_sale_oca/demo/edi_configuration.xml b/edi_sale_oca/demo/edi_configuration.xml new file mode 100644 index 000000000..98df4747f --- /dev/null +++ b/edi_sale_oca/demo/edi_configuration.xml @@ -0,0 +1,36 @@ + + + + Demo Sale OrderResponse - order confirmed + Show case how you can send out an order response automatically + + + + + +# STATES +# ('draft', "Quotation"), +# ('sent', "Quotation Sent"), +# ('sale', "Sales Order"), +# ('cancel', "Cancelled"), +if record.state == 'sale': + record._edi_send_via_edi(conf.type_id) + + + + Demo Sale OrderResponse - order done + Show case how you can send out an order response automatically + + + + + +if record.state in ('done', 'cancel'): + record._edi_send_via_edi(conf.type_id) + + + diff --git a/edi_sale_oca/demo/edi_exchange_type.xml b/edi_sale_oca/demo/edi_exchange_type.xml new file mode 100644 index 000000000..756280cea --- /dev/null +++ b/edi_sale_oca/demo/edi_exchange_type.xml @@ -0,0 +1,22 @@ + + + + + + Demo Sale Order Response + demo_SaleOrder_out + output + {record_name}-{type.code}-{dt} + xml + + + + + + + Demo Sale Order + demo_SaleOrder_in + input + xml + + diff --git a/edi_sale_oca/i18n/edi_sale_oca.pot b/edi_sale_oca/i18n/edi_sale_oca.pot new file mode 100644 index 000000000..a63014d51 --- /dev/null +++ b/edi_sale_oca/i18n/edi_sale_oca.pot @@ -0,0 +1,168 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * edi_sale_oca +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: edi_sale_oca +#: model_terms:ir.ui.view,arch_db:edi_sale_oca.view_order_form +msgid "EDI" +msgstr "" + +#. module: edi_sale_oca +#: model:ir.model,name:edi_sale_oca.model_res_partner +msgid "Contact" +msgstr "" + +#. module: edi_sale_oca +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order__edi_disable_auto +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order_line__edi_disable_auto +msgid "Disable auto" +msgstr "" + +#. module: edi_sale_oca +#: model_terms:ir.ui.view,arch_db:edi_sale_oca.view_order_form +msgid "Disable automated actions" +msgstr "" + +#. module: edi_sale_oca +#: model:ir.ui.menu,name:edi_sale_oca.menu_sale_edi_root +#: model_terms:ir.ui.view,arch_db:edi_sale_oca.view_order_form +msgid "EDI" +msgstr "" + +#. module: edi_sale_oca +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order_line__edi_id +msgid "EDI ID" +msgstr "" + +#. module: edi_sale_oca +#: model_terms:ir.ui.view,arch_db:edi_sale_oca.view_sales_order_filter +msgid "EDI exchange" +msgstr "" + +#. module: edi_sale_oca +#: model_terms:ir.ui.view,arch_db:edi_sale_oca.view_sales_order_filter +msgid "EDI exchange type" +msgstr "" + +#. module: edi_sale_oca +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order__origin_edi_endpoint_id +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order_line__origin_edi_endpoint_id +msgid "EDI origin endpoint" +msgstr "" + +#. module: edi_sale_oca +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order__origin_exchange_type_id +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order_line__origin_exchange_type_id +msgid "EDI origin exchange type" +msgstr "" + +#. module: edi_sale_oca +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order__origin_exchange_record_id +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order_line__origin_exchange_record_id +msgid "EDI origin record" +msgstr "" + +#. module: edi_sale_oca +#: model:ir.model.fields,help:edi_sale_oca.field_sale_order__origin_exchange_record_id +#: model:ir.model.fields,help:edi_sale_oca.field_sale_order_line__origin_exchange_record_id +msgid "EDI record that originated this document." +msgstr "" + +#. module: edi_sale_oca +#: model:ir.model.fields,field_description:edi_sale_oca.field_res_partner__edi_sale_conf_ids +#: model:ir.model.fields,field_description:edi_sale_oca.field_res_users__edi_sale_conf_ids +msgid "EDI sale configuration" +msgstr "" + +#. module: edi_sale_oca +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order__edi_config +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order_line__edi_config +msgid "Edi Config" +msgstr "" + +#. module: edi_sale_oca +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order_line__edi_exchange_ready +msgid "Edi Exchange Ready" +msgstr "" + +#. module: edi_sale_oca +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order__edi_has_form_config +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order_line__edi_has_form_config +msgid "Edi Has Form Config" +msgstr "" + +#. module: edi_sale_oca +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order__exchange_record_ids +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order_line__exchange_record_ids +msgid "Exchange Record" +msgstr "" + +#. module: edi_sale_oca +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order__exchange_record_count +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order_line__exchange_record_count +msgid "Exchange Record Count" +msgstr "" + +#. module: edi_sale_oca +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order__exchange_related_record_ids +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order_line__exchange_related_record_ids +msgid "Exchange Related Record" +msgstr "" + +#. module: edi_sale_oca +#: model:ir.ui.menu,name:edi_sale_oca.menu_sale_edi_records +msgid "Exchanges" +msgstr "" + +#. module: edi_sale_oca +#: model:ir.model.fields,help:edi_sale_oca.field_sale_order_line__edi_id +msgid "Internal or external identifier for records." +msgstr "" + +#. module: edi_sale_oca +#: model:ir.model.fields,help:edi_sale_oca.field_sale_order__origin_edi_endpoint_id +#: model:ir.model.fields,help:edi_sale_oca.field_sale_order_line__origin_edi_endpoint_id +msgid "Record generated via this endpoint" +msgstr "" + +#. module: edi_sale_oca +#: model_terms:ir.ui.view,arch_db:edi_sale_oca.view_partner_form +msgid "Sale" +msgstr "" + +#. module: edi_sale_oca +#: model:ir.actions.act_window,name:edi_sale_oca.act_open_edi_exchange_record_sale_order_view +msgid "Sale Order Exchange Records" +msgstr "" + +#. module: edi_sale_oca +#: model:ir.model,name:edi_sale_oca.model_sale_order +msgid "Sales Order" +msgstr "" + +#. module: edi_sale_oca +#: model:ir.model,name:edi_sale_oca.model_sale_order_line +msgid "Sales Order Line" +msgstr "" + +#. module: edi_sale_oca +#: model_terms:ir.ui.view,arch_db:edi_sale_oca.view_sales_order_filter +msgid "Source: EDI" +msgstr "" + +#. module: edi_sale_oca +#: model:ir.model.fields,help:edi_sale_oca.field_sale_order__edi_disable_auto +#: model:ir.model.fields,help:edi_sale_oca.field_sale_order_line__edi_disable_auto +msgid "When marked, EDI automatic processing will be avoided" +msgstr "" diff --git a/edi_sale_oca/i18n/it.po b/edi_sale_oca/i18n/it.po new file mode 100644 index 000000000..5e9c2f875 --- /dev/null +++ b/edi_sale_oca/i18n/it.po @@ -0,0 +1,180 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * edi_sale_oca +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2025-09-15 08:42+0000\n" +"Last-Translator: mymage \n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.10.4\n" + +#. module: edi_sale_oca +#: model_terms:ir.ui.view,arch_db:edi_sale_oca.view_order_form +msgid "EDI" +msgstr "EDI" + +#. module: edi_sale_oca +#: model:ir.model,name:edi_sale_oca.model_res_partner +msgid "Contact" +msgstr "Contatto" + +#. module: edi_sale_oca +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order__edi_disable_auto +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order_line__edi_disable_auto +msgid "Disable auto" +msgstr "Disabilita automatico" + +#. module: edi_sale_oca +#: model_terms:ir.ui.view,arch_db:edi_sale_oca.view_order_form +msgid "Disable automated actions" +msgstr "Disabilita azioni automatiche" + +#. module: edi_sale_oca +#: model:ir.ui.menu,name:edi_sale_oca.menu_sale_edi_root +#: model_terms:ir.ui.view,arch_db:edi_sale_oca.view_order_form +msgid "EDI" +msgstr "EDI" + +#. module: edi_sale_oca +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order_line__edi_id +msgid "EDI ID" +msgstr "ID EDI" + +#. module: edi_sale_oca +#: model_terms:ir.ui.view,arch_db:edi_sale_oca.view_sales_order_filter +msgid "EDI exchange" +msgstr "Scambio EDI" + +#. module: edi_sale_oca +#: model_terms:ir.ui.view,arch_db:edi_sale_oca.view_sales_order_filter +msgid "EDI exchange type" +msgstr "Tipo scambio EDI" + +#. module: edi_sale_oca +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order__origin_edi_endpoint_id +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order_line__origin_edi_endpoint_id +msgid "EDI origin endpoint" +msgstr "Endpoint origine EDI" + +#. module: edi_sale_oca +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order__origin_exchange_type_id +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order_line__origin_exchange_type_id +msgid "EDI origin exchange type" +msgstr "Tipo scambio origine EDI" + +#. module: edi_sale_oca +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order__origin_exchange_record_id +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order_line__origin_exchange_record_id +msgid "EDI origin record" +msgstr "Record origine EDI" + +#. module: edi_sale_oca +#: model:ir.model.fields,help:edi_sale_oca.field_sale_order__origin_exchange_record_id +#: model:ir.model.fields,help:edi_sale_oca.field_sale_order_line__origin_exchange_record_id +msgid "EDI record that originated this document." +msgstr "Record EDI che ha generato questo documento." + +#. module: edi_sale_oca +#: model:ir.model.fields,field_description:edi_sale_oca.field_res_partner__edi_sale_conf_ids +#: model:ir.model.fields,field_description:edi_sale_oca.field_res_users__edi_sale_conf_ids +msgid "EDI sale configuration" +msgstr "Configurazione vendite EDI" + +#. module: edi_sale_oca +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order__edi_config +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order_line__edi_config +msgid "Edi Config" +msgstr "Configurazione EDI" + +#. module: edi_sale_oca +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order_line__edi_exchange_ready +msgid "Edi Exchange Ready" +msgstr "Pronto allo scambio EDI" + +#. module: edi_sale_oca +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order__edi_has_form_config +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order_line__edi_has_form_config +msgid "Edi Has Form Config" +msgstr "EDI ha una maschera di configurazione" + +#. module: edi_sale_oca +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order__exchange_record_ids +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order_line__exchange_record_ids +msgid "Exchange Record" +msgstr "Record di scambio" + +#. module: edi_sale_oca +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order__exchange_record_count +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order_line__exchange_record_count +msgid "Exchange Record Count" +msgstr "Conteggio record di scambio" + +#. module: edi_sale_oca +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order__exchange_related_record_ids +#: model:ir.model.fields,field_description:edi_sale_oca.field_sale_order_line__exchange_related_record_ids +msgid "Exchange Related Record" +msgstr "Record relativo allo scambio" + +#. module: edi_sale_oca +#: model:ir.ui.menu,name:edi_sale_oca.menu_sale_edi_records +msgid "Exchanges" +msgstr "Scambi" + +#. module: edi_sale_oca +#: model:ir.model.fields,help:edi_sale_oca.field_sale_order_line__edi_id +msgid "Internal or external identifier for records." +msgstr "Identificatore interno o esterno per i record." + +#. module: edi_sale_oca +#: model:ir.model.fields,help:edi_sale_oca.field_sale_order__origin_edi_endpoint_id +#: model:ir.model.fields,help:edi_sale_oca.field_sale_order_line__origin_edi_endpoint_id +msgid "Record generated via this endpoint" +msgstr "Record generato attraverso questo endpoint" + +#. module: edi_sale_oca +#: model_terms:ir.ui.view,arch_db:edi_sale_oca.view_partner_form +msgid "Sale" +msgstr "Vendita" + +#. module: edi_sale_oca +#: model:ir.actions.act_window,name:edi_sale_oca.act_open_edi_exchange_record_sale_order_view +msgid "Sale Order Exchange Records" +msgstr "Record scambio ordine di vendita" + +#. module: edi_sale_oca +#: model:ir.model,name:edi_sale_oca.model_sale_order +msgid "Sales Order" +msgstr "Ordine di vendita" + +#. module: edi_sale_oca +#: model:ir.model,name:edi_sale_oca.model_sale_order_line +msgid "Sales Order Line" +msgstr "Riga ordine di vendita" + +#. module: edi_sale_oca +#: model_terms:ir.ui.view,arch_db:edi_sale_oca.view_sales_order_filter +msgid "Source: EDI" +msgstr "Origine: EDI" + +#. module: edi_sale_oca +#: model:ir.model.fields,help:edi_sale_oca.field_sale_order__edi_disable_auto +#: model:ir.model.fields,help:edi_sale_oca.field_sale_order_line__edi_disable_auto +msgid "When marked, EDI automatic processing will be avoided" +msgstr "Quando selezionata, l'elaborazione EDI automatica verrĂ  evitata" + +#~ msgid "Display Name" +#~ msgstr "Nome visualizzato" + +#~ msgid "ID" +#~ msgstr "ID" + +#~ msgid "Last Modified on" +#~ msgstr "Ultima modifica il" diff --git a/edi_sale_oca/models/__init__.py b/edi_sale_oca/models/__init__.py new file mode 100644 index 000000000..c0ee85119 --- /dev/null +++ b/edi_sale_oca/models/__init__.py @@ -0,0 +1,2 @@ +from . import sale_order +from . import res_partner diff --git a/edi_sale_oca/models/res_partner.py b/edi_sale_oca/models/res_partner.py new file mode 100644 index 000000000..ea2a15c22 --- /dev/null +++ b/edi_sale_oca/models/res_partner.py @@ -0,0 +1,19 @@ +# Copyright 2024 Camptocamp SA +# @author: Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + + +from odoo import fields, models + + +class ResPartner(models.Model): + _inherit = "res.partner" + + edi_sale_conf_ids = fields.Many2many( + string="EDI sale configuration", + comodel_name="edi.configuration", + relation="res_partner_edi_sale_configuration_rel", + column1="partner_id", + column2="conf_id", + domain=[("model_name", "=", "sale.order")], + ) diff --git a/edi_sale_oca/models/sale_order.py b/edi_sale_oca/models/sale_order.py new file mode 100644 index 000000000..e74e72913 --- /dev/null +++ b/edi_sale_oca/models/sale_order.py @@ -0,0 +1,61 @@ +# Copyright 2022 Camptocamp SA +# @author: Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class SaleOrder(models.Model): + _name = "sale.order" + _inherit = [ + "sale.order", + "edi.exchange.consumer.mixin", + ] + + def _edi_config_field_relation(self): + return self.partner_id.edi_sale_conf_ids + + # edi_record_metadata api + def _edi_get_metadata_to_store(self, orig_vals): + data = super()._edi_get_metadata_to_store(orig_vals) + # collect line values + line_vals_by_edi_id = {} + for line_vals in orig_vals.get("order_line", []): + # line_vals in the form `(0, 0, vals)` + vals = line_vals[-1] + line_vals_by_edi_id[vals["edi_id"]] = vals + + data.update({"orig_values": {"lines": line_vals_by_edi_id}}) + return data + + +class SaleOrderLine(models.Model): + _name = "sale.order.line" + _inherit = [ + "sale.order.line", + "edi.exchange.consumer.mixin", + "edi.id.mixin", + ] + + edi_disable_auto = fields.Boolean(related="order_id.edi_disable_auto") + + # TODO: add test + edi_exchange_ready = fields.Boolean(compute="_compute_edi_exchange_ready") + + @api.depends() + def _compute_edi_exchange_ready(self): + for rec in self: + rec.edi_exchange_ready = rec._edi_exchange_ready() + + def _edi_exchange_ready(self): + return not self._is_delivery() and not self.display_type + + @api.model_create_multi + def create(self, vals_list): + # Set default origin if not passed + for vals in vals_list: + orig_id = vals.get("origin_exchange_record_id") + if not orig_id and "order_id" in vals: + order = self.env["sale.order"].browse(vals["order_id"]) + vals["origin_exchange_record_id"] = order.origin_exchange_record_id.id + return super().create(vals_list) diff --git a/edi_sale_oca/pyproject.toml b/edi_sale_oca/pyproject.toml new file mode 100644 index 000000000..4231d0ccc --- /dev/null +++ b/edi_sale_oca/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/edi_sale_oca/readme/CONTRIBUTORS.md b/edi_sale_oca/readme/CONTRIBUTORS.md new file mode 100644 index 000000000..da1e3c10d --- /dev/null +++ b/edi_sale_oca/readme/CONTRIBUTORS.md @@ -0,0 +1,4 @@ +- Simone Orsi \<\> +- Duong (Tran Quoc) \<\> +- Thien (Vo Hong) \<\> +- Italo Lopes \<\> diff --git a/edi_sale_oca/readme/CREDITS.md b/edi_sale_oca/readme/CREDITS.md new file mode 100644 index 000000000..ae28e03a1 --- /dev/null +++ b/edi_sale_oca/readme/CREDITS.md @@ -0,0 +1,2 @@ +The migration of this module from 14.0 to 16.0 was financially supported +by Camptocamp. diff --git a/edi_sale_oca/readme/DESCRIPTION.md b/edi_sale_oca/readme/DESCRIPTION.md new file mode 100644 index 000000000..3ab8f0f87 --- /dev/null +++ b/edi_sale_oca/readme/DESCRIPTION.md @@ -0,0 +1,7 @@ +Handle sale orders via EDI. + +This is a base module to plug sales processes with the EDI framework. + +To handle inbound/outbound sale orders, you need to use +edi_sale_input_oca or edi_sale_output_oca modules, or create your own +modules. diff --git a/edi_sale_oca/static/description/icon.png b/edi_sale_oca/static/description/icon.png new file mode 100644 index 000000000..3a0328b51 Binary files /dev/null and b/edi_sale_oca/static/description/icon.png differ diff --git a/edi_sale_oca/static/description/index.html b/edi_sale_oca/static/description/index.html new file mode 100644 index 000000000..ed59bdef8 --- /dev/null +++ b/edi_sale_oca/static/description/index.html @@ -0,0 +1,449 @@ + + + + + +README.rst + + + +
+ + + +Odoo Community Association + +
+

EDI Sales

+ +

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

+

Handle sale orders via EDI.

+

This is a base module to plug sales processes with the EDI framework.

+

To handle inbound/outbound sale orders, you need to use +edi_sale_input_oca or edi_sale_output_oca modules, or create your own +modules.

+
+

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

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • Camptocamp
  • +
+
+
+

Contributors

+ +
+
+

Other credits

+

The migration of this module from 14.0 to 16.0 was financially supported +by Camptocamp.

+
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

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

+

Current maintainer:

+

simahawk

+

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_sale_oca/tests/__init__.py b/edi_sale_oca/tests/__init__.py new file mode 100644 index 000000000..975f7f795 --- /dev/null +++ b/edi_sale_oca/tests/__init__.py @@ -0,0 +1,2 @@ +from . import test_order +from . import test_generate diff --git a/edi_sale_oca/tests/common.py b/edi_sale_oca/tests/common.py new file mode 100644 index 000000000..3d89aa630 --- /dev/null +++ b/edi_sale_oca/tests/common.py @@ -0,0 +1,100 @@ +# Copyright 2022 Camptocamp SA +# @author: Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields +from odoo.fields import Domain + +from odoo.addons.edi_core_oca.tests.common import EDIBackendTestMixin + + +class SaleEDIBackendTestMixin(EDIBackendTestMixin): + @classmethod + def _get_backend_type(cls): + backend_type = cls.env["edi.backend.type"].search( + Domain([("code", "=", "sale_demo")]), limit=1 + ) + if backend_type: + return backend_type + return cls.env["edi.backend.type"].create( + {"name": "Sale DEMO", "code": "sale_demo"} + ) + + @classmethod + def _get_backend(cls): + backend_type = cls._get_backend_type() + backend = cls.env["edi.backend"].search( + Domain([("backend_type_id", "=", backend_type.id)]), limit=1 + ) + if backend: + return backend + return cls.env["edi.backend"].create( + {"name": "Sale DEMO", "backend_type_id": backend_type.id} + ) + + @classmethod + def _create_exchange_type(cls, **kw): + model = cls.env["edi.exchange.type"] + code = kw.get("code") + if code: + exchange_type = model.search( + Domain([("code", "=", code), ("backend_id", "=", cls.backend.id)]), + limit=1, + ) + if exchange_type: + return exchange_type + return super()._create_exchange_type(**kw) + + +class OrderMixin: + @classmethod + def _setup_order_records(cls): + cls.sale_partner = cls.env["res.partner"].create({"name": "Test Sale Customer"}) + cls.product_a = cls.env["product.product"].create( + {"name": "Product A", "sale_ok": True, "barcode": "1" * 14} + ) + cls.product_b = cls.env["product.product"].create( + {"name": "Product B", "sale_ok": True, "barcode": "2" * 14} + ) + cls.product_c = cls.env["product.product"].create( + {"name": "Product C", "sale_ok": True, "barcode": "3" * 14} + ) + cls.product_d = cls.env["product.product"].create( + {"name": "Product D", "sale_ok": True, "barcode": "4" * 14} + ) + + @classmethod + def _create_sale_order(cls, **kw): + model = cls.env["sale.order"] + vals = dict(commitment_date=fields.Date.today()) + vals.update(kw) + # Loose dependency on onchange_helper + if hasattr(model, "play_onchanges"): + so_vals = model.play_onchanges(vals, []) + else: + so_vals = vals.copy() + if "order_line" in so_vals: + so_vals["order_line"] = [(0, 0, x) for x in vals["order_line"]] + return model.create(so_vals) + + @classmethod + def _setup_order(cls, **kw): + line_defaults = kw.pop("line_defaults", {}) + vals = { + "partner_id": cls.sale_partner.id, + "commitment_date": "2022-07-29", + } + vals.update(kw) + if "client_order_ref" not in vals: + vals["client_order_ref"] = "ABC123" + vals["order_line"] = [ + {"product_id": cls.product_a.id, "product_uom_qty": 300, "edi_id": 1000}, + {"product_id": cls.product_b.id, "product_uom_qty": 200, "edi_id": 2000}, + {"product_id": cls.product_c.id, "product_uom_qty": 100, "edi_id": 3000}, + ] + if line_defaults: + for line in vals["order_line"]: + line.update(line_defaults) + sale = cls._create_sale_order(**vals) + sale.action_confirm() + return sale diff --git a/edi_sale_oca/tests/test_generate.py b/edi_sale_oca/tests/test_generate.py new file mode 100644 index 000000000..b91e5c425 --- /dev/null +++ b/edi_sale_oca/tests/test_generate.py @@ -0,0 +1,89 @@ +# Copyright 2024 Camptocamp SA +# @author: Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.tests.common import TransactionCase + +from .common import SaleEDIBackendTestMixin + + +class TestGenerateViaConf(TransactionCase, SaleEDIBackendTestMixin): + """Verify that sale EDI generation is driven by ``edi.configuration``. + + No component / no fake handler: we simply assert that the snippets bound + to the partner via ``partner_id.edi_sale_conf_ids`` are executed by + the state-change event dispatched by ``edi.exchange.consumer.mixin``. + + Each snippet writes a marker on ``conf.description`` so we can verify + which configurations actually ran. + """ + + _snippet_tpl = ( + "if record.state == '{state}':\n" + " conf.write({{'description': " + "(conf.description or '') + '|' + record.state}})" + ) + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) + cls._setup_env() + cls._setup_records() + + cls.exc_type = cls._create_exchange_type( + name="Demo Sale Order out", + code="demo_SaleOrder_out", + direction="output", + exchange_filename_pattern="{record_name}-{type.code}-{dt}", + exchange_file_ext="xml", + ) + cls.state_change_trigger = cls.env.ref( + "edi_sale_oca.edi_conf_trigger_sale_order_state_change" + ) + sale_model_id = cls.env["ir.model"]._get_id("sale.order") + cls.edi_conf_confirmed = cls.env["edi.configuration"].create( + { + "name": "Demo Sale Order - order confirmed", + "type_id": cls.exc_type.id, + "backend_id": cls.backend.id, + "model_id": sale_model_id, + "trigger_id": cls.state_change_trigger.id, + "snippet_do": cls._snippet_tpl.format(state="sale"), + } + ) + cls.edi_conf_done = cls.env["edi.configuration"].create( + { + "name": "Demo Sale Order - order cancelled", + "type_id": cls.exc_type.id, + "backend_id": cls.backend.id, + "model_id": sale_model_id, + "trigger_id": cls.state_change_trigger.id, + "snippet_do": cls._snippet_tpl.format(state="cancel"), + } + ) + cls.partner = cls.env["res.partner"].create({"name": "John Doe"}) + + def test_new_order_no_conf_no_output(self): + order = self.env["sale.order"].create({"partner_id": self.partner.id}) + order.action_confirm() + self.assertFalse(self.edi_conf_confirmed.description) + self.assertFalse(self.edi_conf_done.description) + + def test_new_order_1conf_output(self): + self.partner.edi_sale_conf_ids = self.edi_conf_confirmed + order = self.env["sale.order"].create({"partner_id": self.partner.id}) + self.assertFalse(self.edi_conf_confirmed.description) + order.action_confirm() + self.assertEqual(self.edi_conf_confirmed.description, "|sale") + self.assertFalse(self.edi_conf_done.description) + + def test_new_order_2conf_output(self): + self.partner.edi_sale_conf_ids = self.edi_conf_confirmed | self.edi_conf_done + order = self.env["sale.order"].create({"partner_id": self.partner.id}) + order.action_confirm() + self.assertEqual(self.edi_conf_confirmed.description, "|sale") + self.assertFalse(self.edi_conf_done.description) + order._action_cancel() + self.assertEqual(self.edi_conf_confirmed.description, "|sale") + self.assertEqual(self.edi_conf_done.description, "|cancel") diff --git a/edi_sale_oca/tests/test_order.py b/edi_sale_oca/tests/test_order.py new file mode 100644 index 000000000..b9597c5f3 --- /dev/null +++ b/edi_sale_oca/tests/test_order.py @@ -0,0 +1,49 @@ +# Copyright 2022 Camptocamp SA +# @author: Simone Orsi +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.tests.common import TransactionCase + +from .common import OrderMixin, SaleEDIBackendTestMixin + + +class TestOrder(TransactionCase, SaleEDIBackendTestMixin, OrderMixin): + @classmethod + def setUpClass(cls): + super().setUpClass() + # force metadata storage w/ proper key + cls.env = cls.env(context=dict(cls.env.context, edi_framework_action=True)) + cls._setup_records() + cls.exchange_type_in.exchange_filename_pattern = "{record.id}-{type.code}-{dt}" + cls.exc_record_in = cls.backend.create_record( + cls.exchange_type_in.code, {"edi_exchange_state": "input_received"} + ) + cls._setup_order_records() + cls.order = cls._setup_order( + origin_exchange_record_id=cls.exc_record_in.id, + ) + + def test_line_origin(self): + order = self.order + self.assertEqual(order.origin_exchange_record_id, self.exc_record_in) + lines = order.order_line + self.env["sale.order.line"].create( + [ + { + "order_id": order.id, + "product_id": self.product_d.id, + "product_uom_qty": 300, + "edi_id": 4000, + }, + { + "order_id": order.id, + "product_id": self.product_d.id, + "product_uom_qty": 400, + "edi_id": 5000, + }, + ] + ) + order.invalidate_recordset() + new_line1, new_line2 = order.order_line - lines + self.assertEqual(new_line1.origin_exchange_record_id, self.exc_record_in) + self.assertEqual(new_line2.origin_exchange_record_id, self.exc_record_in) diff --git a/edi_sale_oca/views/edi_exchange_record.xml b/edi_sale_oca/views/edi_exchange_record.xml new file mode 100644 index 000000000..0eae3c5e5 --- /dev/null +++ b/edi_sale_oca/views/edi_exchange_record.xml @@ -0,0 +1,29 @@ + + + + + Sale Order Exchange Records + ir.actions.act_window + edi.exchange.record + list,form + [('model', '=', 'sale.order')] + {} + + + + diff --git a/edi_sale_oca/views/res_partner.xml b/edi_sale_oca/views/res_partner.xml new file mode 100644 index 000000000..8075fae39 --- /dev/null +++ b/edi_sale_oca/views/res_partner.xml @@ -0,0 +1,24 @@ + + + + + res.partner.view.form + res.partner + + + + + + + + + + + + + + + + + diff --git a/edi_sale_oca/views/sale_order.xml b/edi_sale_oca/views/sale_order.xml new file mode 100644 index 000000000..2133035f3 --- /dev/null +++ b/edi_sale_oca/views/sale_order.xml @@ -0,0 +1,99 @@ + + + + + sale.order.form (in edi_sale) + sale.order + + + + + + + + + + + + + + + + + + + + + + + + + + + + + sale.order.tree (in edi_sale) + sale.order + + + + + + + + + + + sale.order.search (in edi_sale) + sale.order + + + + + + + + + + + + + + + +