"""Migration round-trip: bootstrap step backfills precedents from existing account.partial.reconcile rows. Exercises Task 39's _bank_rec_bootstrap_step end-to-end: 1. Set up a bank-line / invoice reconciliation via the engine. This creates an account.partial.reconcile row. 2. Wipe the auto-recorded fusion.reconcile.precedent rows so the backfill has work to do. 3. Run wizard._bank_rec_bootstrap_step(). 4. Assert at least one precedent was created with source='backfill', the wizard reports successful pattern + MV refresh, and that a second run is a no-op (idempotent). """ from odoo.tests.common import TransactionCase, tagged from . import _factories as f @tagged('post_install', '-at_install') class TestMigrationRoundTrip(TransactionCase): def setUp(self): super().setUp() self.partner = self.env['res.partner'].create({ 'name': 'Migration Round-Trip Partner', }) self.journal = f.make_bank_journal( self.env, name='Migration Bank', code='MIGBK') self.statement = f.make_bank_statement( self.env, journal=self.journal, name='Migration Statement') def _seed_partial_reconciles(self, amounts): """Create one reconciled bank-line/invoice pair per amount, reusing a single bank journal so we don't violate the account_journal_code_company_uniq constraint. Each call here produces one account.partial.reconcile row. Returns the partial recordset. """ Engine = self.env['fusion.reconcile.engine'] partials = self.env['account.partial.reconcile'] for amount in amounts: invoice = f.make_invoice( self.env, partner=self.partner, amount=amount) recv_lines = invoice.line_ids.filtered( lambda l: l.account_id.account_type == 'asset_receivable') bank_line = f.make_bank_line( self.env, statement=self.statement, amount=amount, partner=self.partner) result = Engine.reconcile_one( bank_line, against_lines=recv_lines) partials |= self.env['account.partial.reconcile'].browse( result['partial_ids']) return partials def _wipe_precedents(self): self.env['fusion.reconcile.precedent'].search([ ('partner_id', '=', self.partner.id), ]).unlink() def test_bootstrap_creates_precedents_from_existing_reconciles(self): partials = self._seed_partial_reconciles([125.00, 275.00]) self.assertTrue(partials, "Test setup should produce account.partial.reconcile rows") self._wipe_precedents() before_backfill = self.env['fusion.reconcile.precedent'].search_count([ ('partner_id', '=', self.partner.id), ('source', '=', 'backfill'), ]) self.assertEqual(before_backfill, 0, "Precondition: no backfill precedents should exist before bootstrap") wizard = self.env['fusion.migration.wizard'].create({}) result = wizard._bank_rec_bootstrap_step() self.assertEqual(result['step'], 'bank_rec_bootstrap') self.assertGreaterEqual(result['precedents_created'], 1, "Bootstrap should backfill at least one precedent from the " "partial.reconcile rows produced in setUp") self.assertTrue(result['mv_refreshed'], "Bootstrap should report successful MV refresh") after_backfill = self.env['fusion.reconcile.precedent'].search_count([ ('partner_id', '=', self.partner.id), ('source', '=', 'backfill'), ]) self.assertGreaterEqual(after_backfill, 1, "At least one source='backfill' precedent should exist post-bootstrap") def test_bootstrap_step_idempotent(self): self._seed_partial_reconciles([411.00]) self._wipe_precedents() wizard = self.env['fusion.migration.wizard'].create({}) result1 = wizard._bank_rec_bootstrap_step() created_first_run = result1['precedents_created'] self.assertGreaterEqual(created_first_run, 1) result2 = wizard._bank_rec_bootstrap_step() self.assertEqual(result2['precedents_created'], 0, "Second bootstrap should create zero precedents (idempotent)") self.assertGreaterEqual(result2['precedents_skipped'], created_first_run, "Second bootstrap should skip at least what the first one created") def test_bootstrap_refreshes_mv_without_error(self): """The bootstrap call must not raise even when there's nothing to do.""" wizard = self.env['fusion.migration.wizard'].create({}) try: result = wizard._bank_rec_bootstrap_step() except Exception as e: # noqa: BLE001 self.fail(f"Bootstrap raised: {e}") self.assertIn('mv_refreshed', result) self.assertIn('patterns_refreshed', result)