diff --git a/l10n_uy_ux/models/account_move.py b/l10n_uy_ux/models/account_move.py index 500606e8..bb6b53b8 100644 --- a/l10n_uy_ux/models/account_move.py +++ b/l10n_uy_ux/models/account_move.py @@ -302,6 +302,25 @@ def _compute_l10n_latam_document_number(self): name = name.split(" ")[-1] rec.l10n_latam_document_number = name + def _l10n_uy_edi_get_line_nom_and_desc(self, aml): + """ + Sobrescribimos este método que devuelve el valor de NomItem y DscItem para cada línea del comprobante, + para utilizar siempre la descripción de la línea (aml.name) como está en Odoo nativo, ya que a veces se quiere + modificar el valor a mostrar en la factura manteniendo el producto en la línea. + """ + # B7 NomItem, B8 DscItem + nom_item = aml.name and aml.name[:80] or "-" + description = aml.name and aml.name[80:] or "" + + if aml.l10n_uy_edi_addenda_ids: + adenda = [ + " {%s}" % addenda.content if addenda.is_legend else " " + addenda.content + for addenda in aml.l10n_uy_edi_addenda_ids + ] + description += "".join(adenda) + + return nom_item, description + # Nuevos metodos def action_l10n_uy_get_pdf(self): diff --git a/l10n_uy_ux/tests/test_l10n_uy_ux.py b/l10n_uy_ux/tests/test_l10n_uy_ux.py index 7ed0eedc..4424fdb9 100644 --- a/l10n_uy_ux/tests/test_l10n_uy_ux.py +++ b/l10n_uy_ux/tests/test_l10n_uy_ux.py @@ -1,10 +1,11 @@ from odoo import Command from odoo.addons.l10n_uy_edi.tests.common import TestUyEdi from odoo.exceptions import UserError +from odoo.tests import Form from odoo.tests.common import tagged -@tagged("-at_install", "post_install", "post_install_l10n", "ux") +@tagged("-at_install", "post_install", "post_install_l10n", "l10n_uy_ux") class TestUx(TestUyEdi): @classmethod def setUpClass(self): @@ -120,3 +121,100 @@ def test_with_automatic_report_params(self): }, ), ) + + def test_110_account_move_line_nom_and_desc(self): + """Test account move with varied products and descriptions""" + long_name = "Product name for testing purposes that intentionally contains more than eighty chars" + + products = [ + self.env["product.product"].create({"name": "Product Without Description"}), + self.env["product.product"].create({"name": "Product With Description"}), + self.env["product.product"].create({"name": long_name}), + ] + + addenda = self.env["l10n_uy_edi.addenda"].create( + { + "content": "Addenda Content", + "is_legend": False, + } + ) + + # Define invoice lines data to create + invoice_line_data = [ + {"product_id": products[0], "name": None, "addenda_ids": []}, # Without description + {"product_id": products[1], "name": "Custom Description", "addenda_ids": []}, # With description + {"product_id": products[2], "name": None, "addenda_ids": []}, # +80 chars without description + {"product_id": products[2], "name": "Custom Description", "addenda_ids": []}, # +80 chars with description + {"product_id": products[2], "name": None, "addenda_ids": [addenda]}, # +80 chars with addenda + { + "product_id": products[2], + "name": "Custom Description", + "addenda_ids": [addenda], + }, # +80 chars with desc and addenda + {"product_id": products[0], "name": False, "addenda_ids": []}, # With description deleted by user + {"product_id": products[0], "name": "", "addenda_ids": []}, # With a empty string as description + ] + + # Create invoice using Form to simulate UI interaction + with Form(self.env["account.move"].with_context(default_move_type="out_invoice")) as invoice_form: + invoice_form.partner_id = self.partner_local + for line_data in invoice_line_data: + with invoice_form.invoice_line_ids.new() as line_form: + line_form.product_id = line_data["product_id"] + # Only override name if explicitly provided (not None) + if line_data["name"] is not None: + line_form.name = line_data["name"] + # Add addenda if provided + if line_data["addenda_ids"]: + line_form.l10n_uy_edi_addenda_ids.clear() + for addenda_rec in line_data["addenda_ids"]: + line_form.l10n_uy_edi_addenda_ids.add(addenda_rec) + + invoice = invoice_form.save() + + for idx, line in enumerate(invoice.invoice_line_ids): + nom_item, description = invoice._l10n_uy_edi_get_line_nom_and_desc(line) + if idx == 0: # Without description + self.assertEqual( + nom_item, "Product Without Description"[:80], "NomItem mismatch for line without description" + ) + self.assertEqual(description, "", "Description mismatch for line without description") + elif idx == 1: # With custom description (aml.name is the custom description) + self.assertEqual(nom_item, "Custom Description"[:80], "NomItem mismatch for line with description") + self.assertEqual(description, "", "Description mismatch for line with description") + elif idx == 2: # +80 chars without description + self.assertEqual( + nom_item, long_name[:80], "NomItem mismatch for line with long name without description" + ) + self.assertEqual( + description, long_name[80:], "Description mismatch for line with long name without description" + ) + elif idx == 3: # +80 chars with custom description (aml.name is the custom description) + self.assertEqual( + nom_item, "Custom Description"[:80], "NomItem mismatch for line with long name and description" + ) + self.assertEqual( + description, + "", + "Description mismatch for line with long name and description", + ) + elif idx == 4: # +80 chars with addenda + self.assertEqual(nom_item, long_name[:80], "NomItem mismatch for line with long name and addenda") + self.assertIn( + "Addenda Content", description, "Addenda content missing in description for line with addenda" + ) + elif idx == 5: # +80 chars with custom description and addenda (aml.name is the custom description) + self.assertEqual( + nom_item, + "Custom Description"[:80], + "NomItem mismatch for line with long name, description, and addenda", + ) + # When custom description is set, description should contain the addenda + self.assertIn( + "Addenda Content", + description, + "Addenda content missing in description for line with desc and addenda", + ) + elif idx == 6 or idx == 7: # With description deleted by user or "" (aml.name is False or empty) + self.assertEqual(nom_item, "-", "NomItem mismatch for line with deleted description") + self.assertEqual(description, "", "Description mismatch for line with deleted description") diff --git a/l10n_uy_ux/tests/test_patch_l10n_uy_edi.py b/l10n_uy_ux/tests/test_patch_l10n_uy_edi.py index a315d863..6cbfdb7f 100644 --- a/l10n_uy_ux/tests/test_patch_l10n_uy_edi.py +++ b/l10n_uy_ux/tests/test_patch_l10n_uy_edi.py @@ -50,6 +50,15 @@ def test_default_doc_type_by_id_patch(self): original_method = getattr(TestManual, "test_default_doc_type_by_id").origin original_method(self) + def test_110_account_move_line_nom_and_desc_patch(self): + """Test skipped: l10n_uy_ux intentionally modifies the line name logic for DGI submission. + + The core l10n_uy_edi test expects that line.name is used to send data to DGI, but l10n_uy_ux + modifies this behavior to use the product name instead. This is an intentional change, + so we skip this test in l10n_uy_ux. + """ + self.skipTest("l10n_uy_ux changes the line name logic for DGI submission") + def propagate(method1, method2): if method1: for attr in ("_returns",): @@ -76,6 +85,7 @@ def _skip_method(cls, name, method): test_120_e_ticket_final_consumer_patch, ) _patch_method(TestManual, "test_default_doc_type_by_id", test_default_doc_type_by_id_patch) + _patch_method(TestManual, "test_110_account_move_line_nom_and_desc", test_110_account_move_line_nom_and_desc_patch) _skip_method( TestAccountMoveSend, "test_download_with_existing_cfe",