""" Tests for Template_Engine (account_move_templates). Validates: Requirements 3.1, 3.2, 3.3, 3.4, 3.5 """ from odoo.exceptions import ValidationError from odoo.tests.common import TransactionCase # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def _get_account(env, account_type='asset_current'): """Return an existing account of the given type, or create one.""" account = env['account.account'].search( [('account_type', '=', account_type)], limit=1 ) if not account: account = env['account.account'].create({ 'name': f'Test Account ({account_type})', 'code': f'TST{account_type[:4].upper()}', 'account_type': account_type, }) return account def _make_template(env, name='Test Template', lines=None): """Create an AccountMoveTemplate with the given lines. lines: list of dicts with keys: account_id, move_type, percent, line_type """ vals = {'name': name} if lines is not None: vals['line_ids'] = [(0, 0, line) for line in lines] return env['account.move.template'].create(vals) # --------------------------------------------------------------------------- # TestMoveTemplateValidation # --------------------------------------------------------------------------- class TestMoveTemplateValidation(TransactionCase): """Validates: Requirements 3.1, 3.2, 3.3, 3.4""" def setUp(self): super().setUp() self.account_debit = _get_account(self.env, 'asset_current') self.account_credit = _get_account(self.env, 'liability_current') def _line(self, move_type, percent, account=None): if account is None: account = self.account_debit if move_type == 'debit' else self.account_credit return { 'account_id': account.id, 'move_type': move_type, 'percent': percent, 'line_type': 'product', } def test_no_lines_raises_validation_error(self): """Req 3.1 — template without lines raises ValidationError.""" with self.assertRaises(ValidationError): _make_template(self.env, name='Empty Template', lines=[]) def test_imbalanced_percentages_raises_validation_error(self): """Req 3.2 — debit sum != credit sum raises ValidationError.""" with self.assertRaises(ValidationError): _make_template(self.env, name='Imbalanced', lines=[ self._line('debit', 60.0), self._line('credit', 40.0), ]) def test_balanced_percentages_saves_without_error(self): """Req 3.3 — debit sum == credit sum saves without error.""" template = _make_template(self.env, name='Balanced', lines=[ self._line('debit', 100.0), self._line('credit', 100.0), ]) self.assertTrue(template.id, "Template should be saved with a valid ID") def test_percent_below_minimum_raises_validation_error(self): """Req 3.4 — percent < 0.01 raises ValidationError.""" with self.assertRaises(ValidationError): _make_template(self.env, name='Low Percent', lines=[ self._line('debit', 0.001), self._line('credit', 0.001), ]) def test_percent_above_maximum_raises_validation_error(self): """Req 3.4 — percent > 100.0 raises ValidationError.""" with self.assertRaises(ValidationError): _make_template(self.env, name='High Percent', lines=[ self._line('debit', 101.0), self._line('credit', 101.0), ]) # --------------------------------------------------------------------------- # TestMoveTemplateWizard # --------------------------------------------------------------------------- class TestMoveTemplateWizard(TransactionCase): """Validates: Requirement 3.5""" def setUp(self): super().setUp() self.account_debit = _get_account(self.env, 'asset_current') self.account_credit = _get_account(self.env, 'liability_current') # Create a balanced template: 100% debit / 100% credit self.template = self.env['account.move.template'].create({ 'name': 'Wizard Test Template', 'line_ids': [ (0, 0, { 'account_id': self.account_debit.id, 'move_type': 'debit', 'percent': 100.0, 'line_type': 'product', }), (0, 0, { 'account_id': self.account_credit.id, 'move_type': 'credit', 'percent': 100.0, 'line_type': 'product', }), ], }) # Create a source document that uses the mixin (account.move itself # does not use the mixin, so we create a minimal move to act as source) self.source_move = self.env['account.move'].create({ 'move_type': 'entry', 'state': 'draft', 'line_ids': [ (0, 0, { 'account_id': self.account_debit.id, 'debit': 0.0, 'credit': 0.0, 'name': 'Source line', }), ], }) def test_apply_template_creates_draft_move_with_correct_distribution(self): """Req 3.5 — applying template via wizard creates a draft account.move with correct account distribution.""" base_amount = 1000.0 wizard = self.env['account.move.template.wizard'].create({ 'res_model': 'account.move', 'res_id': self.source_move.id, 'template_id': self.template.id, 'amount': base_amount, }) # Trigger onchange to populate draft lines wizard._onchange_compute_draft_lines() # Verify draft lines were computed self.assertEqual(len(wizard.draft_line_ids), 2, "Wizard should have 2 draft lines (one debit, one credit)") debit_line = wizard.draft_line_ids.filtered(lambda l: l.move_type == 'debit') credit_line = wizard.draft_line_ids.filtered(lambda l: l.move_type == 'credit') self.assertAlmostEqual(debit_line.amount, base_amount, msg="Debit line amount should equal base_amount * 100%") self.assertAlmostEqual(credit_line.amount, base_amount, msg="Credit line amount should equal base_amount * 100%") # Post the wizard — creates the account.move wizard.action_post() # Reload source move and check linked moves self.source_move.invalidate_recordset() linked_moves = self.source_move.move_ids self.assertTrue(linked_moves, "A new account.move should be linked to the source") new_move = linked_moves[-1] self.assertEqual(new_move.state, 'draft', "Created move should be in draft state") move_lines = new_move.line_ids debit_move_line = move_lines.filtered(lambda l: l.debit > 0) credit_move_line = move_lines.filtered(lambda l: l.credit > 0) self.assertTrue(debit_move_line, "Move should have a debit line") self.assertTrue(credit_move_line, "Move should have a credit line") self.assertAlmostEqual(debit_move_line[0].debit, base_amount, msg="Debit amount on move line should match base_amount") self.assertAlmostEqual(credit_move_line[0].credit, base_amount, msg="Credit amount on move line should match base_amount") self.assertEqual(debit_move_line[0].account_id, self.account_debit, "Debit line should use the debit account from template") self.assertEqual(credit_move_line[0].account_id, self.account_credit, "Credit line should use the credit account from template")