From 89ef075344edafeda8b0b690d7eff5d4a2dc4794 Mon Sep 17 00:00:00 2001 From: Eric Antones Date: Mon, 22 Jan 2024 02:16:53 +0100 Subject: [PATCH 1/8] [FIX] connector_oxigesti: removing items from Odoo did not mark the corresponding records in Oxigesti as deprecated --- .../models/product_pricelist_item/__init__.py | 1 + .../models/product_pricelist_item/adapter.py | 2 +- .../models/product_pricelist_item/binding.py | 71 +++++++++++++++- .../product_pricelist_item/export_mapper.py | 2 +- .../models/product_pricelist_item/exporter.py | 83 +++++++++++-------- .../models/product_pricelist_item/listener.py | 22 +++++ .../views/product_pricelist_item_view.xml | 19 +++++ 7 files changed, 163 insertions(+), 37 deletions(-) create mode 100644 connector_oxigesti/models/product_pricelist_item/listener.py diff --git a/connector_oxigesti/models/product_pricelist_item/__init__.py b/connector_oxigesti/models/product_pricelist_item/__init__.py index 43d4796fa..470152118 100644 --- a/connector_oxigesti/models/product_pricelist_item/__init__.py +++ b/connector_oxigesti/models/product_pricelist_item/__init__.py @@ -3,3 +3,4 @@ from . import export_mapper from . import binder from . import binding +from . import listener diff --git a/connector_oxigesti/models/product_pricelist_item/adapter.py b/connector_oxigesti/models/product_pricelist_item/adapter.py index fb6c7293e..0eb795fea 100644 --- a/connector_oxigesti/models/product_pricelist_item/adapter.py +++ b/connector_oxigesti/models/product_pricelist_item/adapter.py @@ -11,7 +11,7 @@ class ProductPricelistItemAdapter(Component): _apply_on = "oxigesti.product.pricelist.item" - _sql = """select b.CodigoArticulo, b.Codigo_Mutua, b.Importe + _sql = """select b.CodigoArticulo, b.Codigo_Mutua, b.Importe, b.Deprecated from %(schema)s.Odoo_Articulos_Generales_x_Cliente b """ diff --git a/connector_oxigesti/models/product_pricelist_item/binding.py b/connector_oxigesti/models/product_pricelist_item/binding.py index e11179a6e..8dcf3538f 100644 --- a/connector_oxigesti/models/product_pricelist_item/binding.py +++ b/connector_oxigesti/models/product_pricelist_item/binding.py @@ -1,8 +1,28 @@ # Copyright NuoBiT Solutions - Eric Antones # Copyright NuoBiT Solutions - Kilian Niubo +# Copyright NuoBiT Solutions - Frank Cespedes # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) -from odoo import api, fields, models +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class Pricelist(models.Model): + _inherit = "product.pricelist" + + # TODO: Review use deleter to mark items as deprecated + def unlink(self): + for rec in self: + if rec.item_ids.oxigesti_bind_ids: + raise ValidationError( + _( + "You can't delete the pricelist %s that has been exported " + "the product prices by customer to Oxigesti. If you want to" + "delete it, you must first delete the items of the pricelist." + ) + % rec.name + ) + return super().unlink() class ProductPricelistItem(models.Model): @@ -14,6 +34,41 @@ class ProductPricelistItem(models.Model): string="Oxigesti Bindings", ) + @api.constrains("compute_price", "applied_on", "product_tmpl_id") + def _check_binding(self): + for rec in self: + if rec.oxigesti_bind_ids: + partners = ( + self.env["res.partner"] + .search( + [ + ("company_id", "=?", rec.company_id.id), + ] + ) + .filtered( + lambda x: x.property_product_pricelist == rec.pricelist_id + ) + ) + if partners: + raise ValidationError( + _( + "You can't change the product, the price calculation " + "method or the applied on field of a pricelist item " + "because they have been exported the product prices by " + "customer to Oxigesti.\nIf you need to change any of " + "these fields, you can delete the pricelist item and " + "create a new one." + ) + ) + + def is_deprecated(self, partner): + self.ensure_one() + return ( + partner.property_product_pricelist != self.pricelist_id + or not self.active + or not self.product_tmpl_id.active + ) + class ProductPricelistItemBinding(models.Model): _name = "oxigesti.product.pricelist.item" @@ -31,6 +86,20 @@ class ProductPricelistItemBinding(models.Model): odoo_partner_id = fields.Many2one( comodel_name="res.partner", string="Partner", required=True, ondelete="cascade" ) + + deprecated = fields.Boolean( + required=True, + compute="_compute_deprecated", + store=True, + ) + + @api.depends( + "active", "product_tmpl_id.active", "odoo_partner_id.property_product_pricelist" + ) + def _compute_deprecated(self): + for rec in self: + rec.deprecated = rec.odoo_id.is_deprecated(rec.odoo_partner_id) + _sql_constraints = [ ( "oxigesti_external_uniq", diff --git a/connector_oxigesti/models/product_pricelist_item/export_mapper.py b/connector_oxigesti/models/product_pricelist_item/export_mapper.py index dc324b212..2089840c9 100644 --- a/connector_oxigesti/models/product_pricelist_item/export_mapper.py +++ b/connector_oxigesti/models/product_pricelist_item/export_mapper.py @@ -63,4 +63,4 @@ def Codigo_Mutua(self, record): @mapping def Deprecated(self, record): - return {"Deprecated": 0} + return {"Deprecated": record.deprecated and 1 or 0} diff --git a/connector_oxigesti/models/product_pricelist_item/exporter.py b/connector_oxigesti/models/product_pricelist_item/exporter.py index 40a1e8770..a187f469d 100644 --- a/connector_oxigesti/models/product_pricelist_item/exporter.py +++ b/connector_oxigesti/models/product_pricelist_item/exporter.py @@ -3,20 +3,18 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) -from odoo.addons.component.core import Component +from odoo.addons.component.core import AbstractComponent, Component -class ProductPricelistItemBatchExporter(Component): +class ProductPricelistItemBatchExporter(AbstractComponent): """Export the Oxigesti Product prices. For every product in the list, a delayed job is created. """ - _name = "oxigesti.product.pricelist.item.delayed.batch.exporter" - _inherit = "oxigesti.delayed.batch.exporter" - _apply_on = "oxigesti.product.pricelist.item" + _name = "oxigesti.product.pricelist.item.batch.exporter" - def run(self, domain=None): + def run_common(self, domain=None): if not domain: domain = [] # Run the batch synchronization @@ -32,47 +30,64 @@ def run(self, domain=None): since_date = value else: domain.append(e) - partner_adapter = self.component( - usage="backend.adapter", model_name="oxigesti.res.partner" - ) - partner_binder = self.binder_for("oxigesti.res.partner") binder = self.binder_for(self.model._name) for p in self.env["res.partner"].search(domain): - partner_external_id = partner_binder.to_external(p, wrap=True) - for pl in p.property_product_pricelist.item_ids.filtered( + pricelist_items = p.property_product_pricelist.item_ids.filtered( lambda x: ( not since_date or x.write_date > since_date or p.write_date > since_date ) - and p.customer_rank >= p.supplier_rank and x.applied_on == "1_product" and x.compute_price == "fixed" and ( not x.sudo().product_tmpl_id.company_id or x.sudo().product_tmpl_id.company_id == p.company_id ) - ): - if pl.product_tmpl_id.default_code and partner_external_id: - external_id = [ - pl.product_tmpl_id.default_code, - partner_adapter.id2dict(partner_external_id)["Codigo_Mutua"], - ] - binding = binder.to_internal(external_id) - if binding and binding.odoo_id != pl: - old_binding = binder._find_binding( - pl, binding_extra_vals={"odoo_partner_id": p.id} - ) - if old_binding: - old_binding.unlink() - binding.odoo_id = pl - binding = binder.wrap_binding( - pl, - binding_extra_vals={ - "odoo_partner_id": p.id, - }, - ) - self._export_record(binding) + ) + if pricelist_items: + # create/update binding data + for pl in pricelist_items: + binding = binder.wrap_binding( + pl, + binding_extra_vals={ + "odoo_partner_id": p.id, + "deprecated": pl.is_deprecated(p), + }, + ) + self._export_record(binding) + + +class ProductPricelistItemDelayedBatchExporter(Component): + """Export the Oxigesti Product prices. + + For every product in the list, a delayed job is created. + """ + + _name = "oxigesti.product.pricelist.item.delayed.batch.exporter" + _inherit = [ + "oxigesti.delayed.batch.exporter", + "oxigesti.product.pricelist.item.batch.exporter", + ] + + def run(self, domain=None): + super().run_common(domain=domain) + + +class ProductPricelistItemDirectBatchExporter(Component): + """Export the Oxigesti Product prices. + + For every product in the list, a direct job is created. + """ + + _name = "oxigesti.product.pricelist.item.direct.batch.exporter" + _inherit = [ + "oxigesti.direct.batch.exporter", + "oxigesti.product.pricelist.item.batch.exporter", + ] + + def run(self, domain=None): + super().run_common(domain=domain) class ProductPricelistItemExporter(Component): diff --git a/connector_oxigesti/models/product_pricelist_item/listener.py b/connector_oxigesti/models/product_pricelist_item/listener.py new file mode 100644 index 000000000..6cded2102 --- /dev/null +++ b/connector_oxigesti/models/product_pricelist_item/listener.py @@ -0,0 +1,22 @@ +# Copyright NuoBiT Solutions - Frank Cespedes +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo.addons.component.core import Component + + +class ProductPricelistItemListener(Component): + _name = "oxigesti.product.pricelist.item.listener" + _inherit = "oxigesti.event.listener" + + _apply_on = "product.pricelist.item" + + def on_record_unlink(self, relation): + bindings = relation.sudo().oxigesti_bind_ids + bindings.deprecated = True + for backend in bindings.backend_id: + with backend.work_on(bindings._name) as work: + exporter = work.component(usage="direct.batch.exporter") + partners = bindings.filtered( + lambda x: x.backend_id == backend + ).odoo_partner_id + exporter.run([("id", "=", partners.ids)]) diff --git a/connector_oxigesti/views/product_pricelist_item_view.xml b/connector_oxigesti/views/product_pricelist_item_view.xml index fc3fa56f4..5a590a613 100644 --- a/connector_oxigesti/views/product_pricelist_item_view.xml +++ b/connector_oxigesti/views/product_pricelist_item_view.xml @@ -2,6 +2,23 @@ + + product.pricelist.item.oxigesti.connector.form + product.pricelist.item + + + + + + + + + + + + + + oxigesti.product.pricelist.item.form oxigesti.product.pricelist.item @@ -13,6 +30,7 @@ + @@ -28,6 +46,7 @@ +