feat(portal): account_summary controller + 3 unit tests
New /my/account_summary route. Splits posted account.move into Invoices (out_invoice) / Credit Memos (out_refund) / Statements (V1 placeholder). Open Balance helper sums amount_residual across open invoices for the partner's commercial tree. Search filters name OR ref (customer PO). Sort options: date desc/asc, amount desc/asc. Filter pills: open / closed / all. Tests cover the tab partitioning, the open-balance sum, and the search behaviour. Helpers use commercial_partner.env so they work in both HTTP context and unit tests without requiring request.env. Test scaffolding uses fp_from_so_invoice=True context flag and invoice_payment_term_id to satisfy the fusion_plating_jobs and fusion_plating_invoicing create/post gates. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -147,3 +147,124 @@ class TestPortalDashboard(TransactionCase):
|
||||
# Work Order is placeholder without a backend fp.job link
|
||||
wo = next(g for g in groups if g['key'] == 'work_order')
|
||||
self.assertTrue(all(d.get('pending') for d in wo['docs']))
|
||||
|
||||
def test_account_summary_partitions_invoices_and_credits(self):
|
||||
"""Account Summary helper splits posted moves by move_type."""
|
||||
from odoo.addons.fusion_plating_portal.controllers.portal import FpCustomerPortal
|
||||
# fp_from_so_invoice=True bypasses the fusion_plating_jobs enforcement
|
||||
# that normally requires invoices to originate from a Sale Order.
|
||||
# payment_term is required by fusion_plating_invoicing's action_post gate.
|
||||
# Both are test-data scaffolding only; they do not affect what is tested.
|
||||
pt = self.env.ref('account.account_payment_term_immediate')
|
||||
Move = self.env['account.move'].with_context(fp_from_so_invoice=True)
|
||||
inv = Move.create({
|
||||
'partner_id': self.partner.id,
|
||||
'move_type': 'out_invoice',
|
||||
'invoice_date': '2026-05-01',
|
||||
'invoice_payment_term_id': pt.id,
|
||||
'invoice_line_ids': [(0, 0, {
|
||||
'name': 'Test plating',
|
||||
'quantity': 1,
|
||||
'price_unit': 250.00,
|
||||
})],
|
||||
})
|
||||
inv.action_post()
|
||||
cm = Move.create({
|
||||
'partner_id': self.partner.id,
|
||||
'move_type': 'out_refund',
|
||||
'invoice_date': '2026-05-02',
|
||||
'invoice_payment_term_id': pt.id,
|
||||
'invoice_line_ids': [(0, 0, {
|
||||
'name': 'Test credit',
|
||||
'quantity': 1,
|
||||
'price_unit': 50.00,
|
||||
})],
|
||||
})
|
||||
cm.action_post()
|
||||
|
||||
controller = FpCustomerPortal()
|
||||
data = controller._fp_account_summary_data(
|
||||
self.partner.commercial_partner_id,
|
||||
tab='invoices',
|
||||
filter_state='all',
|
||||
search='',
|
||||
sort='date_desc',
|
||||
page=1,
|
||||
)
|
||||
# Tab=invoices -> only out_invoice
|
||||
names = data['records'].mapped('name')
|
||||
self.assertIn(inv.name, names)
|
||||
self.assertNotIn(cm.name, names)
|
||||
|
||||
data = controller._fp_account_summary_data(
|
||||
self.partner.commercial_partner_id,
|
||||
tab='credit_memos',
|
||||
filter_state='all',
|
||||
search='',
|
||||
sort='date_desc',
|
||||
page=1,
|
||||
)
|
||||
names = data['records'].mapped('name')
|
||||
self.assertIn(cm.name, names)
|
||||
self.assertNotIn(inv.name, names)
|
||||
|
||||
def test_account_summary_open_balance_sums_residuals(self):
|
||||
"""Open Balance pill = sum of amount_residual across open invoices."""
|
||||
from odoo.addons.fusion_plating_portal.controllers.portal import FpCustomerPortal
|
||||
pt = self.env.ref('account.account_payment_term_immediate')
|
||||
Move = self.env['account.move'].with_context(fp_from_so_invoice=True)
|
||||
inv = Move.create({
|
||||
'partner_id': self.partner.id,
|
||||
'move_type': 'out_invoice',
|
||||
'invoice_date': '2026-05-01',
|
||||
'invoice_payment_term_id': pt.id,
|
||||
'invoice_line_ids': [(0, 0, {
|
||||
'name': 'Open inv',
|
||||
'quantity': 1,
|
||||
'price_unit': 750.00,
|
||||
})],
|
||||
})
|
||||
inv.action_post()
|
||||
|
||||
controller = FpCustomerPortal()
|
||||
open_balance = controller._fp_account_summary_open_balance(
|
||||
self.partner.commercial_partner_id,
|
||||
)
|
||||
# The 750 invoice has amount_residual = 750 until paid
|
||||
self.assertEqual(open_balance, 750.00)
|
||||
|
||||
def test_account_summary_search_matches_name_and_ref(self):
|
||||
"""Search box filters by invoice number OR customer PO (ref)."""
|
||||
from odoo.addons.fusion_plating_portal.controllers.portal import FpCustomerPortal
|
||||
pt = self.env.ref('account.account_payment_term_immediate')
|
||||
Move = self.env['account.move'].with_context(fp_from_so_invoice=True)
|
||||
inv = Move.create({
|
||||
'partner_id': self.partner.id,
|
||||
'move_type': 'out_invoice',
|
||||
'invoice_date': '2026-05-01',
|
||||
'invoice_payment_term_id': pt.id,
|
||||
'ref': 'PO-CUSTOMER-99999',
|
||||
'invoice_line_ids': [(0, 0, {
|
||||
'name': 'Sale',
|
||||
'quantity': 1,
|
||||
'price_unit': 100.0,
|
||||
})],
|
||||
})
|
||||
inv.action_post()
|
||||
controller = FpCustomerPortal()
|
||||
|
||||
# Search by ref (customer PO)
|
||||
data = controller._fp_account_summary_data(
|
||||
self.partner.commercial_partner_id,
|
||||
tab='invoices', filter_state='all',
|
||||
search='99999', sort='date_desc', page=1,
|
||||
)
|
||||
self.assertIn(inv, data['records'])
|
||||
|
||||
# Search that matches nothing
|
||||
data = controller._fp_account_summary_data(
|
||||
self.partner.commercial_partner_id,
|
||||
tab='invoices', filter_state='all',
|
||||
search='zzznotfoundzzz', sort='date_desc', page=1,
|
||||
)
|
||||
self.assertNotIn(inv, data['records'])
|
||||
|
||||
Reference in New Issue
Block a user