CREATE MATERIALIZED VIEW fusion_unreconciled_bank_line_mv pre-computes the data the kanban widget needs (top suggestion, confidence band, attachment count, partner reconcile hint) so that listing 50-100 lines is one indexed query instead of N+1. Refresh strategy: - Triggered on fusion.reconcile.suggestion create/write (best-effort, never poisons the originating transaction) - Cron (every 5 min) — added in Task 25 The MV is created in the model's init() (Odoo calls this on install/upgrade). The SQL DDL is idempotent (CREATE MATERIALIZED VIEW IF NOT EXISTS / CREATE INDEX IF NOT EXISTS) and includes a UNIQUE(id) index so REFRESH MATERIALIZED VIEW CONCURRENTLY is supported. _refresh() falls back to a blocking refresh on the first call after creation. Made-with: Cursor
116 lines
4.8 KiB
Python
116 lines
4.8 KiB
Python
"""Smoke tests for the fusion_unreconciled_bank_line_mv materialized view.
|
|
|
|
NOTE on the explicit ``self.env.cr.commit()`` calls below:
|
|
PostgreSQL's ``REFRESH MATERIALIZED VIEW`` only sees data from
|
|
*committed* transactions. ``TransactionCase`` rolls back at the end
|
|
of each test, so without an explicit commit the freshly-inserted
|
|
bank line would not be visible to the refresh and the assertions
|
|
would fail. The trade-off is that the records we create *are*
|
|
persisted; we therefore unlink them in a ``finally`` block to keep
|
|
test isolation.
|
|
|
|
If this proves too brittle later we can convert this case to extend
|
|
``BaseCase`` (no rollback) and clean up explicitly. For Phase 1 the
|
|
commit-+-finally pattern is the simpler choice.
|
|
"""
|
|
|
|
from odoo.tests.common import TransactionCase, tagged
|
|
from . import _factories as f
|
|
|
|
|
|
@tagged('post_install', '-at_install')
|
|
class TestUnreconciledBankLineMV(TransactionCase):
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.partner = self.env['res.partner'].create({
|
|
'name': 'MV Test Partner',
|
|
})
|
|
# Force a refresh so we see freshly-inserted lines from prior tests.
|
|
# First refresh after creation may need to be blocking; the
|
|
# _refresh helper handles fallback automatically.
|
|
self.env['fusion.unreconciled.bank.line.mv']._refresh(
|
|
concurrently=False)
|
|
|
|
def test_mv_exists_and_is_queryable(self):
|
|
# Smoke: the model can be searched without error.
|
|
rows = self.env['fusion.unreconciled.bank.line.mv'].search(
|
|
[], limit=10)
|
|
self.assertIsNotNone(rows)
|
|
|
|
def test_mv_includes_unreconciled_line(self):
|
|
bank_line = f.make_bank_line(
|
|
self.env, amount=999.99, partner=self.partner)
|
|
# MV refresh sees committed data only; commit, refresh, assert,
|
|
# then unlink to keep test isolation.
|
|
self.env.cr.commit()
|
|
try:
|
|
self.env['fusion.unreconciled.bank.line.mv']._refresh(
|
|
concurrently=False)
|
|
mv_row = self.env['fusion.unreconciled.bank.line.mv'].search([
|
|
('id', '=', bank_line.id),
|
|
])
|
|
self.assertTrue(
|
|
mv_row,
|
|
"MV should contain freshly-inserted unreconciled line")
|
|
self.assertAlmostEqual(mv_row.amount, 999.99, places=2)
|
|
# No suggestion yet -> band 'none', confidence 0.
|
|
self.assertEqual(mv_row.confidence_band, 'none')
|
|
self.assertEqual(mv_row.attachment_count, 0)
|
|
finally:
|
|
bank_line.unlink()
|
|
self.env.cr.commit()
|
|
|
|
def test_mv_excludes_reconciled_line(self):
|
|
bank_line, recv_lines = f.make_reconcileable_pair(
|
|
self.env, amount=100.00, partner=self.partner)
|
|
self.env['fusion.reconcile.engine'].reconcile_one(
|
|
bank_line, against_lines=recv_lines)
|
|
self.env.cr.commit()
|
|
try:
|
|
self.env['fusion.unreconciled.bank.line.mv']._refresh(
|
|
concurrently=False)
|
|
mv_row = self.env['fusion.unreconciled.bank.line.mv'].search([
|
|
('id', '=', bank_line.id),
|
|
])
|
|
self.assertFalse(
|
|
mv_row, "Reconciled line should be excluded from MV")
|
|
finally:
|
|
# Best-effort unwind for test isolation. Use the engine's
|
|
# standard undo path since reconcile_one rewrites the bank
|
|
# move's line_ids.
|
|
try:
|
|
bank_line.action_undo_reconciliation()
|
|
except Exception:
|
|
pass
|
|
try:
|
|
bank_line.unlink()
|
|
except Exception:
|
|
pass
|
|
self.env.cr.commit()
|
|
|
|
def test_mv_confidence_band_high_for_high_conf_suggestion(self):
|
|
bank_line = f.make_bank_line(
|
|
self.env, amount=500.00, partner=self.partner)
|
|
f.make_suggestion(
|
|
self.env, statement_line=bank_line, confidence=0.92)
|
|
self.env.cr.commit()
|
|
try:
|
|
self.env['fusion.unreconciled.bank.line.mv']._refresh(
|
|
concurrently=False)
|
|
mv_row = self.env['fusion.unreconciled.bank.line.mv'].search([
|
|
('id', '=', bank_line.id),
|
|
])
|
|
self.assertTrue(mv_row, "MV row should exist for suggestion line")
|
|
# 0.92 falls in the 'high' band per the SQL CASE (>= 0.85).
|
|
self.assertEqual(mv_row.confidence_band, 'high')
|
|
self.assertAlmostEqual(mv_row.top_confidence, 0.92, places=2)
|
|
finally:
|
|
# Unlink suggestion first (cascade would handle it but explicit
|
|
# is safer if the test order reuses partner records).
|
|
self.env['fusion.reconcile.suggestion'].search([
|
|
('statement_line_id', '=', bank_line.id),
|
|
]).unlink()
|
|
bank_line.unlink()
|
|
self.env.cr.commit()
|