feat(fusion_accounting_bank_rec): matching strategies (AmountExact, FIFO, MultiInvoice)

Made-with: Cursor
This commit is contained in:
gsinghpal
2026-04-19 10:13:00 -04:00
parent b75f215808
commit 3dc74e3987
4 changed files with 204 additions and 0 deletions

View File

@@ -1,2 +1,3 @@
from . import test_memo_tokenizer
from . import test_exchange_diff
from . import test_matching_strategies

View File

@@ -0,0 +1,111 @@
from odoo.tests.common import TransactionCase, tagged
from odoo.addons.fusion_accounting_bank_rec.services.matching_strategies import (
Candidate, AmountExactStrategy, FIFOStrategy, MultiInvoiceStrategy, MatchResult,
)
@tagged('post_install', '-at_install')
class TestAmountExactStrategy(TransactionCase):
def test_picks_exact_amount(self):
candidates = [
Candidate(id=1, amount=99.99, partner_id=42, age_days=10),
Candidate(id=2, amount=100.00, partner_id=42, age_days=20),
Candidate(id=3, amount=100.50, partner_id=42, age_days=5),
]
result = AmountExactStrategy().match(bank_amount=100.00, candidates=candidates)
self.assertEqual(result.picked_ids, [2])
self.assertEqual(result.confidence, 1.0)
def test_no_match_when_no_exact(self):
candidates = [
Candidate(id=1, amount=99.99, partner_id=42, age_days=10),
Candidate(id=2, amount=100.50, partner_id=42, age_days=20),
]
result = AmountExactStrategy().match(bank_amount=100.00, candidates=candidates)
self.assertEqual(result.picked_ids, [])
def test_picks_oldest_when_multiple_exact(self):
candidates = [
Candidate(id=1, amount=100.00, partner_id=42, age_days=10),
Candidate(id=2, amount=100.00, partner_id=42, age_days=30), # oldest
Candidate(id=3, amount=100.00, partner_id=42, age_days=20),
]
result = AmountExactStrategy().match(bank_amount=100.00, candidates=candidates)
self.assertEqual(result.picked_ids, [2])
def test_handles_empty_candidates(self):
result = AmountExactStrategy().match(bank_amount=100.00, candidates=[])
self.assertEqual(result.picked_ids, [])
@tagged('post_install', '-at_install')
class TestFIFOStrategy(TransactionCase):
def test_picks_oldest_first(self):
candidates = [
Candidate(id=1, amount=50.00, partner_id=42, age_days=10),
Candidate(id=2, amount=50.00, partner_id=42, age_days=30),
Candidate(id=3, amount=50.00, partner_id=42, age_days=20),
]
result = FIFOStrategy().match(bank_amount=100.00, candidates=candidates)
self.assertEqual(result.picked_ids, [2, 3]) # oldest two summing to 100
def test_handles_partial_payment(self):
candidates = [
Candidate(id=1, amount=200.00, partner_id=42, age_days=30),
]
result = FIFOStrategy().match(bank_amount=100.00, candidates=candidates)
self.assertEqual(result.picked_ids, [1]) # partial reconcile signaled by residual
self.assertEqual(result.residual, -100.00) # over-allocated; engine handles
def test_handles_empty_candidates(self):
result = FIFOStrategy().match(bank_amount=100.00, candidates=[])
self.assertEqual(result.picked_ids, [])
@tagged('post_install', '-at_install')
class TestMultiInvoiceStrategy(TransactionCase):
def test_finds_smallest_set_summing_to_amount(self):
candidates = [
Candidate(id=1, amount=30.00, partner_id=42, age_days=10),
Candidate(id=2, amount=40.00, partner_id=42, age_days=15),
Candidate(id=3, amount=30.00, partner_id=42, age_days=20),
Candidate(id=4, amount=70.00, partner_id=42, age_days=25),
]
result = MultiInvoiceStrategy(max_combinations=3).match(
bank_amount=100.00, candidates=candidates)
# Two-element solutions exist (e.g., {3,4}=100). Strategy should pick a 2-set.
self.assertEqual(len(result.picked_ids), 2)
# The picked set should sum to 100
picked_amounts = [c.amount for c in candidates if c.id in result.picked_ids]
self.assertAlmostEqual(sum(picked_amounts), 100.00, places=2)
def test_returns_empty_when_no_combination_sums(self):
candidates = [
Candidate(id=1, amount=15.00, partner_id=42, age_days=10),
Candidate(id=2, amount=25.00, partner_id=42, age_days=15),
]
result = MultiInvoiceStrategy(max_combinations=3).match(
bank_amount=100.00, candidates=candidates)
self.assertEqual(result.picked_ids, [])
def test_respects_max_combinations(self):
# Many small invoices — only combinations of ≤3 items considered
candidates = [Candidate(id=i, amount=10.00, partner_id=42, age_days=i)
for i in range(1, 11)]
result = MultiInvoiceStrategy(max_combinations=3).match(
bank_amount=100.00, candidates=candidates)
# Can't make 100 with ≤3 items of $10 each
self.assertEqual(result.picked_ids, [])
def test_strategy_name_includes_combination_size(self):
candidates = [
Candidate(id=1, amount=50.00, partner_id=42, age_days=10),
Candidate(id=2, amount=50.00, partner_id=42, age_days=20),
]
result = MultiInvoiceStrategy(max_combinations=3).match(
bank_amount=100.00, candidates=candidates)
self.assertEqual(set(result.picked_ids), {1, 2})
self.assertIn('multi_invoice', result.strategy_name)