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" + ) 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 @@ - + +

- € / + € /