From 5a6d9c989428db763e70c870fd493328339b4027 Mon Sep 17 00:00:00 2001 From: GitHub Copilot Date: Wed, 15 Jul 2026 18:23:11 +0200 Subject: [PATCH 1/2] [FIX] website_sale_aplicoop: precio por kg/litro con IVA y granel por litro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit El precio por unidad de las tarjetas usaba el campo nativo base_unit_price (list_price sin impuestos), mostrando un importe distinto al precio principal de la tarjeta, que sí incluye IVA. Ahora se calcula a partir del precio ya impositado. Además se extiende el sufijo de precio por unidad ("/L") a productos vendidos por litro, que ya tenían el step de 0.1 pero no el indicador visual. --- .../controllers/website_sale_pricing.py | 12 ++++++++++-- website_sale_aplicoop/views/website_templates.xml | 5 +++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/website_sale_aplicoop/controllers/website_sale_pricing.py b/website_sale_aplicoop/controllers/website_sale_pricing.py index 379baf8..9deef53 100644 --- a/website_sale_aplicoop/controllers/website_sale_pricing.py +++ b/website_sale_aplicoop/controllers/website_sale_pricing.py @@ -54,6 +54,10 @@ def _prepare_product_display_info(self, product, product_price_info, request_obj uom_category_name = "" quantity_step = 1 price_unit_suffix = "" + bulk_unit_suffixes = { + "uom.product_uom_categ_kgm": "/Kg", + "uom.product_uom_categ_vol": "/L", + } if product.uom_id: uom = product.uom_id.sudo() @@ -82,8 +86,7 @@ def _prepare_product_display_info(self, product, product_price_info, request_obj full_xmlid = f"{external_id.module}.{external_id.name}" if full_xmlid in fractional_categories: quantity_step = 0.1 - if full_xmlid == "uom.product_uom_categ_kgm": - price_unit_suffix = "/Kg" + price_unit_suffix = bulk_unit_suffixes.get(full_xmlid, "") except Exception as e: _logger.warning( "_prepare_product_display_info: Error detecting UoM category XML ID for product %s: %s", @@ -91,6 +94,10 @@ def _prepare_product_display_info(self, product, product_price_info, request_obj str(e), ) + base_unit_price = 0.0 + if product.base_unit_count and price_safe: + base_unit_price = price_safe / product.base_unit_count + try: req = request_obj or request tr_env = req.env @@ -106,6 +113,7 @@ def _prepare_product_display_info(self, product, product_price_info, request_obj "safe_uom_category": uom_category_name, "quantity_step": quantity_step, "price_unit_suffix": price_unit_suffix, + "base_unit_price": base_unit_price, "out_of_stock_label": out_of_stock_label, "add_to_cart_label": add_to_cart_label, } diff --git a/website_sale_aplicoop/views/website_templates.xml b/website_sale_aplicoop/views/website_templates.xml index dce168e..9754f29 100644 --- a/website_sale_aplicoop/views/website_templates.xml +++ b/website_sale_aplicoop/views/website_templates.xml @@ -693,9 +693,10 @@ - + +

- € / + € /

From 8bf9779d98bf08c52b799c4e1a0a4d340bfa87d9 Mon Sep 17 00:00:00 2001 From: GitHub Copilot Date: Fri, 31 Jul 2026 18:46:05 +0200 Subject: [PATCH 2/2] [FIX] l10n_es_edi_tbai_reagyp_recibidas: fix chaining within same invoice series TicketBAI maintains independent chains per invoice series (INV, RINV, etc). When building the chain for a new invoice, ensure it links to the previous valid document from the same series, not just the last document regardless of series. Fixes error "time data '' does not match format '%d-%m-%Y'" when previous document's XML is missing or when wrong series document is used for chaining. - Override _get_sale_values to validate series match - Add _get_last_chained_document_by_series to filter by series+state+xml - Skip cancelled documents and documents without valid XML Co-Authored-By: Claude Haiku 4.5 --- .../models/__init__.py | 2 + .../models/l10n_es_edi_tbai_document.py | 62 +++++++++++++++++++ .../models/res_company.py | 22 +++++++ 3 files changed, 86 insertions(+) create mode 100644 l10n_es_edi_tbai_reagyp_recibidas/models/l10n_es_edi_tbai_document.py create mode 100644 l10n_es_edi_tbai_reagyp_recibidas/models/res_company.py diff --git a/l10n_es_edi_tbai_reagyp_recibidas/models/__init__.py b/l10n_es_edi_tbai_reagyp_recibidas/models/__init__.py index e89b491..265dbff 100644 --- a/l10n_es_edi_tbai_reagyp_recibidas/models/__init__.py +++ b/l10n_es_edi_tbai_reagyp_recibidas/models/__init__.py @@ -1 +1,3 @@ from . import account_move # noqa: F401 +from . import res_company # noqa: F401 +from . import l10n_es_edi_tbai_document # noqa: F401 diff --git a/l10n_es_edi_tbai_reagyp_recibidas/models/l10n_es_edi_tbai_document.py b/l10n_es_edi_tbai_reagyp_recibidas/models/l10n_es_edi_tbai_document.py new file mode 100644 index 0000000..9f06ffa --- /dev/null +++ b/l10n_es_edi_tbai_reagyp_recibidas/models/l10n_es_edi_tbai_document.py @@ -0,0 +1,62 @@ +# Copyright 2026 - Today Criptomart +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import re + +from odoo import models + + +class L10nEsEdiTbaiDocument(models.Model): + _inherit = "l10n_es_edi_tbai.document" + + def _get_sale_values(self, values): + """Override to ensure chain links only within the same invoice series.""" + sale_values = super()._get_sale_values(values) + + # Replace chain_prev_document with one from the same series + chain_prev_document = sale_values.get("chain_prev_document") + if chain_prev_document: + current_series = self._get_series_prefix(self.name) + prev_series = self._get_series_prefix(chain_prev_document.name) + + # If series mismatch OR previous doc has no valid XML, find correct one + if ( + current_series != prev_series + or not chain_prev_document.xml_attachment_id + ): + same_series_doc = self._get_last_chained_document_by_series( + current_series + ) + sale_values["chain_prev_document"] = same_series_doc + + return sale_values + + @staticmethod + def _get_series_prefix(doc_name): + """Extract series prefix from document name (e.g., 'INV/2026/' from 'INV/2026/01248').""" + if not doc_name: + return "" + # Everything before the first digit + match = re.search(r"\d+", doc_name) + if match: + return doc_name[: match.start()].rstrip("/") + return doc_name + + def _get_last_chained_document_by_series(self, series_prefix): + """Find the last valid chained document matching a specific series prefix.""" + domain = [ + ("chain_index", "!=", 0), + ("company_id", "=", self.company_id.id), + ("is_cancel", "=", False), + ("state", "=", "accepted"), # Must be accepted in chain + ] + docs = self.search(domain, order="chain_index desc") + + # Return the first doc matching series that has valid XML + for doc in docs: + if ( + self._get_series_prefix(doc.name) == series_prefix + and doc.xml_attachment_id + ): + return doc + + return self.env["l10n_es_edi_tbai.document"] diff --git a/l10n_es_edi_tbai_reagyp_recibidas/models/res_company.py b/l10n_es_edi_tbai_reagyp_recibidas/models/res_company.py new file mode 100644 index 0000000..179e146 --- /dev/null +++ b/l10n_es_edi_tbai_reagyp_recibidas/models/res_company.py @@ -0,0 +1,22 @@ +# Copyright 2026 - Today Criptomart +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import models + + +class ResCompany(models.Model): + _inherit = "res.company" + + def _get_l10n_es_tbai_last_chained_document(self): + """ + Returns the last tbai document posted to this company's chain. + Each invoice series has its own independent chain in TicketBAI. + Filters out cancellation documents to skip cancelled invoices. + """ + domain = [ + ("chain_index", "!=", 0), + ("company_id", "=", self.id), + ("is_cancel", "=", False), + ] + return self.env["l10n_es_edi_tbai.document"].search( + domain, limit=1, order="chain_index desc" + )