2026-07-22 19:12:44 -04:00
|
|
|
|
import unittest
|
2026-08-09 12:19:52 -04:00
|
|
|
|
import datetime
|
2026-07-22 19:12:44 -04:00
|
|
|
|
from collections import Counter
|
2026-08-09 13:24:59 -04:00
|
|
|
|
from configparser import ConfigParser
|
2026-07-22 19:12:44 -04:00
|
|
|
|
from decimal import Decimal
|
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
from xml.etree import ElementTree as ET
|
|
|
|
|
|
|
|
|
|
|
|
from trytond.pool import Pool
|
|
|
|
|
|
from trytond.tests.test_tryton import ModuleTestCase, with_transaction
|
|
|
|
|
|
from trytond.transaction import Transaction
|
|
|
|
|
|
|
|
|
|
|
|
MODULE_DIR = Path(__file__).resolve().parent.parent
|
|
|
|
|
|
|
2026-08-09 13:13:22 -04:00
|
|
|
|
TAX_CODE_LINE_AUDIT = {
|
|
|
|
|
|
'do_tcl_itbis18v_inv': '+',
|
|
|
|
|
|
'do_tcl_itbis18v_cr': '-',
|
|
|
|
|
|
'do_tcl_itbis18c_inv': '-',
|
|
|
|
|
|
'do_tcl_itbis18c_cr': '+',
|
|
|
|
|
|
'do_tcl_ret_itbis_inv': '-',
|
|
|
|
|
|
'do_tcl_ret_itbis_cr': '+',
|
|
|
|
|
|
'do_tcl_ret_itbis_inf75_inv': '-',
|
|
|
|
|
|
'do_tcl_ret_itbis_inf75_cr': '+',
|
|
|
|
|
|
'do_tcl_isr_hon_inv': '-',
|
|
|
|
|
|
'do_tcl_isr_hon_cr': '+',
|
|
|
|
|
|
'do_tcl_isr_est_inv': '+',
|
|
|
|
|
|
'do_tcl_isr_est_cr': '-',
|
|
|
|
|
|
'do_tcl_isc_comb_inv': '+',
|
|
|
|
|
|
'do_tcl_isc_comb_cr': '-',
|
|
|
|
|
|
'do_tcl_chq_inv': '+',
|
|
|
|
|
|
'do_tcl_chq_cr': '-',
|
|
|
|
|
|
'do_tcl_chq020_inv': '+',
|
|
|
|
|
|
'do_tcl_chq020_cr': '-',
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
IFRS_REQUIRED_ACCOUNT_CODES = {
|
|
|
|
|
|
'1', '11', '12',
|
|
|
|
|
|
'1101', '1102', '1103', '1104', '1105', '1106', '1107', '1110',
|
|
|
|
|
|
'110101', '110103', '110104', '110201', '110205', '110301',
|
|
|
|
|
|
'110305', '110601', '110701', '110702', '110703', '111001',
|
|
|
|
|
|
'111002',
|
|
|
|
|
|
'1201', '1205', '1206', '1207', '1208', '1209',
|
|
|
|
|
|
'120101', '120191', '120192', '120501', '120591', '120592',
|
|
|
|
|
|
'120601', '120701', '120702', '120703', '120801', '120891',
|
|
|
|
|
|
'120892', '120901', '120991', '120992',
|
|
|
|
|
|
'2', '21', '22', '23',
|
|
|
|
|
|
'2101', '2104', '2105', '2106', '2201', '2301', '2302', '2303',
|
|
|
|
|
|
'2304',
|
|
|
|
|
|
'210101', '210401', '210501', '210601', '220101', '230201',
|
|
|
|
|
|
'230202', '230301', '230401',
|
|
|
|
|
|
'3', '31', '3101', '3104', '3105', '3106', '310601', '310604',
|
|
|
|
|
|
'4', '41', '42', '4101', '4102', '4103', '4104', '4201', '4208',
|
|
|
|
|
|
'4209', '4210',
|
|
|
|
|
|
'5', '51', '5101', '5102', '5106',
|
|
|
|
|
|
'6', '61', '62', '63', '6101', '6103', '6111', '6112', '6115',
|
|
|
|
|
|
'6116', '6117', '6118', '6119', '6120', '6201', '6203', '6206',
|
|
|
|
|
|
'6207', '6301', '6302', '6303',
|
|
|
|
|
|
'7', '7101', '7102',
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
IFRS_ACCOUNT_TYPE_AUDIT = {
|
|
|
|
|
|
'111002': 'do_type_current_asset',
|
|
|
|
|
|
'120192': 'do_type_fixed_asset',
|
|
|
|
|
|
'120292': 'do_type_fixed_asset',
|
|
|
|
|
|
'120392': 'do_type_fixed_asset',
|
|
|
|
|
|
'120591': 'do_type_rou_asset',
|
|
|
|
|
|
'120592': 'do_type_rou_asset',
|
|
|
|
|
|
'120891': 'do_type_investment_property',
|
|
|
|
|
|
'120892': 'do_type_investment_property',
|
|
|
|
|
|
'120992': 'do_type_intangible',
|
|
|
|
|
|
'7101': 'do_type_control',
|
|
|
|
|
|
'7102': 'do_type_control',
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
IFRS_POLICY_COVERAGE = {
|
|
|
|
|
|
'NIC 1': {'11', '12', '21', '23', '3106', '7101', '7102'},
|
|
|
|
|
|
'NIC 2': {'1103', '110305', '5101', '5106'},
|
|
|
|
|
|
'NIIF 9': {'110205', '110701', '110702', '110703', '120701',
|
|
|
|
|
|
'120702', '120703', '4208', '6206', '310604'},
|
|
|
|
|
|
'NIIF 15': {'110601', '210501', '4101', '4102', '4103', '4104'},
|
|
|
|
|
|
'NIIF 16': {'120501', '120591', '120592', '230201', '230202',
|
|
|
|
|
|
'6112', '6203'},
|
|
|
|
|
|
'NIC 12': {'120601', '230301', '6301', '6302', '6303'},
|
|
|
|
|
|
'NIC 16': {'120101', '120191', '120192', '6110', '6116'},
|
|
|
|
|
|
'NIC 36': {'111002', '120192', '120292', '120392', '120592',
|
|
|
|
|
|
'120892', '120992', '6116', '6117', '4206'},
|
|
|
|
|
|
'NIC 37': {'220101', '230401', '6118', '4207'},
|
|
|
|
|
|
'NIC 38': {'120901', '120991', '120992', '6111', '6117'},
|
|
|
|
|
|
'NIC 40': {'120801', '120891', '120892', '4209', '6207'},
|
|
|
|
|
|
'NIIF 5': {'111001', '111002', '210601', '4210', '6120'},
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-22 19:12:44 -04:00
|
|
|
|
|
|
|
|
|
|
def _iter_xml_records(*filenames):
|
|
|
|
|
|
for filename in filenames:
|
|
|
|
|
|
path = MODULE_DIR / filename
|
|
|
|
|
|
root = ET.parse(path).getroot()
|
|
|
|
|
|
for record in root.findall('.//record'):
|
2026-08-10 00:08:55 -04:00
|
|
|
|
english = filename.endswith('_en.xml')
|
2026-07-22 19:12:44 -04:00
|
|
|
|
values = {
|
|
|
|
|
|
field.get('name'): (
|
|
|
|
|
|
field.get('ref') or field.get('eval') or field.text or '')
|
|
|
|
|
|
for field in record.findall('field')
|
|
|
|
|
|
}
|
2026-08-10 00:08:55 -04:00
|
|
|
|
record_id = record.get('id')
|
|
|
|
|
|
if english:
|
|
|
|
|
|
record_id = record_id.removesuffix('_en')
|
|
|
|
|
|
values = {
|
|
|
|
|
|
name: value.removesuffix('_en')
|
|
|
|
|
|
if name not in {'name', 'description'} else value
|
|
|
|
|
|
for name, value in values.items()
|
|
|
|
|
|
}
|
|
|
|
|
|
yield filename, record_id, record.get('model'), values
|
2026-08-09 13:24:59 -04:00
|
|
|
|
|
|
|
|
|
|
|
2026-07-22 19:12:44 -04:00
|
|
|
|
class AccountDoTestCase(ModuleTestCase):
|
|
|
|
|
|
"Test account_do module"
|
|
|
|
|
|
module = 'account_do'
|
2026-08-10 00:08:55 -04:00
|
|
|
|
extras = [
|
|
|
|
|
|
'account_asset',
|
|
|
|
|
|
'account_deposit',
|
|
|
|
|
|
'account_stock_continental',
|
|
|
|
|
|
]
|
2026-07-22 19:12:44 -04:00
|
|
|
|
|
|
|
|
|
|
@with_transaction()
|
|
|
|
|
|
def test_chart_template_is_loaded_by_default(self):
|
2026-08-10 00:08:55 -04:00
|
|
|
|
'Test English Dominican chart is loaded in an English database'
|
2026-07-22 19:12:44 -04:00
|
|
|
|
pool = Pool()
|
|
|
|
|
|
ModelData = pool.get('ir.model.data')
|
|
|
|
|
|
|
2026-08-10 00:08:55 -04:00
|
|
|
|
self.assertTrue(ModelData.get_id('account_do', 'do_account_root_en'))
|
|
|
|
|
|
|
|
|
|
|
|
@with_transaction()
|
|
|
|
|
|
def test_default_properties_ignore_unrelated_chart(self):
|
|
|
|
|
|
'Test account_do does not override defaults for another chart'
|
|
|
|
|
|
pool = Pool()
|
|
|
|
|
|
AccountTemplate = pool.get('account.account.template')
|
|
|
|
|
|
Company = pool.get('company.company')
|
|
|
|
|
|
Currency = pool.get('currency.currency')
|
|
|
|
|
|
ModelData = pool.get('ir.model.data')
|
|
|
|
|
|
Party = pool.get('party.party')
|
|
|
|
|
|
CreateChart = pool.get('account.create_chart', type='wizard')
|
|
|
|
|
|
|
|
|
|
|
|
currency, = Currency.create([{
|
|
|
|
|
|
'name': 'Test Currency',
|
|
|
|
|
|
'code': 'TST',
|
|
|
|
|
|
'symbol': 'T',
|
|
|
|
|
|
'digits': 2,
|
|
|
|
|
|
'rounding': '0.01',
|
|
|
|
|
|
}])
|
|
|
|
|
|
party, = Party.create([{'name': 'Unrelated Chart Company'}])
|
|
|
|
|
|
company, = Company.create([{
|
|
|
|
|
|
'party': party.id,
|
|
|
|
|
|
'currency': currency.id,
|
|
|
|
|
|
}])
|
|
|
|
|
|
template = AccountTemplate(ModelData.get_id(
|
|
|
|
|
|
'account', 'account_template_root_en'))
|
|
|
|
|
|
session_id, _start, _end = CreateChart.create()
|
|
|
|
|
|
chart = CreateChart(session_id)
|
|
|
|
|
|
chart.account.account_template = template
|
|
|
|
|
|
chart.account.company = company
|
|
|
|
|
|
|
|
|
|
|
|
defaults = chart.default_properties([
|
|
|
|
|
|
'company', 'account_receivable', 'account_payable'])
|
|
|
|
|
|
|
|
|
|
|
|
self.assertEqual(defaults['company'], company.id)
|
|
|
|
|
|
self.assertIsNone(defaults['account_receivable'])
|
|
|
|
|
|
self.assertIsNone(defaults['account_payable'])
|
|
|
|
|
|
CreateChart.delete(session_id)
|
|
|
|
|
|
|
|
|
|
|
|
@with_transaction()
|
|
|
|
|
|
def test_spanish_chart_creates_spanish_accounts_and_taxes(self):
|
|
|
|
|
|
'Test the es_419 chart creates a complete independent chart'
|
|
|
|
|
|
pool = Pool()
|
|
|
|
|
|
Account = pool.get('account.account')
|
|
|
|
|
|
AccountTemplate = pool.get('account.account.template')
|
|
|
|
|
|
Company = pool.get('company.company')
|
|
|
|
|
|
Currency = pool.get('currency.currency')
|
|
|
|
|
|
ModelData = pool.get('ir.model.data')
|
|
|
|
|
|
Party = pool.get('party.party')
|
|
|
|
|
|
Tax = pool.get('account.tax')
|
|
|
|
|
|
TaxCode = pool.get('account.tax.code')
|
|
|
|
|
|
CreateChart = pool.get('account.create_chart', type='wizard')
|
|
|
|
|
|
|
|
|
|
|
|
currency, = Currency.create([{
|
|
|
|
|
|
'name': 'Peso dominicano',
|
|
|
|
|
|
'code': 'DOP',
|
|
|
|
|
|
'symbol': 'RD$',
|
|
|
|
|
|
'digits': 2,
|
|
|
|
|
|
'rounding': '0.01',
|
|
|
|
|
|
}])
|
|
|
|
|
|
party, = Party.create([{'name': 'Empresa Dominicana'}])
|
|
|
|
|
|
company, = Company.create([{
|
|
|
|
|
|
'party': party.id,
|
|
|
|
|
|
'currency': currency.id,
|
|
|
|
|
|
}])
|
|
|
|
|
|
try:
|
|
|
|
|
|
template_id = ModelData.get_id(
|
|
|
|
|
|
'account_do', 'do_account_root_es_419')
|
|
|
|
|
|
except KeyError:
|
|
|
|
|
|
self.skipTest('The es_419 chart is loaded only for es_419 databases')
|
|
|
|
|
|
template = AccountTemplate(template_id)
|
|
|
|
|
|
|
|
|
|
|
|
session_id, _start, _end = CreateChart.create()
|
|
|
|
|
|
chart = CreateChart(session_id)
|
|
|
|
|
|
chart.account.account_template = template
|
|
|
|
|
|
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)
|
|
|
|
|
|
defaults = chart.default_properties([
|
|
|
|
|
|
'company', 'account_receivable', 'account_payable'])
|
|
|
|
|
|
self.assertEqual(receivable.name, 'Cuentas por cobrar clientes')
|
|
|
|
|
|
self.assertEqual(payable.name, 'Cuentas por pagar proveedores')
|
|
|
|
|
|
self.assertEqual(defaults['account_receivable'], receivable.id)
|
|
|
|
|
|
self.assertEqual(defaults['account_payable'], payable.id)
|
|
|
|
|
|
self.assertTrue(Tax.search([
|
|
|
|
|
|
('company', '=', company.id),
|
|
|
|
|
|
('description', '=', 'ITBIS 18% Ventas'),
|
|
|
|
|
|
('rate', '=', Decimal('0.18')),
|
|
|
|
|
|
], limit=1))
|
|
|
|
|
|
self.assertTrue(TaxCode.search([
|
|
|
|
|
|
('company', '=', company.id),
|
|
|
|
|
|
('name', '=', 'Otros Impuestos y Contribuciones'),
|
|
|
|
|
|
], limit=1))
|
|
|
|
|
|
CreateChart.delete(session_id)
|
2026-07-22 19:12:44 -04:00
|
|
|
|
|
|
|
|
|
|
@with_transaction()
|
|
|
|
|
|
def test_chart_creates_company_accounts_and_taxes(self):
|
|
|
|
|
|
'Test Dominican chart creates usable accounts and taxes'
|
|
|
|
|
|
pool = Pool()
|
|
|
|
|
|
Account = pool.get('account.account')
|
|
|
|
|
|
AccountTemplate = pool.get('account.account.template')
|
|
|
|
|
|
Company = pool.get('company.company')
|
|
|
|
|
|
Currency = pool.get('currency.currency')
|
|
|
|
|
|
ModelData = pool.get('ir.model.data')
|
|
|
|
|
|
Party = pool.get('party.party')
|
|
|
|
|
|
Tax = pool.get('account.tax')
|
|
|
|
|
|
TaxCode = pool.get('account.tax.code')
|
2026-08-09 13:13:22 -04:00
|
|
|
|
TaxCodeLine = pool.get('account.tax.code.line')
|
2026-07-22 19:12:44 -04:00
|
|
|
|
TaxRule = pool.get('account.tax.rule')
|
|
|
|
|
|
CreateChart = pool.get('account.create_chart', type='wizard')
|
|
|
|
|
|
|
|
|
|
|
|
currency, = Currency.create([{
|
|
|
|
|
|
'name': 'Dominican Peso',
|
|
|
|
|
|
'code': 'DOP',
|
|
|
|
|
|
'symbol': 'RD$',
|
|
|
|
|
|
'digits': 2,
|
|
|
|
|
|
'rounding': '0.01',
|
|
|
|
|
|
}])
|
|
|
|
|
|
party, = Party.create([{
|
|
|
|
|
|
'name': 'Empresa Dominicana',
|
|
|
|
|
|
}])
|
|
|
|
|
|
company, = Company.create([{
|
|
|
|
|
|
'party': party.id,
|
|
|
|
|
|
'currency': currency.id,
|
|
|
|
|
|
}])
|
|
|
|
|
|
template = AccountTemplate(ModelData.get_id(
|
2026-08-10 00:08:55 -04:00
|
|
|
|
'account_do', 'do_account_root_en'))
|
2026-07-22 19:12:44 -04:00
|
|
|
|
|
|
|
|
|
|
session_id, _start, _end = CreateChart.create()
|
|
|
|
|
|
chart = CreateChart(session_id)
|
|
|
|
|
|
chart.account.account_template = template
|
|
|
|
|
|
chart.account.company = company
|
|
|
|
|
|
chart.transition_create_account()
|
|
|
|
|
|
|
|
|
|
|
|
receivable, = Account.search([
|
|
|
|
|
|
('company', '=', company.id),
|
|
|
|
|
|
('code', '=', '110201'),
|
|
|
|
|
|
('type.receivable', '=', True),
|
|
|
|
|
|
('party_required', '=', True),
|
|
|
|
|
|
('closed', '!=', True),
|
|
|
|
|
|
], limit=1)
|
|
|
|
|
|
payable, = Account.search([
|
|
|
|
|
|
('company', '=', company.id),
|
|
|
|
|
|
('code', '=', '210101'),
|
|
|
|
|
|
('type.payable', '=', True),
|
|
|
|
|
|
('party_required', '=', True),
|
|
|
|
|
|
('closed', '!=', True),
|
|
|
|
|
|
], limit=1)
|
2026-08-09 13:44:20 -04:00
|
|
|
|
defaults = chart.default_properties([
|
|
|
|
|
|
'company', 'account_receivable', 'account_payable'])
|
|
|
|
|
|
self.assertEqual(defaults['company'], company.id)
|
|
|
|
|
|
self.assertEqual(defaults['account_receivable'], receivable.id)
|
|
|
|
|
|
self.assertEqual(defaults['account_payable'], payable.id)
|
2026-07-22 19:12:44 -04:00
|
|
|
|
for code in [
|
2026-08-09 13:13:22 -04:00
|
|
|
|
'111002', '120192', '120292', '120392', '120591',
|
|
|
|
|
|
'120592', '120891', '120892', '120992', '7101', '7102',
|
2026-08-08 20:56:32 -04:00
|
|
|
|
'110401', '110406', '110407', '110408',
|
|
|
|
|
|
'11040101', '11040102', '11040103', '11040104',
|
|
|
|
|
|
'11040601', '11040801', '11040802', '11040803',
|
|
|
|
|
|
'210201', '210202', '210203', '210206', '210207',
|
|
|
|
|
|
'210208', '210209', '210210', '210211', '210212',
|
|
|
|
|
|
'210213', '210214', '210215', '210216', '210217',
|
|
|
|
|
|
'210218', '210219',
|
|
|
|
|
|
'21020101', '21020102', '21020103', '21020104',
|
|
|
|
|
|
'21020105', '21020201', '21020501', '21020502',
|
|
|
|
|
|
'21020601', '21020701', '21020702', '21020801',
|
|
|
|
|
|
'21020802', '21020803', '21020804', '21020805',
|
|
|
|
|
|
'21020901', '21021001', '21021101', '21021201',
|
|
|
|
|
|
'21021301', '21021302', '21021401', '21021501',
|
|
|
|
|
|
'21021502', '21021701', '21021801', '21021901',
|
|
|
|
|
|
'21021902', '6208']:
|
2026-07-22 19:12:44 -04:00
|
|
|
|
with self.subTest(code=code):
|
|
|
|
|
|
self.assertTrue(Account.search([
|
|
|
|
|
|
('company', '=', company.id),
|
|
|
|
|
|
('code', '=', code),
|
|
|
|
|
|
('closed', '!=', True),
|
|
|
|
|
|
], limit=1))
|
2026-08-09 13:13:22 -04:00
|
|
|
|
company_ifrs_types = {
|
2026-08-10 00:08:55 -04:00
|
|
|
|
'111002': 'Current Assets',
|
|
|
|
|
|
'120591': 'Right-of-use Assets',
|
|
|
|
|
|
'120592': 'Right-of-use Assets',
|
|
|
|
|
|
'120891': 'Investment Property',
|
|
|
|
|
|
'120892': 'Investment Property',
|
|
|
|
|
|
'120992': 'Intangible assets',
|
|
|
|
|
|
'7101': 'Closing and control accounts',
|
|
|
|
|
|
'7102': 'Closing and control accounts',
|
2026-08-09 13:13:22 -04:00
|
|
|
|
}
|
|
|
|
|
|
for code, account_type in company_ifrs_types.items():
|
|
|
|
|
|
with self.subTest(code=code, account_type=account_type):
|
|
|
|
|
|
account, = Account.search([
|
|
|
|
|
|
('company', '=', company.id),
|
|
|
|
|
|
('code', '=', code),
|
|
|
|
|
|
('closed', '!=', True),
|
|
|
|
|
|
], limit=1)
|
|
|
|
|
|
self.assertEqual(account.type.name, account_type)
|
2026-07-22 19:12:44 -04:00
|
|
|
|
|
|
|
|
|
|
self.assertGreaterEqual(len(Account.search([
|
|
|
|
|
|
('company', '=', company.id),
|
|
|
|
|
|
])), 200)
|
|
|
|
|
|
self.assertGreaterEqual(len(Tax.search([
|
|
|
|
|
|
('company', '=', company.id),
|
|
|
|
|
|
])), 25)
|
|
|
|
|
|
self.assertTrue(TaxCode.search([
|
|
|
|
|
|
('company', '=', company.id),
|
2026-08-10 00:08:55 -04:00
|
|
|
|
('name', '=', 'ITBIS — Net Balance (Debit − Credit)'),
|
2026-07-22 19:12:44 -04:00
|
|
|
|
], limit=1))
|
|
|
|
|
|
self.assertTrue(TaxRule.search([
|
|
|
|
|
|
('company', '=', company.id),
|
|
|
|
|
|
('kind', '=', 'sale'),
|
|
|
|
|
|
], limit=1))
|
|
|
|
|
|
self.assertTrue(TaxRule.search([
|
|
|
|
|
|
('company', '=', company.id),
|
|
|
|
|
|
('kind', '=', 'purchase'),
|
|
|
|
|
|
], limit=1))
|
|
|
|
|
|
self.assertTrue(Tax.search([
|
|
|
|
|
|
('company', '=', company.id),
|
2026-08-10 00:08:55 -04:00
|
|
|
|
('description', '=', 'ITBIS 18% Sales'),
|
2026-07-22 19:12:44 -04:00
|
|
|
|
('rate', '=', Decimal('0.18')),
|
|
|
|
|
|
], limit=1))
|
|
|
|
|
|
self.assertTrue(Tax.search([
|
|
|
|
|
|
('company', '=', company.id),
|
2026-08-10 00:08:55 -04:00
|
|
|
|
('description', '=', 'ITBIS Withholding Large Taxpayer 30%'),
|
2026-07-22 19:12:44 -04:00
|
|
|
|
('rate', '=', Decimal('-0.054')),
|
|
|
|
|
|
], limit=1))
|
2026-08-08 20:56:32 -04:00
|
|
|
|
self.assertTrue(Tax.search([
|
|
|
|
|
|
('company', '=', company.id),
|
2026-08-10 00:08:55 -04:00
|
|
|
|
('description', '=', 'ITBIS Withholding Informal Supplier 75%'),
|
2026-08-08 20:56:32 -04:00
|
|
|
|
('rate', '=', Decimal('-0.135')),
|
|
|
|
|
|
], limit=1))
|
2026-08-09 12:19:52 -04:00
|
|
|
|
check_015, = Tax.search([
|
|
|
|
|
|
('company', '=', company.id),
|
2026-08-10 00:08:55 -04:00
|
|
|
|
('description', '=', 'Checks and Transfers Tax 0.15%'),
|
2026-08-09 12:19:52 -04:00
|
|
|
|
], limit=1)
|
|
|
|
|
|
check_020, = Tax.search([
|
|
|
|
|
|
('company', '=', company.id),
|
2026-08-10 00:08:55 -04:00
|
|
|
|
('description', '=', 'Checks and Transfers Tax 0.20%'),
|
2026-08-09 12:19:52 -04:00
|
|
|
|
], limit=1)
|
|
|
|
|
|
bank_rule, = TaxRule.search([
|
|
|
|
|
|
('company', '=', company.id),
|
|
|
|
|
|
('name', '=', (
|
2026-08-10 00:08:55 -04:00
|
|
|
|
'Banking Rule Checks / Electronic Transfers '
|
|
|
|
|
|
'(RD)')),
|
2026-08-09 12:19:52 -04:00
|
|
|
|
], limit=1)
|
|
|
|
|
|
self.assertEqual(check_015.end_date, datetime.date(2026, 7, 2))
|
|
|
|
|
|
self.assertEqual(check_020.start_date, datetime.date(2026, 7, 3))
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
bank_rule.apply(check_015, {
|
|
|
|
|
|
'date': datetime.date(2026, 7, 2),
|
|
|
|
|
|
}),
|
|
|
|
|
|
[check_015.id])
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
bank_rule.apply(check_015, {
|
|
|
|
|
|
'date': datetime.date(2026, 7, 3),
|
|
|
|
|
|
}),
|
|
|
|
|
|
[check_020.id])
|
2026-08-09 13:13:22 -04:00
|
|
|
|
real_tax_code_signs = {
|
2026-08-10 00:08:55 -04:00
|
|
|
|
('ITBIS 18% Sales', 'invoice'): '+',
|
|
|
|
|
|
('ITBIS 18% Sales', 'credit'): '-',
|
|
|
|
|
|
('ITBIS 18% Purchases', 'invoice'): '-',
|
|
|
|
|
|
('ITBIS 18% Purchases', 'credit'): '+',
|
|
|
|
|
|
('ITBIS Withholding Large Taxpayer 30%', 'invoice'): '-',
|
|
|
|
|
|
('ITBIS Withholding Large Taxpayer 30%', 'credit'): '+',
|
|
|
|
|
|
('ISR Withholding Individual 10%', 'invoice'): '-',
|
|
|
|
|
|
('ISR Withholding Individual 10%', 'credit'): '+',
|
|
|
|
|
|
('ISR Withholding State 1.5%', 'invoice'): '+',
|
|
|
|
|
|
('ISR Withholding State 1.5%', 'credit'): '-',
|
|
|
|
|
|
('Checks and Transfers Tax 0.20%', 'invoice'): '+',
|
|
|
|
|
|
('Checks and Transfers Tax 0.20%', 'credit'): '-',
|
2026-08-09 13:13:22 -04:00
|
|
|
|
}
|
|
|
|
|
|
for (description, line_type), operator in real_tax_code_signs.items():
|
|
|
|
|
|
with self.subTest(description=description, line_type=line_type):
|
|
|
|
|
|
tax, = Tax.search([
|
|
|
|
|
|
('company', '=', company.id),
|
|
|
|
|
|
('description', '=', description),
|
|
|
|
|
|
], limit=1)
|
|
|
|
|
|
line, = TaxCodeLine.search([
|
|
|
|
|
|
('tax', '=', tax.id),
|
|
|
|
|
|
('type', '=', line_type),
|
|
|
|
|
|
('amount', '=', 'tax'),
|
|
|
|
|
|
], limit=1)
|
|
|
|
|
|
self.assertEqual(line.operator, operator)
|
2026-07-22 19:12:44 -04:00
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-08-10 00:08:55 -04:00
|
|
|
|
class SpanishAccountDoTestCase(ModuleTestCase):
|
|
|
|
|
|
"Test the Latin American Spanish account_do data set"
|
|
|
|
|
|
module = 'account_do'
|
|
|
|
|
|
extras = AccountDoTestCase.extras
|
|
|
|
|
|
language = 'es_419'
|
|
|
|
|
|
|
|
|
|
|
|
test_spanish_chart_creates_spanish_accounts_and_taxes = (
|
|
|
|
|
|
AccountDoTestCase.test_spanish_chart_creates_spanish_accounts_and_taxes)
|
|
|
|
|
|
|
|
|
|
|
|
@with_transaction()
|
|
|
|
|
|
def test_migrate_pre_language_identifiers(self):
|
|
|
|
|
|
'Test 8.0.0 identifiers migrate without deleting referenced records'
|
|
|
|
|
|
pool = Pool()
|
|
|
|
|
|
AccountTemplate = pool.get('account.account.template')
|
|
|
|
|
|
ModelData = pool.get('ir.model.data')
|
|
|
|
|
|
root_id = ModelData.get_id(
|
|
|
|
|
|
'account_do', 'do_account_root_es_419')
|
|
|
|
|
|
root_data, = ModelData.search([
|
|
|
|
|
|
('module', '=', 'account_do'),
|
|
|
|
|
|
('fs_id', '=', 'do_account_root_es_419'),
|
|
|
|
|
|
])
|
|
|
|
|
|
ModelData.write([root_data], {'fs_id': 'do_account_root'})
|
|
|
|
|
|
legacy, = AccountTemplate.create([{
|
|
|
|
|
|
'name': 'Legacy Obsolete Tax Placeholder',
|
|
|
|
|
|
'parent': root_id,
|
|
|
|
|
|
}])
|
|
|
|
|
|
ModelData.create([{
|
|
|
|
|
|
'module': 'account_do',
|
|
|
|
|
|
'fs_id': 'do_tax_itbis_8_venta',
|
|
|
|
|
|
'model': 'account.account.template',
|
|
|
|
|
|
'db_id': legacy.id,
|
|
|
|
|
|
'noupdate': False,
|
|
|
|
|
|
}])
|
|
|
|
|
|
|
|
|
|
|
|
ModelData._migrate_localization_identifiers()
|
|
|
|
|
|
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
ModelData.get_id('account_do', 'do_account_root_es_419'),
|
|
|
|
|
|
root_id)
|
|
|
|
|
|
self.assertFalse(ModelData.search([
|
|
|
|
|
|
('module', '=', 'account_do'),
|
|
|
|
|
|
('fs_id', '=', 'do_tax_itbis_8_venta'),
|
|
|
|
|
|
]))
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-22 19:12:44 -04:00
|
|
|
|
class AccountDoUnitTestCase(unittest.TestCase):
|
|
|
|
|
|
|
2026-08-09 13:24:59 -04:00
|
|
|
|
def test_static_project_configuration_is_complete(self):
|
|
|
|
|
|
config = ConfigParser()
|
|
|
|
|
|
config.read(MODULE_DIR / 'tryton.cfg', encoding='utf-8')
|
|
|
|
|
|
|
|
|
|
|
|
def lines(section, option):
|
|
|
|
|
|
return [
|
|
|
|
|
|
line.strip()
|
|
|
|
|
|
for line in config.get(section, option).splitlines()
|
|
|
|
|
|
if line.strip()
|
|
|
|
|
|
]
|
|
|
|
|
|
|
2026-08-10 00:08:55 -04:00
|
|
|
|
self.assertEqual(config.get('tryton', 'version'), '8.0.1')
|
2026-08-09 13:24:59 -04:00
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
set(lines('tryton', 'depends')),
|
2026-08-10 00:08:55 -04:00
|
|
|
|
{'account', 'ir'})
|
2026-08-09 13:24:59 -04:00
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
lines('tryton', 'xml'),
|
|
|
|
|
|
[
|
2026-08-10 00:08:55 -04:00
|
|
|
|
'account_chart_do_en.xml',
|
|
|
|
|
|
'tax_do_en.xml',
|
|
|
|
|
|
'tax_code_do_en.xml',
|
|
|
|
|
|
'tax_rule_do_en.xml',
|
|
|
|
|
|
'account_chart_do_es_419.xml',
|
|
|
|
|
|
'tax_do_es_419.xml',
|
|
|
|
|
|
'tax_code_do_es_419.xml',
|
|
|
|
|
|
'tax_rule_do_es_419.xml',
|
2026-08-09 13:24:59 -04:00
|
|
|
|
])
|
2026-08-10 00:08:55 -04:00
|
|
|
|
self.assertEqual(lines('register', 'model'), ['ir.ModelData'])
|
2026-08-09 13:44:20 -04:00
|
|
|
|
self.assertEqual(lines('register', 'wizard'), ['account.CreateChart'])
|
2026-08-09 13:24:59 -04:00
|
|
|
|
for filename in lines('tryton', 'xml'):
|
|
|
|
|
|
self.assertTrue((MODULE_DIR / filename).is_file())
|
2026-08-09 13:44:20 -04:00
|
|
|
|
self.assertTrue((MODULE_DIR / 'account.py').is_file())
|
2026-08-09 13:24:59 -04:00
|
|
|
|
pyproject = (MODULE_DIR / 'pyproject.toml').read_text(encoding='utf-8')
|
|
|
|
|
|
self.assertIn("name = 'trytond_account_do'", pyproject)
|
|
|
|
|
|
self.assertIn(
|
|
|
|
|
|
"account_do = 'trytond.modules.account_do'", pyproject)
|
2026-08-10 00:08:55 -04:00
|
|
|
|
self.assertIn(
|
|
|
|
|
|
'{name = "Fundación Un País Mejor"}', pyproject)
|
|
|
|
|
|
self.assertIn(
|
|
|
|
|
|
'homepage = "https://www.unpaismejor.org.do"', pyproject)
|
|
|
|
|
|
self.assertIn(
|
|
|
|
|
|
('repository = "https://code.unpaismejor.org.do/'
|
|
|
|
|
|
'tryton-do/account_do"'), pyproject)
|
|
|
|
|
|
self.assertIn("exclude = ['doc']", pyproject)
|
|
|
|
|
|
for package_file in ['**/*.xml', 'tests/**/*.rst']:
|
2026-08-09 13:24:59 -04:00
|
|
|
|
self.assertIn(package_file, pyproject)
|
2026-08-10 00:08:55 -04:00
|
|
|
|
for filename in [
|
|
|
|
|
|
'__init__.py',
|
|
|
|
|
|
'account.py',
|
|
|
|
|
|
'doc/conf.py',
|
|
|
|
|
|
'tests/test_scenario.py',
|
|
|
|
|
|
]:
|
|
|
|
|
|
with self.subTest(filename=filename):
|
|
|
|
|
|
source = (MODULE_DIR / filename).read_text(encoding='utf-8')
|
|
|
|
|
|
self.assertIn('This file is part of Tryton.', source)
|
2026-08-09 13:44:20 -04:00
|
|
|
|
account = (MODULE_DIR / 'account.py').read_text(encoding='utf-8')
|
|
|
|
|
|
self.assertIn("class CreateChart(metaclass=PoolMeta):", account)
|
|
|
|
|
|
self.assertIn(
|
2026-08-10 00:08:55 -04:00
|
|
|
|
"'account_do.do_account_110201_en'", account)
|
2026-08-09 13:44:20 -04:00
|
|
|
|
self.assertIn(
|
2026-08-10 00:08:55 -04:00
|
|
|
|
"'account_do.do_account_210101_en'", account)
|
|
|
|
|
|
readme = (MODULE_DIR / 'README.rst').read_text(encoding='utf-8')
|
|
|
|
|
|
self.assertNotIn('100% de la base estructural', readme)
|
|
|
|
|
|
self.assertNotIn('catalogo_cuentas_niif_rd_tryton_completo', readme)
|
|
|
|
|
|
for filename in [
|
|
|
|
|
|
'doc/conf.py',
|
|
|
|
|
|
'doc/design.rst',
|
|
|
|
|
|
'doc/reference.rst',
|
|
|
|
|
|
'doc/releases.rst',
|
|
|
|
|
|
]:
|
|
|
|
|
|
self.assertTrue((MODULE_DIR / filename).is_file())
|
2026-08-09 13:24:59 -04:00
|
|
|
|
|
|
|
|
|
|
def test_xml_record_inventory_is_explicit(self):
|
|
|
|
|
|
expected = {
|
2026-08-10 00:08:55 -04:00
|
|
|
|
'account_chart_do_en.xml': {
|
2026-08-09 13:24:59 -04:00
|
|
|
|
'account.account.type.template': 36,
|
|
|
|
|
|
'account.account.template': 281,
|
|
|
|
|
|
},
|
2026-08-10 00:08:55 -04:00
|
|
|
|
'tax_do_en.xml': {
|
2026-08-09 13:24:59 -04:00
|
|
|
|
'account.tax.group': 7,
|
2026-08-10 00:08:55 -04:00
|
|
|
|
'account.tax.template': 50,
|
2026-08-09 13:24:59 -04:00
|
|
|
|
},
|
2026-08-10 00:08:55 -04:00
|
|
|
|
'tax_code_do_en.xml': {
|
|
|
|
|
|
'account.tax.code.template': 44,
|
|
|
|
|
|
'account.tax.code.line.template': 93,
|
2026-08-09 13:24:59 -04:00
|
|
|
|
},
|
2026-08-10 00:08:55 -04:00
|
|
|
|
'tax_rule_do_en.xml': {
|
|
|
|
|
|
'account.tax.rule.template': 23,
|
|
|
|
|
|
'account.tax.rule.line.template': 30,
|
2026-08-09 13:24:59 -04:00
|
|
|
|
},
|
2026-08-10 00:08:55 -04:00
|
|
|
|
'account_chart_do_es_419.xml': {
|
|
|
|
|
|
'account.account.type.template': 36,
|
|
|
|
|
|
'account.account.template': 281,
|
|
|
|
|
|
},
|
|
|
|
|
|
'tax_do_es_419.xml': {
|
|
|
|
|
|
'account.tax.group': 7,
|
|
|
|
|
|
'account.tax.template': 50,
|
|
|
|
|
|
},
|
|
|
|
|
|
'tax_code_do_es_419.xml': {
|
|
|
|
|
|
'account.tax.code.template': 44,
|
|
|
|
|
|
'account.tax.code.line.template': 93,
|
|
|
|
|
|
},
|
|
|
|
|
|
'tax_rule_do_es_419.xml': {
|
|
|
|
|
|
'account.tax.rule.template': 23,
|
|
|
|
|
|
'account.tax.rule.line.template': 30,
|
2026-08-09 13:24:59 -04:00
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
for filename, expected_counts in expected.items():
|
|
|
|
|
|
records = list(_iter_xml_records(filename))
|
|
|
|
|
|
with self.subTest(filename=filename):
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
dict(Counter(model for _, _, model, _ in records)),
|
|
|
|
|
|
expected_counts)
|
|
|
|
|
|
self.assertFalse([
|
|
|
|
|
|
(record_id, model)
|
|
|
|
|
|
for _, record_id, model, _ in records
|
|
|
|
|
|
if not record_id or not model])
|
|
|
|
|
|
duplicate_ids = [
|
|
|
|
|
|
record_id
|
|
|
|
|
|
for record_id, count in Counter(
|
|
|
|
|
|
record_id for _, record_id, _, _ in records).items()
|
|
|
|
|
|
if count > 1]
|
|
|
|
|
|
self.assertEqual(duplicate_ids, [])
|
|
|
|
|
|
|
2026-08-10 00:08:55 -04:00
|
|
|
|
def test_xml_data_is_scoped_to_its_language(self):
|
|
|
|
|
|
'Every data section follows the language-specific chart convention'
|
|
|
|
|
|
for language in ['en', 'es_419']:
|
|
|
|
|
|
for stem in [
|
|
|
|
|
|
'account_chart_do', 'tax_do', 'tax_code_do',
|
|
|
|
|
|
'tax_rule_do']:
|
|
|
|
|
|
filename = f'{stem}_{language}.xml'
|
|
|
|
|
|
root = ET.parse(MODULE_DIR / filename).getroot()
|
|
|
|
|
|
with self.subTest(filename=filename):
|
|
|
|
|
|
self.assertTrue(root.findall('data'))
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
{data.get('language') for data in root.findall('data')},
|
|
|
|
|
|
{language})
|
2026-08-09 13:24:59 -04:00
|
|
|
|
|
2026-08-10 00:08:55 -04:00
|
|
|
|
def test_english_accounting_terminology_is_consistent(self):
|
|
|
|
|
|
'English datasets avoid literal Spanish accounting translations'
|
|
|
|
|
|
text = '\n'.join(
|
|
|
|
|
|
field.text.strip()
|
|
|
|
|
|
for filename in [
|
|
|
|
|
|
'account_chart_do_en.xml', 'tax_do_en.xml',
|
|
|
|
|
|
'tax_code_do_en.xml', 'tax_rule_do_en.xml']
|
|
|
|
|
|
for field in ET.parse(MODULE_DIR / filename).getroot().findall(
|
|
|
|
|
|
'.//field')
|
|
|
|
|
|
if field.get('name') in {'name', 'description'} and field.text)
|
|
|
|
|
|
for phrase in [
|
|
|
|
|
|
'advance itbis', 'assets for right of use',
|
|
|
|
|
|
'charged for paying', 'clients tax rule',
|
|
|
|
|
|
'collected to pay', 'deterioration', 'in favor',
|
|
|
|
|
|
'itbis supported', 'other income and profits',
|
|
|
|
|
|
'physical persons', 'retention', 'withheld status',
|
|
|
|
|
|
]:
|
|
|
|
|
|
with self.subTest(phrase=phrase):
|
|
|
|
|
|
self.assertNotIn(phrase, text.lower())
|
2026-08-09 13:24:59 -04:00
|
|
|
|
|
2026-08-10 00:08:55 -04:00
|
|
|
|
def test_spanish_chart_has_complete_parallel_identifiers(self):
|
|
|
|
|
|
pairs = [
|
|
|
|
|
|
('account_chart_do_en.xml', 'account_chart_do_es_419.xml'),
|
|
|
|
|
|
('tax_do_en.xml', 'tax_do_es_419.xml'),
|
|
|
|
|
|
('tax_code_do_en.xml', 'tax_code_do_es_419.xml'),
|
|
|
|
|
|
('tax_rule_do_en.xml', 'tax_rule_do_es_419.xml'),
|
2026-08-09 13:24:59 -04:00
|
|
|
|
]
|
2026-08-10 00:08:55 -04:00
|
|
|
|
for english_file, spanish_file in pairs:
|
|
|
|
|
|
english = {
|
|
|
|
|
|
record_id: model
|
|
|
|
|
|
for _, record_id, model, _ in _iter_xml_records(english_file)
|
|
|
|
|
|
}
|
|
|
|
|
|
spanish = {
|
|
|
|
|
|
record_id: model
|
|
|
|
|
|
for _, record_id, model, _ in _iter_xml_records(spanish_file)
|
|
|
|
|
|
}
|
|
|
|
|
|
with self.subTest(spanish_file=spanish_file):
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
spanish,
|
|
|
|
|
|
{record_id + '_es_419': model
|
|
|
|
|
|
for record_id, model in english.items()})
|
2026-08-09 13:24:59 -04:00
|
|
|
|
|
2026-08-10 00:08:55 -04:00
|
|
|
|
spanish_accounts = {
|
|
|
|
|
|
record_id: values
|
2026-08-09 13:24:59 -04:00
|
|
|
|
for _, record_id, model, values in _iter_xml_records(
|
2026-08-10 00:08:55 -04:00
|
|
|
|
'account_chart_do_es_419.xml')
|
2026-08-09 13:24:59 -04:00
|
|
|
|
if model == 'account.account.template'
|
|
|
|
|
|
}
|
2026-08-10 00:08:55 -04:00
|
|
|
|
spanish_taxes = {
|
|
|
|
|
|
record_id: values
|
|
|
|
|
|
for _, record_id, model, values in _iter_xml_records(
|
|
|
|
|
|
'tax_do_es_419.xml')
|
|
|
|
|
|
if model == 'account.tax.template'
|
2026-08-09 13:24:59 -04:00
|
|
|
|
}
|
2026-08-10 00:08:55 -04:00
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
spanish_accounts['do_account_root_es_419']['name'],
|
|
|
|
|
|
'Plan de Cuentas NIIF - República Dominicana')
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
spanish_accounts['do_account_110201_es_419']['name'],
|
|
|
|
|
|
'Cuentas por cobrar clientes')
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
spanish_taxes['do_tax_itbis_18_venta_es_419']['description'],
|
|
|
|
|
|
'ITBIS 18% Ventas')
|
2026-08-09 13:24:59 -04:00
|
|
|
|
|
2026-07-22 19:12:44 -04:00
|
|
|
|
def test_xml_references_are_resolved_inside_module(self):
|
|
|
|
|
|
records = list(_iter_xml_records(
|
2026-08-10 00:08:55 -04:00
|
|
|
|
'account_chart_do_en.xml',
|
|
|
|
|
|
'tax_do_en.xml',
|
|
|
|
|
|
'tax_code_do_en.xml',
|
|
|
|
|
|
'tax_rule_do_en.xml',
|
|
|
|
|
|
'account_chart_do_es_419.xml',
|
|
|
|
|
|
'tax_do_es_419.xml',
|
|
|
|
|
|
'tax_code_do_es_419.xml',
|
|
|
|
|
|
'tax_rule_do_es_419.xml',
|
2026-07-22 19:12:44 -04:00
|
|
|
|
))
|
2026-08-10 00:08:55 -04:00
|
|
|
|
ids = set()
|
|
|
|
|
|
for filename in {
|
|
|
|
|
|
filename for filename, _record_id, _model, _values in records}:
|
|
|
|
|
|
root = ET.parse(MODULE_DIR / filename).getroot()
|
|
|
|
|
|
ids.update(
|
|
|
|
|
|
record.get('id') for record in root.findall('.//record'))
|
2026-07-22 19:12:44 -04:00
|
|
|
|
missing = []
|
|
|
|
|
|
for filename in [
|
2026-08-10 00:08:55 -04:00
|
|
|
|
'account_chart_do_en.xml',
|
|
|
|
|
|
'tax_do_en.xml',
|
|
|
|
|
|
'tax_code_do_en.xml',
|
|
|
|
|
|
'tax_rule_do_en.xml',
|
|
|
|
|
|
'account_chart_do_es_419.xml',
|
|
|
|
|
|
'tax_do_es_419.xml',
|
|
|
|
|
|
'tax_code_do_es_419.xml',
|
|
|
|
|
|
'tax_rule_do_es_419.xml',
|
2026-07-22 19:12:44 -04:00
|
|
|
|
]:
|
|
|
|
|
|
root = ET.parse(MODULE_DIR / filename).getroot()
|
|
|
|
|
|
for record in root.findall('.//record'):
|
|
|
|
|
|
for field in record.findall('field'):
|
|
|
|
|
|
ref = field.get('ref')
|
|
|
|
|
|
if ref and '.' not in ref and ref not in ids:
|
|
|
|
|
|
missing.append(
|
|
|
|
|
|
(filename, record.get('id'), field.get('name'), ref))
|
|
|
|
|
|
self.assertEqual(missing, [])
|
|
|
|
|
|
|
|
|
|
|
|
def test_chart_template_codes_are_unique(self):
|
|
|
|
|
|
accounts = [
|
|
|
|
|
|
values for _, _, model, values in _iter_xml_records(
|
2026-08-10 00:08:55 -04:00
|
|
|
|
'account_chart_do_en.xml')
|
2026-07-22 19:12:44 -04:00
|
|
|
|
if model == 'account.account.template'
|
|
|
|
|
|
]
|
|
|
|
|
|
codes = [values['code'] for values in accounts if values.get('code')]
|
|
|
|
|
|
duplicates = [
|
|
|
|
|
|
code for code, count in Counter(codes).items() if count > 1]
|
|
|
|
|
|
self.assertEqual(duplicates, [])
|
|
|
|
|
|
|
|
|
|
|
|
def test_chart_root_and_statement_classification(self):
|
|
|
|
|
|
accounts = {
|
|
|
|
|
|
record_id: values
|
|
|
|
|
|
for _, record_id, model, values in _iter_xml_records(
|
2026-08-10 00:08:55 -04:00
|
|
|
|
'account_chart_do_en.xml')
|
2026-07-22 19:12:44 -04:00
|
|
|
|
if model == 'account.account.template'
|
|
|
|
|
|
}
|
|
|
|
|
|
types = {
|
|
|
|
|
|
record_id: values
|
|
|
|
|
|
for _, record_id, model, values in _iter_xml_records(
|
2026-08-10 00:08:55 -04:00
|
|
|
|
'account_chart_do_en.xml')
|
2026-07-22 19:12:44 -04:00
|
|
|
|
if model == 'account.account.type.template'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
self.assertEqual([
|
|
|
|
|
|
accounts[f'do_account_{code}']['code']
|
|
|
|
|
|
for code in range(1, 8)
|
|
|
|
|
|
], ['1', '2', '3', '4', '5', '6', '7'])
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
types['do_type_retained_earnings']['statement'], 'balance')
|
|
|
|
|
|
self.assertEqual(types['do_type_revenue']['statement'], 'income')
|
|
|
|
|
|
self.assertEqual(types['do_type_expense']['statement'], 'income')
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
accounts['do_account_110201']['type'], 'do_type_receivable')
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
accounts['do_account_210101']['type'], 'do_type_payable')
|
|
|
|
|
|
|
2026-08-09 13:13:22 -04:00
|
|
|
|
def test_ifrs_chart_is_complete_and_typed(self):
|
|
|
|
|
|
accounts = {
|
|
|
|
|
|
values['code']: values
|
|
|
|
|
|
for _, _, model, values in _iter_xml_records(
|
2026-08-10 00:08:55 -04:00
|
|
|
|
'account_chart_do_en.xml')
|
2026-08-09 13:13:22 -04:00
|
|
|
|
if model == 'account.account.template' and values.get('code')
|
|
|
|
|
|
}
|
|
|
|
|
|
types = {
|
|
|
|
|
|
record_id: values
|
|
|
|
|
|
for _, record_id, model, values in _iter_xml_records(
|
2026-08-10 00:08:55 -04:00
|
|
|
|
'account_chart_do_en.xml')
|
2026-08-09 13:13:22 -04:00
|
|
|
|
if model == 'account.account.type.template'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
self.assertLessEqual(IFRS_REQUIRED_ACCOUNT_CODES, set(accounts))
|
|
|
|
|
|
self.assertEqual(types['do_type_control']['statement'], 'off-balance')
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
types['do_type_deferred_tax_asset']['statement'], 'balance')
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
types['do_type_deferred_tax_liability']['statement'], 'balance')
|
|
|
|
|
|
self.assertEqual(types['do_type_rou_asset']['statement'], 'balance')
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
types['do_type_investment_property']['statement'], 'balance')
|
|
|
|
|
|
self.assertEqual(types['do_type_intangible']['statement'], 'balance')
|
|
|
|
|
|
|
|
|
|
|
|
missing_type = [
|
|
|
|
|
|
(code, values['name'])
|
|
|
|
|
|
for code, values in accounts.items()
|
|
|
|
|
|
if values.get('closed') != 'True' and not values.get('type')
|
|
|
|
|
|
]
|
|
|
|
|
|
self.assertEqual(missing_type, [])
|
|
|
|
|
|
|
|
|
|
|
|
for code, account_type in IFRS_ACCOUNT_TYPE_AUDIT.items():
|
|
|
|
|
|
with self.subTest(code=code):
|
|
|
|
|
|
self.assertEqual(accounts[code]['type'], account_type)
|
|
|
|
|
|
|
|
|
|
|
|
def test_ifrs_policy_matrix_is_represented_in_chart(self):
|
|
|
|
|
|
accounts = {
|
|
|
|
|
|
values['code']
|
|
|
|
|
|
for _, _, model, values in _iter_xml_records(
|
2026-08-10 00:08:55 -04:00
|
|
|
|
'account_chart_do_en.xml')
|
2026-08-09 13:13:22 -04:00
|
|
|
|
if model == 'account.account.template' and values.get('code')
|
|
|
|
|
|
}
|
|
|
|
|
|
for standard, codes in IFRS_POLICY_COVERAGE.items():
|
|
|
|
|
|
with self.subTest(standard=standard):
|
|
|
|
|
|
self.assertLessEqual(codes, accounts)
|
|
|
|
|
|
|
2026-07-22 19:12:44 -04:00
|
|
|
|
def test_no_spanish_technical_other_tax_ids_remain(self):
|
|
|
|
|
|
ids = {
|
|
|
|
|
|
record_id
|
|
|
|
|
|
for _, record_id, _, _ in _iter_xml_records(
|
2026-08-10 00:08:55 -04:00
|
|
|
|
'tax_do_en.xml', 'tax_code_do_en.xml')
|
2026-07-22 19:12:44 -04:00
|
|
|
|
}
|
|
|
|
|
|
self.assertFalse([record_id for record_id in ids if 'otros' in record_id])
|
|
|
|
|
|
|
2026-08-10 00:08:55 -04:00
|
|
|
|
def test_tax_templates_are_complete(self):
|
2026-07-22 19:12:44 -04:00
|
|
|
|
taxes = {
|
|
|
|
|
|
record_id: values
|
2026-08-10 00:08:55 -04:00
|
|
|
|
for _, record_id, model, values in _iter_xml_records('tax_do_en.xml')
|
2026-07-22 19:12:44 -04:00
|
|
|
|
if model == 'account.tax.template'
|
|
|
|
|
|
}
|
2026-08-09 13:13:22 -04:00
|
|
|
|
accounts = {
|
|
|
|
|
|
record_id: values
|
|
|
|
|
|
for _, record_id, model, values in _iter_xml_records(
|
2026-08-10 00:08:55 -04:00
|
|
|
|
'account_chart_do_en.xml')
|
2026-08-09 13:13:22 -04:00
|
|
|
|
if model == 'account.account.template'
|
2026-08-08 20:56:32 -04:00
|
|
|
|
}
|
2026-08-10 00:08:55 -04:00
|
|
|
|
self.assertEqual(len(taxes), 50)
|
|
|
|
|
|
for record_id, values in taxes.items():
|
2026-08-08 20:56:32 -04:00
|
|
|
|
with self.subTest(record_id=record_id):
|
|
|
|
|
|
self.assertEqual(
|
2026-08-10 00:08:55 -04:00
|
|
|
|
values.get('credit_note_account'),
|
|
|
|
|
|
values.get('invoice_account'))
|
|
|
|
|
|
account_id = values.get('invoice_account')
|
|
|
|
|
|
if values['type'] != 'none':
|
|
|
|
|
|
self.assertTrue(account_id)
|
2026-08-09 13:13:22 -04:00
|
|
|
|
self.assertIn(account_id, accounts)
|
2026-08-10 00:08:55 -04:00
|
|
|
|
self.assertTrue(
|
|
|
|
|
|
values.get('rate') or values.get('amount'))
|
2026-08-09 13:13:22 -04:00
|
|
|
|
else:
|
|
|
|
|
|
self.assertFalse(values.get('rate'))
|
|
|
|
|
|
self.assertTrue(values.get('legal_notice'))
|
|
|
|
|
|
|
2026-07-22 19:12:44 -04:00
|
|
|
|
self.assertIn(
|
|
|
|
|
|
"Decimal('-5.4')/100",
|
|
|
|
|
|
taxes['do_tax_ret_itbis_30']['rate'])
|
2026-08-08 20:56:32 -04:00
|
|
|
|
self.assertIn(
|
|
|
|
|
|
"Decimal('-13.5')/100",
|
|
|
|
|
|
taxes['do_tax_ret_itbis_75_inf']['rate'])
|
|
|
|
|
|
for record_id in [
|
|
|
|
|
|
'do_tax_ret_isr_hon_5',
|
|
|
|
|
|
'do_tax_ret_isr_serv_10',
|
|
|
|
|
|
'do_tax_ret_isr_alq_10',
|
|
|
|
|
|
'do_tax_ret_isr_est_5',
|
|
|
|
|
|
'do_tax_ret_isr_bovine_1',
|
|
|
|
|
|
'do_tax_ret_isr_exporter_25',
|
|
|
|
|
|
'do_tax_ret_isr_ext_27',
|
|
|
|
|
|
]:
|
|
|
|
|
|
self.assertIn("Decimal('-", taxes[record_id]['rate'])
|
|
|
|
|
|
self.assertIn(
|
|
|
|
|
|
"Decimal('10')/100",
|
|
|
|
|
|
taxes['do_tax_isc_bebidas_alc']['rate'])
|
|
|
|
|
|
self.assertEqual(
|
2026-08-10 00:08:55 -04:00
|
|
|
|
taxes['do_tax_ret_isr_bovine_1']['start_date'],
|
|
|
|
|
|
'datetime.date(2025, 6, 20)')
|
|
|
|
|
|
self.assertIn(
|
|
|
|
|
|
'arts. 401-405; art. 404',
|
|
|
|
|
|
taxes['do_tax_activos_1']['legal_notice'])
|
|
|
|
|
|
self.assertIn(
|
|
|
|
|
|
'art. 7 Ley 173-07',
|
|
|
|
|
|
taxes['do_tax_iti_3']['legal_notice'])
|
|
|
|
|
|
self.assertIn(
|
|
|
|
|
|
'art. 228', taxes['do_tax_propina_10']['legal_notice'])
|
2026-07-22 19:12:44 -04:00
|
|
|
|
self.assertEqual(
|
2026-08-10 00:08:55 -04:00
|
|
|
|
taxes['do_tax_isc_vehiculos']['group'],
|
|
|
|
|
|
'do_tax_group_others')
|
|
|
|
|
|
self.assertNotIn('do_tax_itbis_8_venta', taxes)
|
|
|
|
|
|
self.assertNotIn('do_tax_itbis_9_venta', taxes)
|
2026-08-09 12:19:52 -04:00
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
taxes['do_tax_cheques_015']['end_date'],
|
|
|
|
|
|
'datetime.date(2026, 7, 2)')
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
taxes['do_tax_cheques_020']['start_date'],
|
|
|
|
|
|
'datetime.date(2026, 7, 3)')
|
2026-08-08 20:56:32 -04:00
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
taxes['do_tax_cheques_020']['invoice_account'],
|
|
|
|
|
|
'do_account_21021902')
|
2026-07-22 19:12:44 -04:00
|
|
|
|
self.assertIn(
|
|
|
|
|
|
"Decimal('0.20')/100",
|
|
|
|
|
|
taxes['do_tax_cheques_020']['rate'])
|
|
|
|
|
|
|
2026-08-10 00:08:55 -04:00
|
|
|
|
def test_tax_validation_register_is_complete(self):
|
|
|
|
|
|
'Every tax template is inventoried in the validation register'
|
|
|
|
|
|
taxes = {
|
|
|
|
|
|
record_id
|
|
|
|
|
|
for _, record_id, model, _ in _iter_xml_records('tax_do_en.xml')
|
|
|
|
|
|
if model == 'account.tax.template'
|
|
|
|
|
|
}
|
|
|
|
|
|
documentation = (MODULE_DIR / 'doc' / 'tax_validation.rst').read_text(
|
|
|
|
|
|
encoding='utf-8')
|
|
|
|
|
|
documented = {
|
|
|
|
|
|
token.strip('`')
|
|
|
|
|
|
for token in documentation.split()
|
|
|
|
|
|
if token.startswith('``do_tax_') and token.endswith('``')
|
|
|
|
|
|
}
|
|
|
|
|
|
self.assertEqual(documented, taxes)
|
|
|
|
|
|
|
2026-07-22 19:12:44 -04:00
|
|
|
|
def test_tax_code_templates_use_expected_signs(self):
|
|
|
|
|
|
codes = {
|
|
|
|
|
|
record_id: values
|
|
|
|
|
|
for _, record_id, model, values in _iter_xml_records(
|
2026-08-10 00:08:55 -04:00
|
|
|
|
'tax_code_do_en.xml')
|
2026-07-22 19:12:44 -04:00
|
|
|
|
if model == 'account.tax.code.template'
|
|
|
|
|
|
}
|
|
|
|
|
|
lines = {
|
|
|
|
|
|
record_id: values
|
|
|
|
|
|
for _, record_id, model, values in _iter_xml_records(
|
2026-08-10 00:08:55 -04:00
|
|
|
|
'tax_code_do_en.xml')
|
2026-07-22 19:12:44 -04:00
|
|
|
|
if model == 'account.tax.code.line.template'
|
|
|
|
|
|
}
|
|
|
|
|
|
for record_id in [
|
|
|
|
|
|
'do_tc_itbis',
|
|
|
|
|
|
'do_tc_isr',
|
|
|
|
|
|
'do_tc_isc',
|
|
|
|
|
|
'do_tc_cdt',
|
|
|
|
|
|
'do_tc_others',
|
|
|
|
|
|
]:
|
|
|
|
|
|
self.assertEqual(codes[record_id].get('parent'), 'None')
|
2026-08-10 00:08:55 -04:00
|
|
|
|
self.assertEqual(codes['do_tc_isr']['name'], 'ISR: Withholdings')
|
2026-07-22 19:12:44 -04:00
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
lines['do_tcl_isr_int_pf_inv']['code'],
|
|
|
|
|
|
'do_tc_isr_intereses_pf')
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
lines['do_tcl_isr_int_pf_cr']['code'],
|
|
|
|
|
|
'do_tc_isr_intereses_pf')
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
lines['do_tcl_isr_int_pj_inv']['code'],
|
|
|
|
|
|
'do_tc_isr_intereses')
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
lines['do_tcl_isr_int_pj_cr']['code'],
|
|
|
|
|
|
'do_tc_isr_intereses')
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
lines['do_tcl_chq020_inv']['code'],
|
|
|
|
|
|
'do_tc_others_checks_020')
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
lines['do_tcl_chq020_cr']['code'],
|
|
|
|
|
|
'do_tc_others_checks_020')
|
2026-08-09 13:13:22 -04:00
|
|
|
|
for record_id, operator in TAX_CODE_LINE_AUDIT.items():
|
|
|
|
|
|
with self.subTest(record_id=record_id):
|
|
|
|
|
|
self.assertEqual(lines[record_id]['operator'], operator)
|
2026-07-22 19:12:44 -04:00
|
|
|
|
self.assertEqual(lines['do_tcl_itbis18v_inv']['operator'], '+')
|
|
|
|
|
|
self.assertEqual(lines['do_tcl_itbis18c_inv']['operator'], '-')
|
2026-08-08 20:56:32 -04:00
|
|
|
|
self.assertEqual(lines['do_tcl_ret_itbis_inv']['operator'], '-')
|
|
|
|
|
|
self.assertEqual(lines['do_tcl_ret_itbis_inf75_inv']['operator'], '-')
|
|
|
|
|
|
self.assertEqual(lines['do_tcl_isr_hon_inv']['operator'], '-')
|
|
|
|
|
|
self.assertEqual(lines['do_tcl_isr_est_inv']['operator'], '+')
|
|
|
|
|
|
self.assertEqual(lines['do_tcl_isr_est5_inv']['operator'], '+')
|
|
|
|
|
|
self.assertEqual(lines['do_tcl_isr_bovine_inv']['operator'], '-')
|
|
|
|
|
|
self.assertEqual(lines['do_tcl_isr_exporter_inv']['operator'], '+')
|
|
|
|
|
|
self.assertEqual(lines['do_tcl_isc_comb_inv']['operator'], '+')
|
2026-07-22 19:12:44 -04:00
|
|
|
|
self.assertEqual(lines['do_tcl_prop_inv']['code'], 'do_tc_others_tip')
|
|
|
|
|
|
|
2026-08-09 12:19:52 -04:00
|
|
|
|
def test_tax_rule_templates_include_date_sensitive_bank_tax(self):
|
|
|
|
|
|
rules = {
|
|
|
|
|
|
record_id: values
|
|
|
|
|
|
for _, record_id, model, values in _iter_xml_records(
|
2026-08-10 00:08:55 -04:00
|
|
|
|
'tax_rule_do_en.xml')
|
2026-08-09 12:19:52 -04:00
|
|
|
|
if model == 'account.tax.rule.template'
|
|
|
|
|
|
}
|
|
|
|
|
|
lines = {
|
|
|
|
|
|
record_id: values
|
|
|
|
|
|
for _, record_id, model, values in _iter_xml_records(
|
2026-08-10 00:08:55 -04:00
|
|
|
|
'tax_rule_do_en.xml')
|
2026-08-09 12:19:52 -04:00
|
|
|
|
if model == 'account.tax.rule.line.template'
|
|
|
|
|
|
}
|
|
|
|
|
|
self.assertIn('do_tax_rule_bank_check_transfer', rules)
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
lines['do_trline_bank_check_transfer_015']['origin_tax'],
|
|
|
|
|
|
'do_tax_cheques_015')
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
lines['do_trline_bank_check_transfer_015']['tax'],
|
|
|
|
|
|
'do_tax_cheques_015')
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
lines['do_trline_bank_check_transfer_015']['end_date'],
|
|
|
|
|
|
'datetime.date(2026, 7, 2)')
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
lines['do_trline_bank_check_transfer_020']['origin_tax'],
|
|
|
|
|
|
'do_tax_cheques_015')
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
lines['do_trline_bank_check_transfer_020']['tax'],
|
|
|
|
|
|
'do_tax_cheques_020')
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
lines['do_trline_bank_check_transfer_020']['start_date'],
|
|
|
|
|
|
'datetime.date(2026, 7, 3)')
|
|
|
|
|
|
|
2026-07-22 19:12:44 -04:00
|
|
|
|
del ModuleTestCase
|