From 54ee9100ce413a0120d392d668d19a2e986ea4d4 Mon Sep 17 00:00:00 2001 From: Guido Galetto Date: Wed, 25 Feb 2026 10:15:10 -0300 Subject: [PATCH 1/6] [ADD] product_catalog_tree: the attribute 'optional = hide' is added to the product_catalog_price field --- product_catalog_tree/views/product_product_views.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/product_catalog_tree/views/product_product_views.xml b/product_catalog_tree/views/product_product_views.xml index 47219fa3c..e65baf961 100644 --- a/product_catalog_tree/views/product_product_views.xml +++ b/product_catalog_tree/views/product_product_views.xml @@ -21,7 +21,7 @@ - + From 85dcce9db842c99ab6cbfbe1913834c228849384 Mon Sep 17 00:00:00 2001 From: Guido Galetto Date: Wed, 25 Feb 2026 10:21:28 -0300 Subject: [PATCH 2/6] [ADD] product_ux: the attribute 'optional = hide' is added to the pricelist_price field --- product_ux/views/product_product_views.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/product_ux/views/product_product_views.xml b/product_ux/views/product_product_views.xml index 90a0bd03c..4410d35bf 100644 --- a/product_ux/views/product_product_views.xml +++ b/product_ux/views/product_product_views.xml @@ -8,7 +8,7 @@ - + From d70dd8fd74fd6a3ef9b00b3f60c50da692d957ea Mon Sep 17 00:00:00 2001 From: Guido Galetto Date: Wed, 25 Feb 2026 10:27:35 -0300 Subject: [PATCH 3/6] [ADD] product_price_taxes_included: the attribute 'optional = hide' is added to the taxed_lst_price field --- .../views/product_product_views.xml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/product_price_taxes_included/views/product_product_views.xml b/product_price_taxes_included/views/product_product_views.xml index 563118f65..6887810b4 100644 --- a/product_price_taxes_included/views/product_product_views.xml +++ b/product_price_taxes_included/views/product_product_views.xml @@ -7,7 +7,11 @@ - + + From 46eaa0327ea4123866f58ac684e63725e9ae820f Mon Sep 17 00:00:00 2001 From: Guido Galetto Date: Wed, 4 Mar 2026 15:54:33 +0000 Subject: [PATCH 4/6] [ADD] product_catalog_tree: show prices with taxes included --- product_catalog_tree/__manifest__.py | 2 + product_catalog_tree/models/__init__.py | 1 + .../models/product_product.py | 56 +++++++++++++++++++ .../models/sale_order_line.py | 26 +++++++++ .../views/product_product_views.xml | 20 +++++++ 5 files changed, 105 insertions(+) create mode 100644 product_catalog_tree/models/sale_order_line.py diff --git a/product_catalog_tree/__manifest__.py b/product_catalog_tree/__manifest__.py index 0ae097572..cee11519e 100644 --- a/product_catalog_tree/__manifest__.py +++ b/product_catalog_tree/__manifest__.py @@ -30,6 +30,8 @@ "depends": [ "product", "stock", + "account", + "sale", ], "data": [ "views/product_product_views.xml", diff --git a/product_catalog_tree/models/__init__.py b/product_catalog_tree/models/__init__.py index f816d6174..958f867a5 100644 --- a/product_catalog_tree/models/__init__.py +++ b/product_catalog_tree/models/__init__.py @@ -4,3 +4,4 @@ ############################################################################## from . import product_product from . import product_catalog_mixin +from . import sale_order_line diff --git a/product_catalog_tree/models/product_product.py b/product_catalog_tree/models/product_product.py index a47c6aa59..69e98de52 100644 --- a/product_catalog_tree/models/product_product.py +++ b/product_catalog_tree/models/product_product.py @@ -25,6 +25,11 @@ class ProductProduct(models.Model): compute="_compute_catalog_supplier_uom", readonly=True, ) + product_catalog_price_taxed = fields.Float( + string="Order Price with taxes", + compute="_compute_product_catalog_price_taxed", + readonly=True, + ) def _compute_catalog_supplier_uom(self): """Obtener la UoM del proveedor si estamos en una orden de compra""" @@ -46,6 +51,57 @@ def _compute_catalog_supplier_uom(self): if seller and seller.product_uom_id: rec.product_catalog_supplier_uom = seller.product_uom_id.name + @api.depends("product_tmpl_id.taxes_id", "product_catalog_price") + @api.depends_context("company", "company_id", "order_id") + def _compute_product_catalog_price_taxed(self): + """Calcula product_catalog_price con impuestos incluidos. + Toma los impuestos de las líneas de orden si están disponibles, de lo contrario + usa los impuestos por defecto del producto aplicando la posición fiscal. + """ + res_model = self.env.context.get("product_catalog_order_model") + order_id = self.env.context.get("order_id") + + for rec in self: + if not rec.product_catalog_price: + rec.product_catalog_price_taxed = 0.0 + continue + + # Obtener moneda, partner y compañía del contexto de la orden si está disponible + if res_model and order_id: + order = self.env[res_model].browse(order_id) + currency = order.currency_id if hasattr(order, "currency_id") else self.env.company.currency_id + partner = order.partner_id if hasattr(order, "partner_id") else self.env["res.partner"] + company_id = order.company_id.id if hasattr(order, "company_id") else self.env.company.id + + # Intentar obtener impuestos de las líneas de orden existentes para este producto + taxes = self.env["account.tax"] + if hasattr(order, "order_line"): + order_lines = order.order_line.filtered(lambda l: l.product_id.id == rec.id and not l.display_type) + if order_lines: + # Usar impuestos de la primera línea de orden (deberían ser iguales para el mismo producto) + if hasattr(order_lines[0], "tax_ids"): + taxes = order_lines[0].tax_ids + + # Si no existen líneas de orden aún, obtener impuestos del producto y aplicar posición fiscal + if not taxes: + taxes = rec.taxes_id.filtered(lambda x: x.company_id.id == company_id) + # Aplicar posición fiscal si existe + if hasattr(order, "fiscal_position_id") and order.fiscal_position_id: + taxes = order.fiscal_position_id.map_tax(taxes) + else: + currency = self.env.company.currency_id + partner = self.env["res.partner"] + company_id = self.env.context.get("company_id", self.env.company.id) + taxes = rec.taxes_id.filtered(lambda x: x.company_id.id == company_id) + + if taxes: + tax_result = taxes.sudo().compute_all( + rec.product_catalog_price, currency=currency, quantity=1.0, product=rec, partner=partner + ) + rec.product_catalog_price_taxed = tax_result["total_included"] + else: + rec.product_catalog_price_taxed = rec.product_catalog_price + def write(self, vals): """ Si en vals solo viene product_catalog_qty hacemos esto para que usuarios sin permiso de escribir diff --git a/product_catalog_tree/models/sale_order_line.py b/product_catalog_tree/models/sale_order_line.py new file mode 100644 index 000000000..8489794ef --- /dev/null +++ b/product_catalog_tree/models/sale_order_line.py @@ -0,0 +1,26 @@ +############################################################################## +# For copyright and license notices, see __manifest__.py file in module root +# directory +############################################################################## +from odoo import models + + +class SaleOrderLine(models.Model): + _inherit = "sale.order.line" + + def write(self, vals): + """Invalida el caché del catálogo de productos cuando cambian los impuestos en líneas de orden.""" + # Guardar impuestos antiguos para comparar + old_taxes_map = {line.id: line.tax_ids.ids for line in self} + res = super().write(vals) + + if "tax_ids" in vals: + # Verificar qué líneas realmente cambiaron los impuestos + changed_lines = self.filtered(lambda l: old_taxes_map.get(l.id) != l.tax_ids.ids) + if changed_lines: + products = changed_lines.mapped("product_id") + if products: + # Invalidar caché y marcar como modificado para forzar recálculo + products.invalidate_recordset(["product_catalog_price_taxed"]) + products.modified(["product_catalog_price_taxed"]) + return res diff --git a/product_catalog_tree/views/product_product_views.xml b/product_catalog_tree/views/product_product_views.xml index e65baf961..85bb03840 100644 --- a/product_catalog_tree/views/product_product_views.xml +++ b/product_catalog_tree/views/product_product_views.xml @@ -22,6 +22,7 @@ + @@ -35,4 +36,23 @@ + + product.view.kanban.catalog + product.product + + + + + + + + +
+
+ With taxes: +
+
+
+
+ From 7c1b1639d4728bb04a34031f12231d75c671c6a1 Mon Sep 17 00:00:00 2001 From: Guido Galetto Date: Fri, 20 Mar 2026 18:44:39 +0000 Subject: [PATCH 5/6] [IMP] product_catalog_tree: simplify price taxed compute using product taxes + fiscal position --- product_catalog_tree/models/__init__.py | 1 - .../models/product_product.py | 25 +++++------------- .../models/sale_order_line.py | 26 ------------------- 3 files changed, 7 insertions(+), 45 deletions(-) delete mode 100644 product_catalog_tree/models/sale_order_line.py diff --git a/product_catalog_tree/models/__init__.py b/product_catalog_tree/models/__init__.py index 958f867a5..f816d6174 100644 --- a/product_catalog_tree/models/__init__.py +++ b/product_catalog_tree/models/__init__.py @@ -4,4 +4,3 @@ ############################################################################## from . import product_product from . import product_catalog_mixin -from . import sale_order_line diff --git a/product_catalog_tree/models/product_product.py b/product_catalog_tree/models/product_product.py index 69e98de52..6598360e6 100644 --- a/product_catalog_tree/models/product_product.py +++ b/product_catalog_tree/models/product_product.py @@ -52,11 +52,11 @@ def _compute_catalog_supplier_uom(self): rec.product_catalog_supplier_uom = seller.product_uom_id.name @api.depends("product_tmpl_id.taxes_id", "product_catalog_price") - @api.depends_context("company", "company_id", "order_id") + @api.depends_context("company", "company_id", "order_id", "product_catalog_order_model") def _compute_product_catalog_price_taxed(self): """Calcula product_catalog_price con impuestos incluidos. - Toma los impuestos de las líneas de orden si están disponibles, de lo contrario - usa los impuestos por defecto del producto aplicando la posición fiscal. + Usa los impuestos del producto aplicando la posición fiscal si existe, + igual que el resto de los campos de precio en Odoo. """ res_model = self.env.context.get("product_catalog_order_model") order_id = self.env.context.get("order_id") @@ -73,21 +73,10 @@ def _compute_product_catalog_price_taxed(self): partner = order.partner_id if hasattr(order, "partner_id") else self.env["res.partner"] company_id = order.company_id.id if hasattr(order, "company_id") else self.env.company.id - # Intentar obtener impuestos de las líneas de orden existentes para este producto - taxes = self.env["account.tax"] - if hasattr(order, "order_line"): - order_lines = order.order_line.filtered(lambda l: l.product_id.id == rec.id and not l.display_type) - if order_lines: - # Usar impuestos de la primera línea de orden (deberían ser iguales para el mismo producto) - if hasattr(order_lines[0], "tax_ids"): - taxes = order_lines[0].tax_ids - - # Si no existen líneas de orden aún, obtener impuestos del producto y aplicar posición fiscal - if not taxes: - taxes = rec.taxes_id.filtered(lambda x: x.company_id.id == company_id) - # Aplicar posición fiscal si existe - if hasattr(order, "fiscal_position_id") and order.fiscal_position_id: - taxes = order.fiscal_position_id.map_tax(taxes) + # Obtener impuestos del producto y aplicar posición fiscal (patrón estándar de Odoo) + taxes = rec.taxes_id.filtered(lambda x: x.company_id.id == company_id) + if hasattr(order, "fiscal_position_id") and order.fiscal_position_id: + taxes = order.fiscal_position_id.map_tax(taxes) else: currency = self.env.company.currency_id partner = self.env["res.partner"] diff --git a/product_catalog_tree/models/sale_order_line.py b/product_catalog_tree/models/sale_order_line.py deleted file mode 100644 index 8489794ef..000000000 --- a/product_catalog_tree/models/sale_order_line.py +++ /dev/null @@ -1,26 +0,0 @@ -############################################################################## -# For copyright and license notices, see __manifest__.py file in module root -# directory -############################################################################## -from odoo import models - - -class SaleOrderLine(models.Model): - _inherit = "sale.order.line" - - def write(self, vals): - """Invalida el caché del catálogo de productos cuando cambian los impuestos en líneas de orden.""" - # Guardar impuestos antiguos para comparar - old_taxes_map = {line.id: line.tax_ids.ids for line in self} - res = super().write(vals) - - if "tax_ids" in vals: - # Verificar qué líneas realmente cambiaron los impuestos - changed_lines = self.filtered(lambda l: old_taxes_map.get(l.id) != l.tax_ids.ids) - if changed_lines: - products = changed_lines.mapped("product_id") - if products: - # Invalidar caché y marcar como modificado para forzar recálculo - products.invalidate_recordset(["product_catalog_price_taxed"]) - products.modified(["product_catalog_price_taxed"]) - return res From 1673939298a68e445334c95e3207e2a96c970d50 Mon Sep 17 00:00:00 2001 From: Guido Galetto Date: Mon, 20 Apr 2026 13:55:04 +0000 Subject: [PATCH 6/6] [BOT] retrigger tests