Prepare ncf manager for Tryton 8.0

This commit is contained in:
José Arturo García
2026-07-22 21:27:53 -04:00
commit 805191edfa
43 changed files with 9012 additions and 0 deletions
View File
+448
View File
@@ -0,0 +1,448 @@
import datetime
from decimal import Decimal
from types import SimpleNamespace
import unittest
from trytond.modules.ncf_manager.sequence import Sequence
from trytond.modules.ncf_manager.invoice import Invoice
from trytond.pool import Pool
from trytond.tests.test_tryton import ModuleTestCase, with_transaction
from trytond.transaction import Transaction
class NcfManagerTestCase(ModuleTestCase):
"Test Ncf Manager module"
module = 'ncf_manager'
def _create_company(self):
pool = Pool()
Company = pool.get('company.company')
Currency = pool.get('currency.currency')
Party = pool.get('party.party')
currency, = Currency.create([{
'name': 'Dominican Peso',
'code': 'DOP',
'symbol': 'RD$',
'digits': 2,
'rounding': '0.01',
}])
party, = Party.create([{
'name': 'Empresa NCF',
'addresses': [('create', [{}])],
}])
company, = Company.create([{
'party': party.id,
'currency': currency.id,
}])
return company
def _create_chart(self, company):
pool = Pool()
Account = pool.get('account.account')
AccountTemplate = pool.get('account.account.template')
CreateChart = pool.get('account.create_chart', type='wizard')
Journal = pool.get('account.journal')
ModelData = pool.get('ir.model.data')
session_id, _start, _end = CreateChart.create()
chart = CreateChart(session_id)
chart.account.account_template = AccountTemplate(ModelData.get_id(
'account_do', 'do_account_root'))
chart.account.company = company
chart.transition_create_account()
receivable, = Account.search([
('company', '=', company.id),
('code', '=', '110201'),
], limit=1)
payable, = Account.search([
('company', '=', company.id),
('code', '=', '210101'),
], limit=1)
revenue, = Account.search([
('company', '=', company.id),
('code', '=', '4102'),
], limit=1)
chart.properties.company = company
chart.properties.account_receivable = receivable
chart.properties.account_payable = payable
with Transaction().set_context(company=company.id):
chart.transition_create_properties()
CreateChart.delete(session_id)
journal, = Journal.search([('type', '=', 'revenue')], limit=1)
return receivable, revenue, journal
def _create_fiscalyear(self, company, today=None):
pool = Pool()
FiscalYear = pool.get('account.fiscalyear')
InvoiceSequence = pool.get('account.fiscalyear.invoice_sequence')
StrictSequence = pool.get('ir.sequence.strict')
SequenceType = pool.get('ir.sequence.type')
today = today or datetime.date.today()
year = today.year
move_type, = SequenceType.search([
('name', '=', 'Account Move'),
], limit=1)
invoice_type, = SequenceType.search([
('name', '=', 'Invoice'),
], limit=1)
move_sequence, = StrictSequence.create([{
'name': 'FY 2026 Moves',
'sequence_type': move_type.id,
'company': company.id,
}])
invoice_sequence, = StrictSequence.create([{
'name': 'FY 2026 Invoices',
'sequence_type': invoice_type.id,
'company': company.id,
}])
fiscalyear, = FiscalYear.create([{
'name': str(year),
'start_date': datetime.date(year, 1, 1),
'end_date': datetime.date(year, 12, 31),
'company': company.id,
'move_sequence': move_sequence.id,
}])
fiscalyear.invoice_sequences = [InvoiceSequence(
company=company,
in_invoice_sequence=invoice_sequence,
in_credit_note_sequence=invoice_sequence,
out_invoice_sequence=invoice_sequence,
out_credit_note_sequence=invoice_sequence)]
fiscalyear.save()
FiscalYear.create_period([fiscalyear])
def _create_payment_term(self):
PaymentTerm = Pool().get('account.invoice.payment_term')
term, = PaymentTerm.create([{
'name': 'Contado',
'lines': [('create', [{'type': 'remainder'}])],
}])
return term
def _create_ncf_sequence(self, company, today=None):
pool = Pool()
ModelData = pool.get('ir.model.data')
Manager = pool.get('ncf.manager')
ManagerSequence = pool.get('ncf.manager.sequence')
StrictSequence = pool.get('ir.sequence.strict')
SequenceType = pool.get('ir.sequence.type')
today = today or datetime.date.today()
sequence_type = SequenceType(ModelData.get_id(
'ncf_manager', 'sequence_type_out_invoice_01'))
strict_sequence, = StrictSequence.create([{
'name': 'B01 Factura Credito Fiscal',
'sequence_type': sequence_type.id,
'company': company.id,
'prefix': 'B01',
'padding': 8,
'number_next': 1,
'number_increment': 1,
'from_number': 1,
'until_number': 2,
'expiration_date': datetime.date(today.year, 12, 31),
}])
manager, = Manager.create([{
'company': company.id,
'app_date': datetime.date(today.year, 1, 1),
'app_number': '0000000001',
'auth_number': '0000000001',
'state': 'active',
}])
with Transaction().set_context(company=company.id):
sequence, = ManagerSequence.create([{
'pos': manager.id,
'ncf_type': '1',
'ncf_sequences': strict_sequence.id,
'invoice_type': 'invoice_customer',
'state': 'active',
}])
return sequence
@with_transaction()
def test_customer_invoice_post_assigns_b01_ncf(self):
'Test full NCF assignment workflow on posted customer invoice'
pool = Pool()
Invoice = pool.get('account.invoice')
Party = pool.get('party.party')
today = datetime.date.today()
company = self._create_company()
receivable, revenue, journal = self._create_chart(company)
self._create_fiscalyear(company, today=today)
payment_term = self._create_payment_term()
ncf_sequence = self._create_ncf_sequence(company, today=today)
party, = Party.create([{
'name': 'Cliente NCF',
'addresses': [('create', [{}])],
}])
with Transaction().set_context(
company=company.id, date=today):
invoice, = Invoice.create([{
'type': 'out',
'company': company.id,
'party': party.id,
'invoice_address': party.addresses[0].id,
'account': receivable.id,
'currency': company.currency.id,
'invoice_date': today,
'accounting_date': today,
'journal': journal.id,
'payment_term': payment_term.id,
'ncf_type': '1',
'ncf_sequence': ncf_sequence.id,
'revenue_type': '01',
'payment_method': '04',
'lines': [('create', [{
'type': 'line',
'description': 'Servicio',
'quantity': 1,
'unit_price': Decimal('500.00'),
'account': revenue.id,
}])],
}])
Invoice.post([invoice])
invoice = Invoice(invoice.id)
self.assertEqual(invoice.state, 'posted')
self.assertEqual(invoice.ncf_number, 'B0100000001')
self.assertEqual(invoice.ncf_sequence, ncf_sequence)
self.assertEqual(
invoice.expiration_date, datetime.date(today.year, 12, 31))
del ModuleTestCase
class NcfManagerUnitTestCase(unittest.TestCase):
def test_sequence_defaults_match_ncf_type(self):
sequence = SimpleNamespace(
sequence_type=SimpleNamespace(name='Factura de Crédito Fiscal'))
self.assertEqual(Sequence.on_change_with_prefix(sequence), 'B01')
self.assertEqual(Sequence.on_change_with_padding(sequence), 8)
def test_sequence_approved_and_remaining_quantities(self):
sequence = SimpleNamespace(
from_number=1,
until_number=10,
number_next=4)
self.assertEqual(Sequence.on_change_with_approved_qty(sequence), 10)
self.assertEqual(Sequence.on_change_with_remaining_qty(sequence), 7)
def test_debit_note_code_follows_origin_ncf_type(self):
original_enabled = Invoice._ncf_electronic_invoicing_enabled
Invoice._ncf_electronic_invoicing_enabled = classmethod(
lambda cls: False)
try:
physical = SimpleNamespace(ncf_type='1')
electronic = SimpleNamespace(ncf_type='31')
self.assertEqual(
Invoice._ncf_resolve_debit_note_code(physical), '3')
self.assertEqual(
Invoice._ncf_resolve_debit_note_code(electronic), '3')
finally:
Invoice._ncf_electronic_invoicing_enabled = original_enabled
def test_debit_note_preparation_uses_physical_without_electronic_module(self):
electronic_sequence = SimpleNamespace(ncf_type='33')
physical_sequence = SimpleNamespace(ncf_type='3')
original_find_active_sequence = Invoice._ncf_find_active_sequence
original_enabled = Invoice._ncf_electronic_invoicing_enabled
def find_sequence(cls, invoice, ncf_code):
if ncf_code == '33':
return electronic_sequence
if ncf_code == '3':
return physical_sequence
return None
Invoice._ncf_find_active_sequence = classmethod(find_sequence)
Invoice._ncf_electronic_invoicing_enabled = classmethod(
lambda cls: False)
try:
origin = SimpleNamespace(
ncf_number='B0100000001',
ncf_type='1',
payment_method='04',
revenue_type='01',
isr_withholding_type=None)
debit_note = SimpleNamespace(
company=SimpleNamespace(rec_name='Solutema'),
type='out',
ncf_number_modified=None,
ncf_type=None,
ncf_sequence=None,
payment_method=None,
revenue_type=None,
isr_withholding_type=None)
Invoice._ncf_prepare_debit_note_from_origin(origin, debit_note)
finally:
Invoice._ncf_find_active_sequence = original_find_active_sequence
Invoice._ncf_electronic_invoicing_enabled = original_enabled
self.assertEqual(debit_note.ncf_number_modified, 'B0100000001')
self.assertEqual(debit_note.ncf_type, '3')
self.assertEqual(debit_note.ncf_sequence, physical_sequence)
self.assertEqual(debit_note.payment_method, '04')
self.assertEqual(debit_note.revenue_type, '01')
def test_debit_note_preparation_prefers_electronic_when_module_is_active(self):
electronic_sequence = SimpleNamespace(ncf_type='33')
original_find_active_sequence = Invoice._ncf_find_active_sequence
original_enabled = Invoice._ncf_electronic_invoicing_enabled
def find_sequence(cls, invoice, ncf_code):
return electronic_sequence if ncf_code == '33' else None
Invoice._ncf_find_active_sequence = classmethod(find_sequence)
Invoice._ncf_electronic_invoicing_enabled = classmethod(
lambda cls: True)
try:
origin = SimpleNamespace(
ncf_number='B0100000001',
ncf_type='1',
payment_method='04',
revenue_type='01',
isr_withholding_type=None)
debit_note = SimpleNamespace(
company=SimpleNamespace(rec_name='Solutema'),
type='out',
ncf_number_modified=None,
ncf_type=None,
ncf_sequence=None,
payment_method=None,
revenue_type=None,
isr_withholding_type=None)
Invoice._ncf_prepare_debit_note_from_origin(origin, debit_note)
finally:
Invoice._ncf_find_active_sequence = original_find_active_sequence
Invoice._ncf_electronic_invoicing_enabled = original_enabled
self.assertEqual(debit_note.ncf_number_modified, 'B0100000001')
self.assertEqual(debit_note.ncf_type, '33')
self.assertEqual(debit_note.ncf_sequence, electronic_sequence)
def test_debit_note_preparation_falls_back_to_physical_sequence(self):
physical_sequence = SimpleNamespace(ncf_type='3')
original_find_active_sequence = Invoice._ncf_find_active_sequence
original_enabled = Invoice._ncf_electronic_invoicing_enabled
def find_sequence(cls, invoice, ncf_code):
return physical_sequence if ncf_code == '3' else None
Invoice._ncf_find_active_sequence = classmethod(find_sequence)
Invoice._ncf_electronic_invoicing_enabled = classmethod(
lambda cls: True)
try:
origin = SimpleNamespace(
ncf_number='B0100000001',
ncf_type='1',
payment_method='04',
revenue_type='01',
isr_withholding_type=None)
debit_note = SimpleNamespace(
company=SimpleNamespace(rec_name='Solutema'),
type='out',
ncf_number_modified=None,
ncf_type=None,
ncf_sequence=None,
payment_method=None,
revenue_type=None,
isr_withholding_type=None)
Invoice._ncf_prepare_debit_note_from_origin(origin, debit_note)
finally:
Invoice._ncf_find_active_sequence = original_find_active_sequence
Invoice._ncf_electronic_invoicing_enabled = original_enabled
self.assertEqual(debit_note.ncf_number_modified, 'B0100000001')
self.assertEqual(debit_note.ncf_type, '3')
self.assertEqual(debit_note.ncf_sequence, physical_sequence)
def test_credit_note_preparation_prefers_electronic_sequence_for_b_origin(self):
electronic_sequence = SimpleNamespace(ncf_type='34')
original_find_active_sequence = Invoice._ncf_find_active_sequence
original_enabled = Invoice._ncf_electronic_invoicing_enabled
def find_sequence(cls, invoice, ncf_code):
return electronic_sequence if ncf_code == '34' else None
Invoice._ncf_find_active_sequence = classmethod(find_sequence)
Invoice._ncf_electronic_invoicing_enabled = classmethod(
lambda cls: True)
try:
origin = SimpleNamespace(
ncf_number='B0100000001',
ncf_type='1')
credit_note = SimpleNamespace(
company=SimpleNamespace(rec_name='Solutema'),
type='out',
ncf_number_modified=None,
ncf_type=None,
ncf_sequence=None,
payment_method=None,
revenue_type=None)
Invoice._ncf_prepare_credit_note_from_origin(origin, credit_note)
finally:
Invoice._ncf_find_active_sequence = original_find_active_sequence
Invoice._ncf_electronic_invoicing_enabled = original_enabled
self.assertEqual(credit_note.ncf_number_modified, 'B0100000001')
self.assertEqual(credit_note.ncf_type, '34')
self.assertEqual(credit_note.ncf_sequence, electronic_sequence)
self.assertEqual(credit_note.payment_method, '06')
def test_modified_ncf_validation_applies_to_debit_notes(self):
debit_note = SimpleNamespace(
ncf_type='3',
ncf_number_modified='B0100000001',
type='out')
Invoice.check_ncf_number_modified(debit_note)
def test_fiscal_classification_validation_order_for_customer_invoice(self):
calls = []
invoice = SimpleNamespace(
type='out',
check_revenue_type=lambda: calls.append('revenue_type'),
check_expense_type=lambda: calls.append('expense_type'),
check_isr_withholding_type=lambda: calls.append(
'isr_withholding_type'),
check_payment_method=lambda: calls.append('payment_method'))
Invoice._ncf_check_fiscal_classification(invoice)
self.assertEqual(calls, [
'revenue_type',
'isr_withholding_type',
'payment_method',
])
def test_fiscal_classification_validation_order_for_supplier_invoice(self):
calls = []
invoice = SimpleNamespace(
type='in',
check_revenue_type=lambda: calls.append('revenue_type'),
check_expense_type=lambda: calls.append('expense_type'),
check_isr_withholding_type=lambda: calls.append(
'isr_withholding_type'),
check_payment_method=lambda: calls.append('payment_method'))
Invoice._ncf_check_fiscal_classification(invoice)
self.assertEqual(calls, [
'expense_type',
'isr_withholding_type',
'payment_method',
])
+46
View File
@@ -0,0 +1,46 @@
from types import SimpleNamespace
import unittest
from trytond.modules.ncf_manager.ncf_manager import (
NCFManager, NCFManagerSequences)
from trytond.modules.ncf_manager.sequence import Sequence
class NcfManagerUnitTestCase(unittest.TestCase):
def test_manager_defaults_and_rec_name(self):
self.assertEqual(NCFManager.default_state(), 'draft')
manager = SimpleNamespace()
manager.id = 42
manager.company = SimpleNamespace(rec_name='ACME')
manager.app_number = 'APP001'
self.assertEqual(NCFManager.get_rec_name(manager, 'name'), 'ACME - APP001')
def test_sequence_defaults_and_rec_name(self):
self.assertEqual(NCFManagerSequences.default_state(), 'draft')
sequence = SimpleNamespace()
sequence.ncf_type_string = 'Factura de Credito Fiscal - B01'
self.assertEqual(
NCFManagerSequences.get_rec_name(sequence, 'name'),
'Factura de Credito Fiscal ')
def test_ir_sequence_on_change_values_for_ncf_type(self):
sequence = SimpleNamespace()
sequence.sequence_type = SimpleNamespace(name='Factura de Consumo')
self.assertEqual(Sequence.on_change_with_prefix(sequence), 'B02')
self.assertEqual(Sequence.on_change_with_padding(sequence), 8)
self.assertEqual(Sequence.on_change_with_name(sequence), 'Factura de Consumo')
def test_ir_sequence_quantities(self):
sequence = SimpleNamespace()
sequence.from_number = 10
sequence.until_number = 20
sequence.number_next = 12
self.assertEqual(Sequence.on_change_with_approved_qty(sequence), 11)
self.assertEqual(Sequence.on_change_with_remaining_qty(sequence), 9)
def test_unknown_ncf_type_has_empty_sequence_defaults(self):
sequence = SimpleNamespace()
sequence.sequence_type = SimpleNamespace(name='Unknown')
self.assertIsNone(Sequence.on_change_with_prefix(sequence))
self.assertEqual(Sequence.on_change_with_padding(sequence), 0)