diff --git a/README.md b/README.md index 0fccb7e26..ee496644b 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,9 @@ addon | version | maintainers | summary [edi_account_core_oca](edi_account_core_oca/) | 18.0.1.1.1 | etobella | Define EDI Configuration for Account Moves [edi_account_oca](edi_account_oca/) | 18.0.1.1.1 | etobella | Define some component listeners for Account Moves [edi_component_oca](edi_component_oca/) | 18.0.1.1.0 | simahawk etobella | Allow to use Connector as a source in EDI -[edi_core_oca](edi_core_oca/) | 18.0.1.7.2 | simahawk etobella | Define backends, exchange types, exchange records, basic automation and views for handling EDI exchanges. +[edi_core_oca](edi_core_oca/) | 18.0.1.7.3 | simahawk etobella | Define backends, exchange types, exchange records, basic automation and views for handling EDI exchanges. [edi_endpoint_oca](edi_endpoint_oca/) | 18.0.1.0.3 | | Base module allowing configuration of custom endpoints for EDI framework. +[edi_exchange_deduplicate_oca](edi_exchange_deduplicate_oca/) | 18.0.1.0.0 | simahawk etobella | Introduce a deduplication mechanism at the sending step [edi_exchange_template_oca](edi_exchange_template_oca/) | 18.0.1.3.3 | simahawk | Allows definition of exchanges via templates. [edi_exchange_template_party_data](edi_exchange_template_party_data/) | 18.0.1.0.1 | simahawk | Glue module between edi_exchange_template and edi_party_data [edi_notification_oca](edi_notification_oca/) | 18.0.1.0.0 | | Define notification activities on exchange records. diff --git a/edi_core_oca/README.rst b/edi_core_oca/README.rst index a73b1e222..1746c1441 100644 --- a/edi_core_oca/README.rst +++ b/edi_core_oca/README.rst @@ -11,7 +11,7 @@ EDI !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:5e54bc58f7c88ff2eaac2207226e25488a4aa47b800e1cdc606f485cf0523830 + !! source digest: sha256:a2ec0c8c9a701363efa1965a3296fbc0972859cf5c22504865d391c74d13ab86 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png diff --git a/edi_core_oca/__manifest__.py b/edi_core_oca/__manifest__.py index 31955b191..ddd8abbcd 100644 --- a/edi_core_oca/__manifest__.py +++ b/edi_core_oca/__manifest__.py @@ -9,7 +9,7 @@ Define backends, exchange types, exchange records, basic automation and views for handling EDI exchanges. """, - "version": "18.0.1.7.2", + "version": "18.0.1.7.3", "website": "https://github.com/OCA/edi-framework", "development_status": "Beta", "license": "LGPL-3", diff --git a/edi_core_oca/models/edi_exchange_type.py b/edi_core_oca/models/edi_exchange_type.py index 3346e487f..4eaa92824 100644 --- a/edi_core_oca/models/edi_exchange_type.py +++ b/edi_core_oca/models/edi_exchange_type.py @@ -158,6 +158,7 @@ class EDIExchangeType(models.Model): rule_ids = fields.One2many( comodel_name="edi.exchange.type.rule", inverse_name="type_id", + context={"active_test": False}, help="Rules to handle exchanges and UI automatically", ) quick_exec = fields.Boolean( diff --git a/edi_core_oca/static/description/index.html b/edi_core_oca/static/description/index.html index 7c70c56f1..014b14ba2 100644 --- a/edi_core_oca/static/description/index.html +++ b/edi_core_oca/static/description/index.html @@ -372,7 +372,7 @@

EDI

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:5e54bc58f7c88ff2eaac2207226e25488a4aa47b800e1cdc606f485cf0523830 +!! source digest: sha256:a2ec0c8c9a701363efa1965a3296fbc0972859cf5c22504865d391c74d13ab86 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

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

Base EDI backend.

diff --git a/edi_core_oca/tests/test_exchange_type.py b/edi_core_oca/tests/test_exchange_type.py index 5d3ae33bb..753c0b652 100644 --- a/edi_core_oca/tests/test_exchange_type.py +++ b/edi_core_oca/tests/test_exchange_type.py @@ -5,6 +5,7 @@ from freezegun import freeze_time +from odoo.fields import Command from odoo.tools import mute_logger from .common import EDIBackendCommonTestCase @@ -147,26 +148,71 @@ def test_filename_pattern_settings(self): self._test_exchange_filename("Test-File-0000001.csv") def test_archive_rules(self): - exc_type = self.exchange_type_out - rule1 = exc_type.rule_ids.create( - { - "type_id": exc_type.id, - "name": "Fake partner rule", - "model_id": self.env["ir.model"]._get("res.partner").id, - } - ) - rule2 = exc_type.rule_ids.create( + # Make sure to drop the ``active_test`` flag to be able to properly test + # whether archived rules can be found in the exchange type O2M field + ctx = dict(self.env.context) + ctx.pop("active_test", None) + exc_type = self.exchange_type_out.with_context(ctx) # pylint: disable=W8121 + exc_type.write( { - "type_id": exc_type.id, - "name": "Fake user rule", - "model_id": self.env["ir.model"]._get("res.users").id, + "rule_ids": [ + Command.clear(), # Drop preexisting rules to avoid pollution + Command.create( + { + "name": "Fake partner rule", + "model_id": self.env["ir.model"]._get("res.partner").id, + } + ), + Command.create( + { + "name": "Fake user rule", + "model_id": self.env["ir.model"]._get("res.users").id, + } + ), + ] } ) - exc_type.active = False - rule1.invalidate_recordset() - rule2.invalidate_recordset() - self.assertFalse(rule1.active) - self.assertFalse(rule2.active) + rules = rule_1, rule_2 = exc_type.rule_ids + + def _check_exc_type_rule_ids(): + exc_type.invalidate_recordset(["rule_ids"]) + self.assertEqual(exc_type.rule_ids, rules) + + # Make sure both Exc Type and all its rules are active + self.assertTrue(exc_type.active) + self.assertTrue(rule_1.active) + self.assertTrue(rule_2.active) + _check_exc_type_rule_ids() + + # Archive one of the rules, make sure the Exc Type and the other rule stay + # active, and the archived rule is still found in the Exc Type O2M field + rule_1.action_archive() + self.assertTrue(exc_type.active) + self.assertFalse(rule_1.active) + self.assertTrue(rule_2.active) + _check_exc_type_rule_ids() + + # Archive the Exc Type, make sure both rules are archived, and they both are + # still found in the Exc Type O2M field + exc_type.action_archive() + self.assertFalse(exc_type.active) + self.assertFalse(rule_1.active) + self.assertFalse(rule_2.active) + _check_exc_type_rule_ids() + + # Reactivate the Exc Type, make sure both rules are still archived, and they + # both are still found in the Exc Type O2M field + exc_type.action_unarchive() + self.assertTrue(exc_type.active) + self.assertFalse(rule_1.active) + self.assertFalse(rule_2.active) + _check_exc_type_rule_ids() + + # Force ``active_test`` in record ctx => archived rules are found anyway + # (record context does not override field context) + for value in (True, False): + exc_type = exc_type.with_context(active_test=value) + _check_exc_type_rule_ids() def _create_exchange_record(self, exc_type): return self.backend.create_record( diff --git a/edi_core_oca/views/edi_exchange_type_views.xml b/edi_core_oca/views/edi_exchange_type_views.xml index cddc4a36b..67c2e97c1 100644 --- a/edi_core_oca/views/edi_exchange_type_views.xml +++ b/edi_core_oca/views/edi_exchange_type_views.xml @@ -209,14 +209,7 @@ list,form [] - - {'search_default_filter_all': 1, 'active_test': False} + {'search_default_filter_all': 1} Config -> Exchange Type". + +Enable "Deduplicate on Send" option -> Enable "Delete obsolete records" +option. + +Usage +===== + +With all the types that have been enabled "Deduplicate on Send" option, +this module will check their records if a fresher one does not exist for +the same record. If so, mark the oldest one as obsolete (except +"block_obsolescence" records) + +- "block_obsolescence" is an technical option on records to avoid + marking them as obsolete. + +With all the types that have been enabled "Delete obsolete records" +option, the cron will remove their obsolete records. + +- If the records are obsolete, delete them even if their type's flag has + been disabled. + +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) + +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 +.. |maintainer-etobella| image:: https://github.com/etobella.png?size=40px + :target: https://github.com/etobella + :alt: etobella + +Current `maintainers `__: + +|maintainer-simahawk| |maintainer-etobella| + +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_exchange_deduplicate_oca/__init__.py b/edi_exchange_deduplicate_oca/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/edi_exchange_deduplicate_oca/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/edi_exchange_deduplicate_oca/__manifest__.py b/edi_exchange_deduplicate_oca/__manifest__.py new file mode 100644 index 000000000..8576d5b5e --- /dev/null +++ b/edi_exchange_deduplicate_oca/__manifest__.py @@ -0,0 +1,19 @@ +# Copyright 2024 Camptocamp +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +{ + "name": "Edi Exchange Deduplicate OCA", + "summary": """ + Introduce a deduplication mechanism at the sending step""", + "version": "18.0.1.0.0", + "license": "LGPL-3", + "author": "Camptocamp,Odoo Community Association (OCA)", + "maintainers": ["simahawk", "etobella"], + "website": "https://github.com/OCA/edi-framework", + "depends": ["edi_core_oca"], + "data": [ + "data/cron.xml", + "views/edi_exchange_type_views.xml", + ], + "demo": [], +} diff --git a/edi_exchange_deduplicate_oca/data/cron.xml b/edi_exchange_deduplicate_oca/data/cron.xml new file mode 100644 index 000000000..02e9eb208 --- /dev/null +++ b/edi_exchange_deduplicate_oca/data/cron.xml @@ -0,0 +1,17 @@ + + + + EDI exchange delete obsolete records + + + 1 + days + + code + model.search([])._cron_delete_obsolete_records() + + diff --git a/edi_exchange_deduplicate_oca/i18n/edi_exchange_deduplicate_oca.pot b/edi_exchange_deduplicate_oca/i18n/edi_exchange_deduplicate_oca.pot new file mode 100644 index 000000000..1dabac0d3 --- /dev/null +++ b/edi_exchange_deduplicate_oca/i18n/edi_exchange_deduplicate_oca.pot @@ -0,0 +1,76 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * edi_exchange_deduplicate_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_exchange_deduplicate_oca +#: model:ir.model.fields,help:edi_exchange_deduplicate_oca.field_edi_exchange_type__deduplicate_on_send +msgid "" +"Before sending an exchange record, check if a fresher one does not exist for" +" same record; if so, mark oldest one as obsolete." +msgstr "" + +#. module: edi_exchange_deduplicate_oca +#: model:ir.model.fields,field_description:edi_exchange_deduplicate_oca.field_edi_exchange_record__block_obsolescence +msgid "Block Obsolescence" +msgstr "" + +#. module: edi_exchange_deduplicate_oca +#: model:ir.model.fields,field_description:edi_exchange_deduplicate_oca.field_edi_exchange_type__deduplicate_on_send +msgid "Deduplicate on Send" +msgstr "" + +#. module: edi_exchange_deduplicate_oca +#: model:ir.model.fields,field_description:edi_exchange_deduplicate_oca.field_edi_exchange_type__delete_obsolete_records +msgid "Delete obsolete records" +msgstr "" + +#. module: edi_exchange_deduplicate_oca +#: model:ir.model.fields,help:edi_exchange_deduplicate_oca.field_edi_exchange_type__delete_obsolete_records +msgid "Delete records marked as obsolete." +msgstr "" + +#. module: edi_exchange_deduplicate_oca +#: model:ir.model,name:edi_exchange_deduplicate_oca.model_edi_backend +msgid "EDI Backend" +msgstr "" + +#. module: edi_exchange_deduplicate_oca +#: model:ir.model,name:edi_exchange_deduplicate_oca.model_edi_exchange_type +msgid "EDI Exchange Type" +msgstr "" + +#. module: edi_exchange_deduplicate_oca +#: model:ir.model,name:edi_exchange_deduplicate_oca.model_edi_exchange_record +msgid "EDI exchange Record" +msgstr "" + +#. module: edi_exchange_deduplicate_oca +#: model:ir.actions.server,name:edi_exchange_deduplicate_oca.cron_edi_backend_delete_obsolete_records_ir_actions_server +msgid "EDI exchange delete obsolete records" +msgstr "" + +#. module: edi_exchange_deduplicate_oca +#: model:ir.model.fields,field_description:edi_exchange_deduplicate_oca.field_edi_exchange_record__edi_exchange_state +msgid "Exchange state" +msgstr "" + +#. module: edi_exchange_deduplicate_oca +#: model:ir.model.fields,help:edi_exchange_deduplicate_oca.field_edi_exchange_record__block_obsolescence +msgid "Flag record that can never be marked as obsolete" +msgstr "" + +#. module: edi_exchange_deduplicate_oca +#: model:ir.model.fields.selection,name:edi_exchange_deduplicate_oca.selection__edi_exchange_record__edi_exchange_state__obsolete +msgid "Obsolete" +msgstr "" diff --git a/edi_exchange_deduplicate_oca/i18n/it.po b/edi_exchange_deduplicate_oca/i18n/it.po new file mode 100644 index 000000000..efddbf985 --- /dev/null +++ b/edi_exchange_deduplicate_oca/i18n/it.po @@ -0,0 +1,82 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * edi_exchange_deduplicate_oca +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2024-08-07 17:58+0000\n" +"Last-Translator: mymage \n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.6.2\n" + +#. module: edi_exchange_deduplicate_oca +#: model:ir.model.fields,help:edi_exchange_deduplicate_oca.field_edi_exchange_type__deduplicate_on_send +msgid "" +"Before sending an exchange record, check if a fresher one does not exist for" +" same record; if so, mark oldest one as obsolete." +msgstr "" +"Prima di inviare un record di scambio, controlla se ne esiste uno più " +"recente per lo stesso record; se così, marca il vecchio come obsoleto." + +#. module: edi_exchange_deduplicate_oca +#: model:ir.model.fields,field_description:edi_exchange_deduplicate_oca.field_edi_exchange_record__block_obsolescence +msgid "Block Obsolescence" +msgstr "Obsolescenza blocco" + +#. module: edi_exchange_deduplicate_oca +#: model:ir.model.fields,field_description:edi_exchange_deduplicate_oca.field_edi_exchange_type__deduplicate_on_send +msgid "Deduplicate on Send" +msgstr "Duplica alla spedizione" + +#. module: edi_exchange_deduplicate_oca +#: model:ir.model.fields,field_description:edi_exchange_deduplicate_oca.field_edi_exchange_type__delete_obsolete_records +msgid "Delete obsolete records" +msgstr "Cancella record obsoleti" + +#. module: edi_exchange_deduplicate_oca +#: model:ir.model.fields,help:edi_exchange_deduplicate_oca.field_edi_exchange_type__delete_obsolete_records +msgid "Delete records marked as obsolete." +msgstr "Cancella record marcati come obsoleti." + +#. module: edi_exchange_deduplicate_oca +#: model:ir.model,name:edi_exchange_deduplicate_oca.model_edi_backend +msgid "EDI Backend" +msgstr "Backend EDI" + +#. module: edi_exchange_deduplicate_oca +#: model:ir.model,name:edi_exchange_deduplicate_oca.model_edi_exchange_type +msgid "EDI Exchange Type" +msgstr "Tipo scambio EDI" + +#. module: edi_exchange_deduplicate_oca +#: model:ir.model,name:edi_exchange_deduplicate_oca.model_edi_exchange_record +msgid "EDI exchange Record" +msgstr "Record di scambio EDI" + +#. module: edi_exchange_deduplicate_oca +#: model:ir.actions.server,name:edi_exchange_deduplicate_oca.cron_edi_backend_delete_obsolete_records_ir_actions_server +#: model:ir.cron,cron_name:edi_exchange_deduplicate_oca.cron_edi_backend_delete_obsolete_records +msgid "EDI exchange delete obsolete records" +msgstr "Scambio EDI cancella record obsoleti" + +#. module: edi_exchange_deduplicate_oca +#: model:ir.model.fields,field_description:edi_exchange_deduplicate_oca.field_edi_exchange_record__edi_exchange_state +msgid "Exchange state" +msgstr "Stato scambio" + +#. module: edi_exchange_deduplicate_oca +#: model:ir.model.fields,help:edi_exchange_deduplicate_oca.field_edi_exchange_record__block_obsolescence +msgid "Flag record that can never be marked as obsolete" +msgstr "Segna record che non devono mai essere marcati obsoleti" + +#. module: edi_exchange_deduplicate_oca +#: model:ir.model.fields.selection,name:edi_exchange_deduplicate_oca.selection__edi_exchange_record__edi_exchange_state__obsolete +msgid "Obsolete" +msgstr "Obsoleto" diff --git a/edi_exchange_deduplicate_oca/models/__init__.py b/edi_exchange_deduplicate_oca/models/__init__.py new file mode 100644 index 000000000..ddad285da --- /dev/null +++ b/edi_exchange_deduplicate_oca/models/__init__.py @@ -0,0 +1,3 @@ +from . import edi_backend +from . import edi_exchange_type +from . import edi_exchange_record diff --git a/edi_exchange_deduplicate_oca/models/edi_backend.py b/edi_exchange_deduplicate_oca/models/edi_backend.py new file mode 100644 index 000000000..12a75a380 --- /dev/null +++ b/edi_exchange_deduplicate_oca/models/edi_backend.py @@ -0,0 +1,48 @@ +# Copyright 2024 Camptocamp +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +import logging + +from odoo import models + +_logger = logging.getLogger(__name__) + + +class EDIBackend(models.Model): + _inherit = "edi.backend" + + def _failed_output_check_send_msg(self): + return "Nothing to do. Likely already sent or obsolete." + + def _cron_delete_obsolete_records(self, **kw): + for backend in self: + backend._delete_obsolete_records(**kw) + + def _delete_obsolete_records(self, record_ids=None, **kw): + """Cleanup obsolete records. + + Go through types with `delete_obsolete_records` flag on + and delete their obsolete records if any. + """ + obsolete_records = self.exchange_record_model.search( + self._obsolete_records_domain(record_ids=record_ids) + ) + _logger.info( + "EDI Exchange delete records: found %d obsolete records to delete.", + len(obsolete_records), + ) + if obsolete_records: + obsolete_records.unlink() + + def _obsolete_records_domain(self, record_ids=None): + """ + Domain for obsolete records need to delete. + If the record is obsolete, delete it even if the type's flag has been disabled. + """ + domain = [ + ("backend_id", "=", self.id), + ("edi_exchange_state", "=", "obsolete"), + ] + if record_ids: + domain.append(("id", "in", record_ids)) + return domain diff --git a/edi_exchange_deduplicate_oca/models/edi_exchange_record.py b/edi_exchange_deduplicate_oca/models/edi_exchange_record.py new file mode 100644 index 000000000..dcbb3f096 --- /dev/null +++ b/edi_exchange_deduplicate_oca/models/edi_exchange_record.py @@ -0,0 +1,51 @@ +# Copyright 2024 Camptocamp +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from odoo import api, fields, models + + +class EDIExchangeRecord(models.Model): + _inherit = "edi.exchange.record" + + edi_exchange_state = fields.Selection( + selection_add=[ + ("obsolete", "Obsolete"), + ], + ondelete={"obsolete": "cascade"}, + ) + block_obsolescence = fields.Boolean( + default=False, + help="Flag record that can never be marked as obsolete", + ) + + @api.constrains("edi_exchange_state") + def _constrain_edi_exchange_state(self): + # Remove `obsolete` record for this check + self = self.filtered(lambda r: r.edi_exchange_state != "obsolete") + return super()._constrain_edi_exchange_state() + + @api.model_create_multi + def create(self, vals_list): + records = super().create(vals_list) + for rec in records: + check_obsoleted_record = ( + rec.type_id.direction == "output" and rec.type_id.deduplicate_on_send + ) + if check_obsoleted_record: + obsoleted_records = rec._edi_get_duplicates() + if obsoleted_records: + obsoleted_records.edi_exchange_state = "obsolete" + return records + + def _edi_get_duplicates(self, count=False): + self.ensure_one() + return (self.search_count if count else self.search)( + [ + ("id", "<", self.id), + ("res_id", "=", self.res_id), + ("model", "=", self.model), + ("type_id", "=", self.type_id.id), + ("edi_exchange_state", "in", ("new", "output_pending")), + ("block_obsolescence", "=", False), + ], + ) diff --git a/edi_exchange_deduplicate_oca/models/edi_exchange_type.py b/edi_exchange_deduplicate_oca/models/edi_exchange_type.py new file mode 100644 index 000000000..80fcbd7d8 --- /dev/null +++ b/edi_exchange_deduplicate_oca/models/edi_exchange_type.py @@ -0,0 +1,20 @@ +# Copyright 2024 Camptocamp +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class EDIExchangeType(models.Model): + _inherit = "edi.exchange.type" + + deduplicate_on_send = fields.Boolean( + string="Deduplicate on Send", + default=False, + help="Before sending an exchange record, check if a fresher one does not " + "exist for same record; if so, mark oldest one as obsolete.", + ) + delete_obsolete_records = fields.Boolean( + string="Delete obsolete records", + default=True, + help="Delete records marked as obsolete.", + ) diff --git a/edi_exchange_deduplicate_oca/pyproject.toml b/edi_exchange_deduplicate_oca/pyproject.toml new file mode 100644 index 000000000..4231d0ccc --- /dev/null +++ b/edi_exchange_deduplicate_oca/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/edi_exchange_deduplicate_oca/readme/CONFIGURE.md b/edi_exchange_deduplicate_oca/readme/CONFIGURE.md new file mode 100644 index 000000000..fd2d3271d --- /dev/null +++ b/edi_exchange_deduplicate_oca/readme/CONFIGURE.md @@ -0,0 +1,4 @@ +Go to "EDI -\> Config -\> Exchange Type". + +Enable "Deduplicate on Send" option -\> Enable "Delete obsolete records" +option. diff --git a/edi_exchange_deduplicate_oca/readme/CONTRIBUTORS.md b/edi_exchange_deduplicate_oca/readme/CONTRIBUTORS.md new file mode 100644 index 000000000..faee38dc0 --- /dev/null +++ b/edi_exchange_deduplicate_oca/readme/CONTRIBUTORS.md @@ -0,0 +1,2 @@ +- Simone Orsi \ +- Duong (Tran Quoc) \ diff --git a/edi_exchange_deduplicate_oca/readme/DESCRIPTION.md b/edi_exchange_deduplicate_oca/readme/DESCRIPTION.md new file mode 100644 index 000000000..434a47858 --- /dev/null +++ b/edi_exchange_deduplicate_oca/readme/DESCRIPTION.md @@ -0,0 +1,4 @@ +This module adds options for deduplication records before sending step on type: +- deduplicate_on_send: check if a fresher one does not exist for the + same record. If so, mark the oldest one as obsolete. +- delete_obsolete_records: Delete records marked as obsolete. diff --git a/edi_exchange_deduplicate_oca/readme/USAGE.md b/edi_exchange_deduplicate_oca/readme/USAGE.md new file mode 100644 index 000000000..70fcdfdbd --- /dev/null +++ b/edi_exchange_deduplicate_oca/readme/USAGE.md @@ -0,0 +1,7 @@ +With all the types that have been enabled "Deduplicate on Send" option, this module will check their records if a fresher one does not exist for the same record. If so, mark the oldest one as obsolete (except "block_obsolescence" records) +- "block_obsolescence" is an technical option on records to avoid + marking them as obsolete. + +With all the types that have been enabled "Delete obsolete records" option, the cron will remove their obsolete records. +- If the records are obsolete, delete them even if their type's flag has + been disabled. diff --git a/edi_exchange_deduplicate_oca/static/description/icon.png b/edi_exchange_deduplicate_oca/static/description/icon.png new file mode 100644 index 000000000..3a0328b51 Binary files /dev/null and b/edi_exchange_deduplicate_oca/static/description/icon.png differ diff --git a/edi_exchange_deduplicate_oca/static/description/index.html b/edi_exchange_deduplicate_oca/static/description/index.html new file mode 100644 index 000000000..f8a806666 --- /dev/null +++ b/edi_exchange_deduplicate_oca/static/description/index.html @@ -0,0 +1,463 @@ + + + + + +README.rst + + + +
+ + + +Odoo Community Association + +
+

