# -*- coding: utf-8 -*- # Copyright 2026 Nexa Systems Inc. # License OPL-1 from odoo.exceptions import UserError from odoo.tests.common import TransactionCase, tagged def _inv_fixture(): return [{ 'id': 'inv-1', 'stripe_invoice_id': 'in_test1', 'invoice_number': 'NEX-0001', 'user_external_id': 'u-1', 'partner_name': 'Acme', 'partner_email': 'ar@acme.test', 'invoice_date': '2026-05-01', 'currency': 'CAD', 'status': 'open', 'subtotal': 100.0, 'tax': 13.0, 'amount_paid': 0.0, 'paid_at': None, 'items': [{'description': 'Odoo ERP Hosting (2026-05-01 to 2026-06-01)', 'quantity': 1.0, 'unit_price': 100.0, 'amount': 100.0}], }] @tagged('post_install', '-at_install') class TestLedgerFamily(TransactionCase): def setUp(self): super().setUp() self.W = self.env['fusion.billing.invoice.ledger.wizard'].sudo() def test_family_classification(self): f = self.W._fc_family_for self.assertEqual(f('Odoo ERP Hosting (2026-05-01 to 2026-06-01)'), 'hosting') self.assertEqual(f('WordPress Website Hosting - Managed (at $50.00 / month)'), 'hosting') self.assertEqual(f('Managed Odoo - Standard (at $49.99 / month)'), 'managed') self.assertEqual(f('Daily Backup Protection'), 'addons') self.assertEqual(f('Remaining time on Daily Backup Protection after 27 May 2026'), 'addons') self.assertEqual(f('Something Unmapped'), 'other') def test_income_account_per_family_distinct(self): a_host = self.W._fc_income_account('hosting') a_add = self.W._fc_income_account('addons') self.assertEqual(a_host.account_type, 'income') self.assertNotEqual(a_host, a_add) self.assertEqual(self.W._fc_income_account('hosting'), a_host) # idempotent @tagged('post_install', '-at_install') class TestLedgerTax(TransactionCase): def setUp(self): super().setUp() self.W = self.env['fusion.billing.invoice.ledger.wizard'].sudo() def test_tax_for_13pct_is_a_13_percent_sale_tax(self): tax = self.W._fc_tax_for(100.0, 13.0) self.assertTrue(tax, "expected an HST/13% sale tax on the Canadian COA") self.assertEqual(tax.type_tax_use, 'sale') res = tax.compute_all(100.0) self.assertAlmostEqual(res['total_included'] - res['total_excluded'], 13.0, places=2) def test_tax_for_zero_is_zero_or_empty(self): tax = self.W._fc_tax_for(100.0, 0.0) if tax: res = tax.compute_all(100.0) self.assertAlmostEqual(res['total_included'] - res['total_excluded'], 0.0, places=2) @tagged('post_install', '-at_install') class TestLedgerIngest(TransactionCase): def setUp(self): super().setUp() self.W = self.env['fusion.billing.invoice.ledger.wizard'].sudo() self.Move = self.env['account.move'] def test_ingest_creates_draft_invoice_with_right_totals(self): self.W._ingest_invoices(_inv_fixture(), post=False) mv = self.Move.search([('x_fc_nexacloud_invoice_id', '=', 'inv-1')]) self.assertEqual(len(mv), 1) self.assertEqual(mv.move_type, 'out_invoice') self.assertEqual(mv.state, 'draft') self.assertAlmostEqual(mv.amount_untaxed, 100.0, places=2) self.assertAlmostEqual(mv.amount_tax, 13.0, places=2) # equals source tax self.assertAlmostEqual(mv.amount_total, 113.0, places=2) self.assertEqual(mv.partner_id.email, 'ar@acme.test') self.assertEqual(mv.invoice_line_ids.account_id, self.W._fc_income_account('hosting')) def test_ingest_is_idempotent(self): self.W._ingest_invoices(_inv_fixture(), post=False) self.W._ingest_invoices(_inv_fixture(), post=False) self.assertEqual(self.Move.search_count( [('x_fc_nexacloud_invoice_id', '=', 'inv-1')]), 1) def test_paid_invoice_is_reconciled_and_shows_paid(self): data = _inv_fixture() data[0].update({'status': 'paid', 'amount_paid': 113.0, 'paid_at': '2026-05-02'}) self.W._ingest_invoices(data, post=True) mv = self.Move.search([('x_fc_nexacloud_invoice_id', '=', 'inv-1')]) self.assertEqual(mv.state, 'posted') self.assertIn(mv.payment_state, ('paid', 'in_payment')) def test_post_ingested_posts_drafts(self): self.W._ingest_invoices(_inv_fixture(), post=False) n = self.W._post_ingested() mv = self.Move.search([('x_fc_nexacloud_invoice_id', '=', 'inv-1')]) self.assertEqual(mv.state, 'posted') self.assertGreaterEqual(n, 1) def test_read_invoices_guards_missing_dsn(self): self.env['ir.config_parameter'].sudo().set_param('fusion_billing.nexacloud_dsn', '') with self.assertRaises(UserError): self.W._read_nexacloud_invoices() def test_prune_shadow_removes_shadow_subs_only(self): p = self.env['res.partner'].sudo().create({'name': 'X'}) shadow = self.env['sale.order'].sudo().create({'partner_id': p.id, 'x_fc_shadow': True}) counts = self.W._fc_prune_metered_shadow() self.assertFalse(shadow.exists()) self.assertGreaterEqual(counts.get('subscriptions', 0), 1)