From 3b5d90022118f4fe36d6fabf67b41294c0bb2d80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=CC=81=20Arturo=20Garci=CC=81a?= Date: Sun, 30 Aug 2026 18:28:13 -0400 Subject: [PATCH] Homologate shared fiscal behavior with Tryton 7.0 --- README.rst | 7 +++ invoice.py | 79 +++++++++++++--------------------- party.py | 2 - sequence.py | 11 ++++- tests/test_sequence_helpers.py | 14 ++++++ tox.ini | 2 +- tryton.cfg | 2 +- 7 files changed, 62 insertions(+), 55 deletions(-) diff --git a/README.rst b/README.rst index be8f06c..203b8fa 100644 --- a/README.rst +++ b/README.rst @@ -29,6 +29,13 @@ The module assigns fiscal numbers transactionally from ``ir.sequence.strict``. It does not sign or transmit e-CF documents; transmission remains the explicit responsibility of an electronic invoicing connector. +Version compatibility +********************* + +The 7.0 and 8.0 series share the same fiscal models, workflows, importer, +validations and tests. Only the module registration, packaging metadata and +report XML required by each Tryton series remain version-specific. + Authorization workflow ********************** diff --git a/invoice.py b/invoice.py index b73726c..715fd21 100755 --- a/invoice.py +++ b/invoice.py @@ -277,7 +277,7 @@ class Invoice(metaclass=PoolMeta): 'on_change_with_payment_method_barter') payment_method_credit_note = fields.Function(Monetary( - "Credit Note'", currency='currency', digits='currency'), + "Credit Note", currency='currency', digits='currency'), 'on_change_with_payment_method_credit_note') payment_method_mixed_payment = fields.Function(Monetary( @@ -1030,15 +1030,30 @@ class Invoice(metaclass=PoolMeta): @classmethod def search_amount_invoiced_services(cls, name, clause): + return cls._search_amount_invoiced_by_product_type( + name, clause, 'service') + + @classmethod + def search_amount_invoiced_goods(cls, name, clause): + return cls._search_amount_invoiced_by_product_type( + name, clause, 'goods') + + @classmethod + def _search_amount_invoiced_by_product_type( + cls, name, clause, product_type): pool = Pool() Rule = pool.get('ir.rule') Line = pool.get('account.invoice.line') Invoice = pool.get('account.invoice') Currency = pool.get('currency.currency') - type_name = cls.amount_invoiced_services._field.sql_type().base + Product = pool.get('product.product') + Template = pool.get('product.template') + type_name = getattr(cls, name)._field.sql_type().base line = Line.__table__() invoice = Invoice.__table__() currency = Currency.__table__() + product = Product.__table__() + template = Template.__table__() _, operator, value = clause if value is None: @@ -1049,53 +1064,19 @@ class Invoice(metaclass=PoolMeta): if _backend_name() == 'sqlite': value = float(value) - query = line.join(invoice, - condition=(invoice.id == line.invoice) - ).join(currency, - condition=(currency.id == invoice.currency) - ).select(line.invoice, - where=line.invoice.in_( - invoice_query), - group_by=line.invoice, - having=Operator(Coalesce(Sum( - Round((line.quantity * line.unit_price).cast( - type_name), - currency.digits)), 0).cast(type_name), - value)) - return [('id', 'in', query)] - - @classmethod - def search_amount_invoiced_goods(cls, name, clause): - pool = Pool() - Rule = pool.get('ir.rule') - Line = pool.get('account.invoice.line') - Invoice = pool.get('account.invoice') - Currency = pool.get('currency.currency') - type_name = cls.amount_invoiced_goods._field.sql_type().base - line = Line.__table__() - invoice = Invoice.__table__() - currency = Currency.__table__() - - _, operator, value = clause - if value is None: - return [] - invoice_query = Rule.query_get('account.invoice') - Operator = fields.SQL_OPERATORS[operator] - if _backend_name() == 'sqlite': - value = float(value) - - query = line.join(invoice, - condition=(invoice.id == line.invoice) - ).join(currency, - condition=(currency.id == invoice.currency) - ).select(line.invoice, - where=line.invoice.in_(invoice_query), - group_by=line.invoice, - having=Operator(Coalesce(Sum( - Round((line.quantity * line.unit_price).cast( - type_name), - currency.digits)), 0).cast(type_name), - value)) + query = (line.join(invoice, condition=(invoice.id == line.invoice)) + .join(currency, condition=(currency.id == invoice.currency)) + .join(product, condition=(product.id == line.product)) + .join(template, condition=(template.id == product.template)) + .select( + line.invoice, + where=(line.invoice.in_(invoice_query) + & (line.type == 'line') + & (template.type == product_type)), + group_by=line.invoice, + having=Operator(Coalesce(Sum( + Round((line.quantity * line.unit_price).cast(type_name), + currency.digits)), 0).cast(type_name), value))) return [('id', 'in', query)] @classmethod diff --git a/party.py b/party.py index 61143d7..43e8b9e 100644 --- a/party.py +++ b/party.py @@ -67,8 +67,6 @@ class Party(metaclass=PoolMeta): config = Configuration(1) return config.get_multivalue( 'default_supplier_ncf_type', **pattern) - - class PartyNcfType(ModelSQL, ValueMixin): diff --git a/sequence.py b/sequence.py index 41982c0..bec93e2 100644 --- a/sequence.py +++ b/sequence.py @@ -1,5 +1,7 @@ # This file is part of ncf_manager module for Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. +import unicodedata + from trytond.exceptions import UserError from trytond.model import fields from trytond.pyson import Eval @@ -33,6 +35,11 @@ _NCF_TYPE_MAP = { NCF_PREFIXES = tuple(prefix for prefix, _padding in _NCF_TYPE_MAP.values()) +def _sequence_type_entry(sequence_type): + name = unicodedata.normalize('NFC', sequence_type.name or '') + return _NCF_TYPE_MAP.get(name) + + class Sequence(metaclass=PoolMeta): __name__ = 'ir.sequence' @@ -90,14 +97,14 @@ class Sequence(metaclass=PoolMeta): def on_change_with_prefix(self, name=None): if not self.sequence_type or not self.sequence_type.name: return None - entry = _NCF_TYPE_MAP.get(self.sequence_type.name) + entry = _sequence_type_entry(self.sequence_type) return entry[0] if entry else None @fields.depends('sequence_type') def on_change_with_padding(self, name=None): if not self.sequence_type or not self.sequence_type.name: return 0 - entry = _NCF_TYPE_MAP.get(self.sequence_type.name) + entry = _sequence_type_entry(self.sequence_type) return entry[1] if entry else 0 @fields.depends('from_number', 'until_number') diff --git a/tests/test_sequence_helpers.py b/tests/test_sequence_helpers.py index d6f4f7b..d99040c 100644 --- a/tests/test_sequence_helpers.py +++ b/tests/test_sequence_helpers.py @@ -1,4 +1,5 @@ from types import SimpleNamespace +import inspect import unittest from trytond.modules.ncf_manager.ncf_manager import ( @@ -6,6 +7,7 @@ from trytond.modules.ncf_manager.ncf_manager import ( from trytond.modules.ncf_manager.sequence import Sequence from trytond.modules.ncf_manager.importer import ( normalize_dgii_row, parse_dgii_authorizations) +from trytond.modules.ncf_manager.invoice import Invoice class NcfManagerUnitTestCase(unittest.TestCase): @@ -59,6 +61,18 @@ class NcfManagerUnitTestCase(unittest.TestCase): self.assertIsNone(Sequence.on_change_with_prefix(sequence)) self.assertEqual(Sequence.on_change_with_padding(sequence), 0) + def test_decomposed_electronic_sequence_name_is_supported(self): + sequence = SimpleNamespace() + sequence.sequence_type = SimpleNamespace(name='Compras Electro\u0301nico') + self.assertEqual(Sequence.on_change_with_prefix(sequence), 'E41') + self.assertEqual(Sequence.on_change_with_padding(sequence), 10) + + def test_amount_searcher_filters_the_requested_product_type(self): + source = inspect.getsource( + Invoice._search_amount_invoiced_by_product_type) + self.assertIn('template.type == product_type', source) + self.assertIn("line.type == 'line'", source) + def test_csv_parser_preserves_unquoted_detail(self): source_format, rows = parse_dgii_authorizations( f'{self.HEADERS}\r\n{self.ROW}\r\n'.encode()) diff --git a/tox.ini b/tox.ini index 0bf424f..970f9ff 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = {py37,py38,py39,py310}-{sqlite,postgresql} +envlist = {py311,py312,py313,py314}-{sqlite,postgresql} [testenv] extras = test diff --git a/tryton.cfg b/tryton.cfg index 3af0055..73871fe 100644 --- a/tryton.cfg +++ b/tryton.cfg @@ -1,5 +1,5 @@ [tryton] -version=8.0.3 +version=8.0.4 depends: account_do account_invoice_do