Edi Exchange Deduplicate OCA

+ +

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

+

This module adds options for deduplication records before sending step +on type:

+
    +
  • deduplicate_on_send: check if a fresher one does not exist for the +same record. If so, mark the oldest one as obsolete.
  • +
  • delete_obsolete_records: Delete records marked as obsolete.
  • +
+

Table of contents

+ +
+

Configuration

+

Go to “EDI -> Config -> Exchange Type”.

+

Enable “Deduplicate on Send” option -> Enable “Delete obsolete records” +option.

+
+
+

Usage

+

With all the types that have been enabled “Deduplicate on Send” option, +this module will check their records if a fresher one does not exist for +the same record. If so, mark the oldest one as obsolete (except +“block_obsolescence” records)

+
    +
  • “block_obsolescence” is an technical option on records to avoid +marking them as obsolete.
  • +
+

With all the types that have been enabled “Delete obsolete records” +option, the cron will remove their obsolete records.

+
    +
  • If the records are obsolete, delete them even if their type’s flag has +been disabled.
  • +
+
+
+

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

+ +
+
+

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 maintainers:

+

simahawk etobella

+

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_exchange_deduplicate_oca/tests/__init__.py b/edi_exchange_deduplicate_oca/tests/__init__.py new file mode 100644 index 000000000..c4bb24c72 --- /dev/null +++ b/edi_exchange_deduplicate_oca/tests/__init__.py @@ -0,0 +1,2 @@ +from . import test_edi_backend_cron +from . import test_edi_duplicate diff --git a/edi_exchange_deduplicate_oca/tests/test_edi_backend_cron.py b/edi_exchange_deduplicate_oca/tests/test_edi_backend_cron.py new file mode 100644 index 000000000..3162001be --- /dev/null +++ b/edi_exchange_deduplicate_oca/tests/test_edi_backend_cron.py @@ -0,0 +1,39 @@ +# Copyright 2024 Camptocamp +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from odoo.tools import mute_logger + +from odoo.addons.edi_core_oca.tests.test_edi_backend_cron import EDIBackendTestCronCase + +LOGGERS = ("odoo.addons.edi_core_oca.models.edi_backend", "odoo.addons.queue_job.delay") + + +class EDIBackendTestCronDeduplicationCase(EDIBackendTestCronCase): + @mute_logger(*LOGGERS) + def test_exchange_delete_obsolete_records(self): + self.exchange_type_out.write( + { + "exchange_file_auto_generate": True, + "deduplicate_on_send": True, + "delete_obsolete_records": True, + } + ) + record1_1 = self.backend.create_record( + "test_csv_output", {"model": self.partner._name, "res_id": self.partner.id} + ) + record1_2 = self.backend.create_record( + "test_csv_output", {"model": self.partner._name, "res_id": self.partner.id} + ) + record1_3 = self.backend.create_record( + "test_csv_output", {"model": self.partner._name, "res_id": self.partner.id} + ) + # all the older records should have been obsolete by record1_3 + records = self.record1 + record1_1 + record1_2 + self.backend._check_output_exchange_sync() + for record in records: + self.assertEqual(record.edi_exchange_state, "obsolete") + self.assertEqual(record1_3.edi_exchange_state, "output_sent") + self.backend._delete_obsolete_records() + for record in records: + self.assertFalse(record.exists()) + self.assertTrue(record1_3.exists()) diff --git a/edi_exchange_deduplicate_oca/tests/test_edi_duplicate.py b/edi_exchange_deduplicate_oca/tests/test_edi_duplicate.py new file mode 100644 index 000000000..2273e51d8 --- /dev/null +++ b/edi_exchange_deduplicate_oca/tests/test_edi_duplicate.py @@ -0,0 +1,144 @@ +# Copyright 2024 Camptocamp +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from odoo_test_helper import FakeModelLoader + +from odoo.tools import mute_logger + +from odoo.addons.edi_core_oca.tests.common import EDIBackendCommonTestCase + +LOGGERS = ( + "odoo.addons.edi_core_oca.models.edi_backend", + "odoo.addons.queue_job.delay", +) + + +class EDIDeduplicateTestCase(EDIBackendCommonTestCase): + def setUp(self): + super().setUp() + self.loader = FakeModelLoader(self.env, self.__module__) + self.loader.backup_registry() + from odoo.addons.edi_core_oca.tests.fake_models import EdiTestExecution + + self.loader.update_registry((EdiTestExecution,)) + self.model = self.env["ir.model"].search( + [("model", "=", "edi.framework.test.execution")] + ) + self.exchange_type_out.write( + { + "exchange_file_auto_generate": True, + "generate_model_id": self.model.id, + "send_model_id": self.model.id, + "output_validate_model_id": self.model.id, + } + ) + + def tearDown(self): + self.loader.restore_registry() + super().tearDown() + + @mute_logger(*LOGGERS) + def test_deduplicate_on_send(self): + self.exchange_type_out.write( + { + "deduplicate_on_send": True, + } + ) + record1 = self.backend.create_record( + "test_csv_output", + { + "model": self.partner._name, + "res_id": self.partner.id, + }, + ) + record2 = self.backend.create_record( + "test_csv_output", + { + "model": self.partner._name, + "res_id": self.partner.id, + }, + ) + record3 = self.backend.create_record( + "test_csv_output", + { + "model": self.partner._name, + "res_id": self.partner.id, + }, + ) + records = record1 + record2 + self.backend._check_output_exchange_sync() + # Because we just sent the last record, so the others should be "obsolete" + for record in records: + self.assertEqual(record.edi_exchange_state, "obsolete") + self.assertEqual(record3.edi_exchange_state, "output_sent") + + @mute_logger(*LOGGERS) + def test_no_deduplicate_on_send(self): + self.exchange_type_out.write( + { + "deduplicate_on_send": False, + } + ) + record1 = self.backend.create_record( + "test_csv_output", + { + "model": self.partner._name, + "res_id": self.partner.id, + }, + ) + record2 = self.backend.create_record( + "test_csv_output", + { + "model": self.partner._name, + "res_id": self.partner.id, + }, + ) + record3 = self.backend.create_record( + "test_csv_output", + { + "model": self.partner._name, + "res_id": self.partner.id, + }, + ) + records = record1 + record2 + record3 + self.backend._check_output_exchange_sync() + # All the records should be "output_sent" + for record in records: + self.assertEqual(record.edi_exchange_state, "output_sent") + + @mute_logger(*LOGGERS) + def test_block_obsolescence(self): + self.exchange_type_out.write( + { + "deduplicate_on_send": True, + } + ) + record1 = self.backend.create_record( + "test_csv_output", + { + "model": self.partner._name, + "res_id": self.partner.id, + }, + ) + record2 = self.backend.create_record( + "test_csv_output", + { + "model": self.partner._name, + "res_id": self.partner.id, + # Checking + "block_obsolescence": True, + }, + ) + record3 = self.backend.create_record( + "test_csv_output", + { + "model": self.partner._name, + "res_id": self.partner.id, + }, + ) + self.backend._check_output_exchange_sync() + # Normally, record2 has been "obsolete" + # But with block_obsolescence = True, it will be "output_sent" too + self.assertEqual(record1.edi_exchange_state, "obsolete") + self.assertEqual(record2.edi_exchange_state, "output_sent") + self.assertEqual(record3.edi_exchange_state, "output_sent") diff --git a/edi_exchange_deduplicate_oca/views/edi_exchange_type_views.xml b/edi_exchange_deduplicate_oca/views/edi_exchange_type_views.xml new file mode 100644 index 000000000..0470b5255 --- /dev/null +++ b/edi_exchange_deduplicate_oca/views/edi_exchange_type_views.xml @@ -0,0 +1,17 @@ + + + + edi.exchange.type.form.inherit + edi.exchange.type + + + + + + + + + diff --git a/setup/_metapackage/pyproject.toml b/setup/_metapackage/pyproject.toml index abcb4d0a4..7c59f4867 100644 --- a/setup/_metapackage/pyproject.toml +++ b/setup/_metapackage/pyproject.toml @@ -1,12 +1,13 @@ [project] name = "odoo-addons-oca-edi-framework" -version = "18.0.20260524.0" +version = "18.0.20260609.0" dependencies = [ "odoo-addon-edi_account_core_oca==18.0.*", "odoo-addon-edi_account_oca==18.0.*", "odoo-addon-edi_component_oca==18.0.*", "odoo-addon-edi_core_oca==18.0.*", "odoo-addon-edi_endpoint_oca==18.0.*", + "odoo-addon-edi_exchange_deduplicate_oca==18.0.*", "odoo-addon-edi_exchange_template_oca==18.0.*", "odoo-addon-edi_exchange_template_party_data==18.0.*", "odoo-addon-edi_notification_oca==18.0.*",