test(fusion_accounting_bank_rec): test data factories for bank-rec testing
Provides make_bank_journal, make_bank_statement, make_bank_line, make_invoice, make_vendor_bill, make_suggestion, make_pattern, make_precedent, make_reconcileable_pair helpers used across the bank-rec test suite. Replaces the original plan's SQL-fixture capture with programmatic factories — same testing intent, simpler maintenance, no real Westin data baked into the repo. Note: the original plan called for 5 SQL fixtures captured from the local DB (westin_simple_match.sql, westin_partial_chain.sql, etc.). Those are replaced by factory-driven test creation in Task 19 — eliminates fragile hand-curated SQL while testing the same code paths. Made-with: Cursor
This commit is contained in:
@@ -7,3 +7,4 @@ from . import test_pattern_extraction
|
|||||||
from . import test_confidence_scoring
|
from . import test_confidence_scoring
|
||||||
from . import test_reconcile_engine_unit
|
from . import test_reconcile_engine_unit
|
||||||
from . import test_reconcile_engine_property
|
from . import test_reconcile_engine_property
|
||||||
|
from . import test_factories
|
||||||
|
|||||||
185
fusion_accounting_bank_rec/tests/_factories.py
Normal file
185
fusion_accounting_bank_rec/tests/_factories.py
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
"""Test data factories for fusion_accounting_bank_rec.
|
||||||
|
|
||||||
|
Provides recordset builders for use across all test files. Sane defaults
|
||||||
|
let tests be readable: `make_bank_line(env, amount=100, partner=p)` instead
|
||||||
|
of 30 lines of recordset setup.
|
||||||
|
|
||||||
|
These factories work against the real Odoo registry — they exercise the
|
||||||
|
same code paths as production. Each factory is idempotent in the sense
|
||||||
|
that calling it multiple times returns separate records.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from datetime import date, timedelta
|
||||||
|
|
||||||
|
from odoo import fields
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Bank journal + statements
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def make_bank_journal(env, *, name='Test Bank', code=None):
|
||||||
|
"""Create a bank journal. `code` defaults to first 5 chars of `name`."""
|
||||||
|
code = code or name[:5].upper().replace(' ', '')
|
||||||
|
return env['account.journal'].create({
|
||||||
|
'name': name,
|
||||||
|
'type': 'bank',
|
||||||
|
'code': code,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def make_bank_statement(env, *, journal=None, name='Test Statement', date_=None):
|
||||||
|
"""Create a bank statement. Auto-creates a bank journal if not provided."""
|
||||||
|
journal = journal or make_bank_journal(env)
|
||||||
|
return env['account.bank.statement'].create({
|
||||||
|
'name': name,
|
||||||
|
'journal_id': journal.id,
|
||||||
|
'date': date_ or date.today(),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def make_bank_line(env, *, journal=None, statement=None, amount=100.00,
|
||||||
|
partner=None, memo='Test line', date_=None):
|
||||||
|
"""Create a bank statement line. Creates statement if not provided.
|
||||||
|
|
||||||
|
Most-common factory in tests. Defaults give a $100 line with no partner."""
|
||||||
|
if not statement:
|
||||||
|
statement = make_bank_statement(env, journal=journal, date_=date_)
|
||||||
|
return env['account.bank.statement.line'].create({
|
||||||
|
'statement_id': statement.id,
|
||||||
|
'journal_id': statement.journal_id.id,
|
||||||
|
'date': date_ or date.today(),
|
||||||
|
'payment_ref': memo,
|
||||||
|
'amount': amount,
|
||||||
|
'partner_id': partner.id if partner else False,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Invoices + journal items
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def _ensure_test_product(env):
|
||||||
|
"""Get or create a service product suitable for invoice lines."""
|
||||||
|
product = env['product.product'].search([('type', '=', 'service')], limit=1)
|
||||||
|
if not product:
|
||||||
|
product = env['product.product'].create({
|
||||||
|
'name': 'Fusion Test Service',
|
||||||
|
'type': 'service',
|
||||||
|
})
|
||||||
|
return product
|
||||||
|
|
||||||
|
|
||||||
|
def make_invoice(env, *, partner, amount=100.00, date_=None, currency=None,
|
||||||
|
product=None, posted=True):
|
||||||
|
"""Create a customer invoice (out_invoice). Posted by default."""
|
||||||
|
product = product or _ensure_test_product(env)
|
||||||
|
vals = {
|
||||||
|
'move_type': 'out_invoice',
|
||||||
|
'partner_id': partner.id,
|
||||||
|
'invoice_date': date_ or date.today(),
|
||||||
|
'invoice_line_ids': [(0, 0, {
|
||||||
|
'product_id': product.id,
|
||||||
|
'name': 'Test invoice line',
|
||||||
|
'quantity': 1,
|
||||||
|
'price_unit': amount,
|
||||||
|
})],
|
||||||
|
}
|
||||||
|
if currency:
|
||||||
|
vals['currency_id'] = currency.id
|
||||||
|
move = env['account.move'].create(vals)
|
||||||
|
if posted:
|
||||||
|
move.action_post()
|
||||||
|
return move
|
||||||
|
|
||||||
|
|
||||||
|
def make_vendor_bill(env, *, partner, amount=100.00, date_=None, currency=None,
|
||||||
|
product=None, posted=True):
|
||||||
|
"""Create a vendor bill (in_invoice). Posted by default."""
|
||||||
|
product = product or _ensure_test_product(env)
|
||||||
|
vals = {
|
||||||
|
'move_type': 'in_invoice',
|
||||||
|
'partner_id': partner.id,
|
||||||
|
'invoice_date': date_ or date.today(),
|
||||||
|
'invoice_line_ids': [(0, 0, {
|
||||||
|
'product_id': product.id,
|
||||||
|
'name': 'Test bill line',
|
||||||
|
'quantity': 1,
|
||||||
|
'price_unit': amount,
|
||||||
|
})],
|
||||||
|
}
|
||||||
|
if currency:
|
||||||
|
vals['currency_id'] = currency.id
|
||||||
|
move = env['account.move'].create(vals)
|
||||||
|
if posted:
|
||||||
|
move.action_post()
|
||||||
|
return move
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Suggestions + patterns + precedents (fusion-specific)
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def make_suggestion(env, *, statement_line, candidate_move_lines=None,
|
||||||
|
confidence=0.92, rank=1, reasoning='Test suggestion',
|
||||||
|
state='pending'):
|
||||||
|
"""Create a fusion.reconcile.suggestion against a bank line."""
|
||||||
|
candidate_ids = candidate_move_lines.ids if candidate_move_lines else []
|
||||||
|
return env['fusion.reconcile.suggestion'].create({
|
||||||
|
'company_id': env.company.id,
|
||||||
|
'statement_line_id': statement_line.id,
|
||||||
|
'proposed_move_line_ids': [(6, 0, candidate_ids)],
|
||||||
|
'confidence': confidence,
|
||||||
|
'rank': rank,
|
||||||
|
'reasoning': reasoning,
|
||||||
|
'state': state,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def make_pattern(env, *, partner, reconcile_count=10, pref_strategy='exact_amount',
|
||||||
|
typical_cadence_days=14.0, common_memo_tokens='RBC,ETF'):
|
||||||
|
"""Create a fusion.reconcile.pattern for a partner."""
|
||||||
|
return env['fusion.reconcile.pattern'].create({
|
||||||
|
'company_id': env.company.id,
|
||||||
|
'partner_id': partner.id,
|
||||||
|
'reconcile_count': reconcile_count,
|
||||||
|
'pref_strategy': pref_strategy,
|
||||||
|
'typical_cadence_days': typical_cadence_days,
|
||||||
|
'common_memo_tokens': common_memo_tokens,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def make_precedent(env, *, partner, amount=1847.50, days_ago=14,
|
||||||
|
memo_tokens='RBC,ETF,REF', count=1, source='manual'):
|
||||||
|
"""Create a fusion.reconcile.precedent."""
|
||||||
|
return env['fusion.reconcile.precedent'].create({
|
||||||
|
'company_id': env.company.id,
|
||||||
|
'partner_id': partner.id,
|
||||||
|
'amount': amount,
|
||||||
|
'currency_id': env.company.currency_id.id,
|
||||||
|
'date': date.today() - timedelta(days=days_ago),
|
||||||
|
'memo_tokens': memo_tokens,
|
||||||
|
'matched_move_line_count': count,
|
||||||
|
'reconciled_at': fields.Datetime.now(),
|
||||||
|
'source': source,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Convenience composite — bank line + matching invoice ready to reconcile
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
def make_reconcileable_pair(env, *, amount=100.00, partner=None, date_=None):
|
||||||
|
"""Create a bank line + a customer invoice with the same partner+amount.
|
||||||
|
Returns (bank_line, invoice_recv_lines) ready to pass to engine.reconcile_one().
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(bank_line, invoice_receivable_lines) tuple
|
||||||
|
"""
|
||||||
|
if not partner:
|
||||||
|
partner = env['res.partner'].create({'name': 'Reconcile Test Partner'})
|
||||||
|
invoice = make_invoice(env, partner=partner, amount=amount, date_=date_)
|
||||||
|
bank_line = make_bank_line(env, amount=amount, partner=partner, date_=date_)
|
||||||
|
recv_lines = invoice.line_ids.filtered(
|
||||||
|
lambda l: l.account_id.account_type == 'asset_receivable')
|
||||||
|
return (bank_line, recv_lines)
|
||||||
74
fusion_accounting_bank_rec/tests/test_factories.py
Normal file
74
fusion_accounting_bank_rec/tests/test_factories.py
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
"""Smoke tests verifying the factories produce usable records.
|
||||||
|
|
||||||
|
Not testing factory correctness exhaustively — just that each helper
|
||||||
|
returns a record of the expected type with the expected basic state."""
|
||||||
|
|
||||||
|
from odoo.tests.common import TransactionCase, tagged
|
||||||
|
|
||||||
|
from . import _factories as f
|
||||||
|
|
||||||
|
|
||||||
|
@tagged('post_install', '-at_install')
|
||||||
|
class TestFactories(TransactionCase):
|
||||||
|
|
||||||
|
def test_make_bank_journal(self):
|
||||||
|
journal = f.make_bank_journal(self.env)
|
||||||
|
self.assertEqual(journal._name, 'account.journal')
|
||||||
|
self.assertEqual(journal.type, 'bank')
|
||||||
|
|
||||||
|
def test_make_bank_statement(self):
|
||||||
|
statement = f.make_bank_statement(self.env)
|
||||||
|
self.assertEqual(statement._name, 'account.bank.statement')
|
||||||
|
self.assertTrue(statement.journal_id)
|
||||||
|
|
||||||
|
def test_make_bank_line(self):
|
||||||
|
line = f.make_bank_line(self.env, amount=250.00, memo='Smoke memo')
|
||||||
|
self.assertEqual(line._name, 'account.bank.statement.line')
|
||||||
|
self.assertEqual(line.amount, 250.00)
|
||||||
|
self.assertEqual(line.payment_ref, 'Smoke memo')
|
||||||
|
self.assertFalse(line.is_reconciled)
|
||||||
|
|
||||||
|
def test_make_bank_line_with_partner(self):
|
||||||
|
partner = self.env['res.partner'].create({'name': 'Factory Partner'})
|
||||||
|
line = f.make_bank_line(self.env, partner=partner, amount=500)
|
||||||
|
self.assertEqual(line.partner_id, partner)
|
||||||
|
|
||||||
|
def test_make_invoice_posted(self):
|
||||||
|
partner = self.env['res.partner'].create({'name': 'Invoice Partner'})
|
||||||
|
invoice = f.make_invoice(self.env, partner=partner, amount=300)
|
||||||
|
self.assertEqual(invoice._name, 'account.move')
|
||||||
|
self.assertEqual(invoice.move_type, 'out_invoice')
|
||||||
|
self.assertEqual(invoice.state, 'posted')
|
||||||
|
self.assertAlmostEqual(invoice.amount_total, 300, places=2)
|
||||||
|
|
||||||
|
def test_make_vendor_bill_posted(self):
|
||||||
|
partner = self.env['res.partner'].create({'name': 'Vendor Partner'})
|
||||||
|
bill = f.make_vendor_bill(self.env, partner=partner, amount=400)
|
||||||
|
self.assertEqual(bill.move_type, 'in_invoice')
|
||||||
|
self.assertEqual(bill.state, 'posted')
|
||||||
|
|
||||||
|
def test_make_suggestion(self):
|
||||||
|
line = f.make_bank_line(self.env, amount=100)
|
||||||
|
sug = f.make_suggestion(self.env, statement_line=line, confidence=0.85)
|
||||||
|
self.assertEqual(sug._name, 'fusion.reconcile.suggestion')
|
||||||
|
self.assertEqual(sug.confidence, 0.85)
|
||||||
|
self.assertEqual(sug.state, 'pending')
|
||||||
|
|
||||||
|
def test_make_pattern(self):
|
||||||
|
partner = self.env['res.partner'].create({'name': 'Pattern Partner'})
|
||||||
|
pattern = f.make_pattern(self.env, partner=partner, reconcile_count=20)
|
||||||
|
self.assertEqual(pattern._name, 'fusion.reconcile.pattern')
|
||||||
|
self.assertEqual(pattern.reconcile_count, 20)
|
||||||
|
|
||||||
|
def test_make_precedent(self):
|
||||||
|
partner = self.env['res.partner'].create({'name': 'Precedent Partner'})
|
||||||
|
precedent = f.make_precedent(self.env, partner=partner, amount=999.99)
|
||||||
|
self.assertEqual(precedent._name, 'fusion.reconcile.precedent')
|
||||||
|
self.assertEqual(precedent.amount, 999.99)
|
||||||
|
self.assertEqual(precedent.source, 'manual')
|
||||||
|
|
||||||
|
def test_make_reconcileable_pair(self):
|
||||||
|
bank_line, recv_lines = f.make_reconcileable_pair(self.env, amount=750)
|
||||||
|
self.assertEqual(bank_line.amount, 750.00)
|
||||||
|
self.assertGreater(len(recv_lines), 0)
|
||||||
|
self.assertAlmostEqual(sum(recv_lines.mapped('amount_residual')), 750, places=2)
|
||||||
Reference in New Issue
Block a user