"""Tests for the double-entry ledger and its integration with the tax engine. Run from the project root: python -m unittest discover -s tests """ import unittest from decimal import Decimal from src.engine import ( isr_provisional_monthly, iva_monthly, resico_isr_monthly, taxable_base, ) from src.ledger import Ledger, Line def dec(s): return Decimal(s) class TestPosting(unittest.TestCase): def setUp(self): self.lg = Ledger(":memory:") def tearDown(self): self.lg.close() def test_unbalanced_entry_rejected(self): with self.assertRaises(ValueError): self.lg.post( "2024-05-01", "bad", [Line("102.01", debit=100), Line("401.01", credit=50)], ) def test_empty_entry_rejected(self): with self.assertRaises(ValueError): self.lg.post("2024-05-01", "empty", [Line("102.01", debit=0)]) def test_income_builds_balanced_entry(self): self.lg.record_income("2024-05-10", "Diseño de logo", 45000, iva_rate="0.16") # bank 52,200 ; income 45,000 ; iva trasladado 7,200 self.assertEqual(self.lg.account_balance("102.01"), dec("52200.00")) self.assertEqual(self.lg.account_balance("401.01"), dec("45000.00")) self.assertEqual(self.lg.account_balance("213.01"), dec("7200.00")) def test_deductible_expense_splits_iva(self): self.lg.record_expense("2024-05-15", "Licencia Adobe", 12000, iva_rate="0.16") self.assertEqual(self.lg.account_balance("601.01"), dec("12000.00")) # expense self.assertEqual(self.lg.account_balance("118.01"), dec("1920.00")) # iva acreditable self.assertEqual(self.lg.account_balance("102.01"), dec("-13920.00")) # bank out def test_income_with_retentions(self): # Persona física → persona moral: 10% ISR + 10.6667% IVA retained self.lg.record_income( "2024-06-10", "Consultoría", 10000, iva_rate="0.16", isr_retenido="1000", iva_retenido="1066.67", ) self.assertEqual(self.lg.account_balance("102.01"), dec("9533.33")) # net cash self.assertEqual(self.lg.account_balance("119.02"), dec("1000.00")) # isr retenido self.assertEqual(self.lg.account_balance("119.01"), dec("1066.67")) # iva retenido def test_zero_rated_income_has_no_iva(self): self.lg.record_income("2024-05-20", "Export", 5000, iva_rate="0") self.assertEqual(self.lg.account_balance("102.01"), dec("5000.00")) self.assertEqual(self.lg.account_balance("213.01"), dec("0.00")) def test_classifier_specific_expense_account(self): self.lg.ensure_account("613.01", "Gastos de viaje", "expense", "debit") self.lg.record_expense("2024-05-18", "Vuelo a cliente", 3000, iva_rate="0.16", expense_code="613.01") self.assertEqual(self.lg.account_balance("613.01"), dec("3000.00")) class TestMonthTotals(unittest.TestCase): def setUp(self): self.lg = Ledger(":memory:") self.lg.record_income("2024-05-10", "Diseño", 45000, iva_rate="0.16") self.lg.record_expense("2024-05-15", "Adobe", 12000, iva_rate="0.16") self.lg.record_expense("2024-05-20", "Multa", 500, iva_rate="0", deductible=False) # noise in another month must not leak in self.lg.record_income("2024-06-01", "Otro mes", 99999, iva_rate="0.16") def tearDown(self): self.lg.close() def test_aggregates(self): m = self.lg.month_totals(2024, 5) self.assertEqual(m.income, dec("45000.00")) self.assertEqual(m.deductible_expenses, dec("12000.00")) self.assertEqual(m.nondeductible_expenses, dec("500.00")) self.assertEqual(m.iva_trasladado, dec("7200.00")) self.assertEqual(m.iva_acreditable, dec("1920.00")) def test_month_isolation(self): self.assertEqual(self.lg.month_totals(2024, 6).income, dec("99999.00")) class TestStatements(unittest.TestCase): def setUp(self): self.lg = Ledger(":memory:") self.lg.record_income("2024-05-10", "Diseño", 45000, iva_rate="0.16") self.lg.record_expense("2024-05-15", "Adobe", 12000, iva_rate="0.16") def tearDown(self): self.lg.close() def test_income_statement(self): s = self.lg.income_statement(2024, 5) self.assertEqual(s.revenue, dec("45000.00")) self.assertEqual(s.expenses, dec("12000.00")) self.assertEqual(s.net_profit, dec("33000.00")) def test_balance_sheet_balances(self): bs = self.lg.balance_sheet("2024-05-31") # assets: bank 38,280 + iva acreditable 1,920 = 40,200 # liabilities: iva trasladado 7,200 ; equity = 33,000 self.assertEqual(bs.assets, dec("40200.00")) self.assertEqual(bs.liabilities, dec("7200.00")) self.assertEqual(bs.equity, dec("33000.00")) # the accounting identity must hold self.assertEqual(bs.assets, dec(str(bs.liabilities + bs.equity))) class TestLedgerEngineIntegration(unittest.TestCase): """The whole point: ledger aggregates flow into the deterministic engine.""" def setUp(self): self.lg = Ledger(":memory:") self.lg.record_income("2024-05-10", "Diseño", 45000, iva_rate="0.16") self.lg.record_expense("2024-05-15", "Adobe", 12000, iva_rate="0.16") def tearDown(self): self.lg.close() def test_resico_from_ledger(self): m = self.lg.month_totals(2024, 5) self.assertEqual(resico_isr_monthly(m.income).amount, dec("495.00")) def test_general_isr_from_ledger(self): m = self.lg.month_totals(2024, 5) base = taxable_base(m.income, m.deductible_expenses) self.assertEqual(base, dec("33000")) self.assertEqual(isr_provisional_monthly(base).amount, dec("5418.90")) def test_iva_from_ledger(self): m = self.lg.month_totals(2024, 5) r = iva_monthly(m.iva_trasladado, m.iva_acreditable, m.iva_retenido) self.assertEqual(r.amount, dec("5280.00")) if __name__ == "__main__": unittest.main()