Fix NCF overlap validation by document direction
Publicar paquete PyPI / publish (push) Successful in 1m48s

This commit is contained in:
José Arturo García
2026-08-27 10:37:04 -04:00
parent 54649bb8e9
commit c01ab2e54c
4 changed files with 111 additions and 3 deletions
+5 -2
View File
@@ -37,8 +37,11 @@ Finished``. Create the authorization and its fiscal lines in draft, then use
the **Activate** button. A line can select an existing strict sequence or ask the **Activate** button. A line can select an existing strict sequence or ask
the module to create one on activation from its authorized range. Activation the module to create one on activation from its authorized range. Activation
checks company, fiscal type, prefix, padding, validity, availability and range checks company, fiscal type, prefix, padding, validity, availability and range
overlap. The last issued number automatically finishes the line. Finishing overlap. Incoming supplier documents and outgoing customer documents use
an authorization closes every remaining active line without reusing numbers. independent numbering authorities, so overlap is enforced only within the
same document direction. The last issued number automatically finishes the
line. Finishing an authorization closes every remaining active line without
reusing numbers.
Electronic fiscal numbers are only reserved by this module. Activating an Electronic fiscal numbers are only reserved by this module. Activating an
E-series authorization never signs or transmits an e-CF to DGII. E-series authorization never signs or transmits an e-CF to DGII.
+1
View File
@@ -568,6 +568,7 @@ class NCFManagerSequences(Workflow, ModelSQL, ModelView):
('id', '!=', self.id), ('id', '!=', self.id),
('pos.company', '=', self.pos.company.id), ('pos.company', '=', self.pos.company.id),
('ncf_type', '=', self.ncf_type), ('ncf_type', '=', self.ncf_type),
('invoice_type', '=', self.invoice_type),
('state', '=', 'active'), ('state', '=', 'active'),
('ncf_sequences', '!=', None), ('ncf_sequences', '!=', None),
]) ])
+104
View File
@@ -165,6 +165,46 @@ class NcfManagerTestCase(ModuleTestCase):
}]) }])
return sequence return sequence
def _create_active_e31_range(self, company, invoice_type,
from_number=1, until_number=100):
pool = Pool()
Manager = pool.get('ncf.manager')
ManagerSequence = pool.get('ncf.manager.sequence')
ModelData = pool.get('ir.model.data')
StrictSequence = pool.get('ir.sequence.strict')
SequenceType = pool.get('ir.sequence.type')
today = datetime.date.today()
sequence_type = SequenceType(ModelData.get_id(
'ncf_manager', 'sequence_type_out_invoice_31'))
strict_sequence, = StrictSequence.create([{
'name': f'E31 {invoice_type}',
'sequence_type': sequence_type.id,
'company': company.id,
'prefix': 'E31',
'padding': 10,
'number_next': from_number,
'number_increment': 1,
'from_number': from_number,
'until_number': until_number,
'expiration_date': datetime.date(today.year + 1, 12, 31),
}])
manager, = Manager.create([{
'company': company.id,
'app_date': today,
'app_number': 'APP0000001',
'auth_number': 'AUT0000001',
'state': 'active',
}])
line, = ManagerSequence.create([{
'pos': manager.id,
'ncf_type': '31',
'ncf_sequences': strict_sequence.id,
'invoice_type': invoice_type,
'state': 'active',
}])
return line
@staticmethod @staticmethod
def _dgii_xml(ncf_type='31', authorization='6005423851', def _dgii_xml(ncf_type='31', authorization='6005423851',
start='E310000000001', end='E310000000100', quantity='100'): start='E310000000001', end='E310000000100', quantity='100'):
@@ -292,6 +332,70 @@ class NcfManagerTestCase(ModuleTestCase):
self.assertEqual(manager.ncf_sequences[0].state, 'finished') self.assertEqual(manager.ncf_sequences[0].state, 'finished')
self.assertIsNotNone(manager.finished_at) self.assertIsNotNone(manager.finished_at)
@with_transaction()
def test_overlap_is_independent_by_invoice_direction(self):
'A supplier range must not block the company outgoing authorization'
pool = Pool()
Manager = pool.get('ncf.manager')
company = self._create_company()
today = datetime.date.today()
supplier_line = self._create_active_e31_range(
company, 'invoice_supplier', until_number=2_000_000_000)
manager, = Manager.create([{
'company': company.id,
'app_date': today,
'app_number': '6009912073',
'auth_number': '6005423851',
'ncf_sequences': [('create', [{
'invoice_type': 'invoice_customer',
'ncf_type': '31',
'sequence_mode': 'create',
'from_number': 1,
'number_next': 1,
'until_number': 100,
'expiration_date': datetime.date(
today.year + 1, 12, 31),
}])],
}])
Manager.activate([manager])
manager = Manager(manager.id)
self.assertEqual(manager.state, 'active')
self.assertEqual(manager.ncf_sequences[0].state, 'active')
self.assertEqual(supplier_line.state, 'active')
@with_transaction()
def test_overlap_still_blocks_same_invoice_direction(self):
'Two outgoing authorizations may not reserve the same E31 numbers'
pool = Pool()
Manager = pool.get('ncf.manager')
company = self._create_company()
today = datetime.date.today()
self._create_active_e31_range(
company, 'invoice_customer', from_number=1, until_number=100)
manager, = Manager.create([{
'company': company.id,
'app_date': today,
'app_number': '6009912073',
'auth_number': '6005423851',
'ncf_sequences': [('create', [{
'invoice_type': 'invoice_customer',
'ncf_type': '31',
'sequence_mode': 'create',
'from_number': 50,
'number_next': 50,
'until_number': 150,
'expiration_date': datetime.date(
today.year + 1, 12, 31),
}])],
}])
with self.assertRaises(UserError):
Manager.activate([manager])
@with_transaction() @with_transaction()
def test_import_dgii_authorization_is_idempotent(self): def test_import_dgii_authorization_is_idempotent(self):
pool = Pool() pool = Pool()
+1 -1
View File
@@ -1,5 +1,5 @@
[tryton] [tryton]
version=8.0.1 version=8.0.2
depends: depends:
account_do account_do
account_invoice_do account_invoice_do