Files
Odoo-Modules/fusion_claims/tests/test_dashboard.py
gsinghpal ace82de88c feat(fusion_claims): add dashboard create-SO hotlinks
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 03:50:58 -04:00

368 lines
19 KiB
Python

# -*- coding: utf-8 -*-
from odoo.tests.common import TransactionCase, tagged
@tagged('-at_install', 'post_install', 'fusion_claims')
class TestFusionClaimsDashboard(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.Dashboard = cls.env['fusion.claims.dashboard']
cls.User = cls.env['res.users']
cls.Partner = cls.env['res.partner']
# Manager user (sees everything)
cls.manager = cls.User.create({
'name': 'Test Dashboard Manager',
'login': 'test_dash_mgr',
'group_ids': [
(4, cls.env.ref('fusion_claims.group_fusion_claims_manager').id),
(4, cls.env.ref('sales_team.group_sale_salesman').id),
],
})
# Sales rep (sees only own cases)
cls.salesrep = cls.User.create({
'name': 'Test Dashboard Salesrep',
'login': 'test_dash_rep',
'group_ids': [
(4, cls.env.ref('fusion_claims.group_fusion_claims_user').id),
(4, cls.env.ref('sales_team.group_sale_salesman').id),
],
})
cls.partner = cls.Partner.create({'name': 'Test Client'})
@classmethod
def _make_invoice(cls, user, billing_status, amount=1000.0,
exported=False, export_date=None,
invoice_type='adp', payment_state='not_paid'):
"""Helper: create a posted ADP invoice linked to an SO owned by `user`."""
so = cls.env['sale.order'].with_context(skip_status_validation=True).create({
'partner_id': cls.partner.id,
'user_id': user.id,
'x_fc_sale_type': 'adp',
'x_fc_adp_application_status': 'approved',
})
invoice = cls.env['account.move'].with_context(skip_sync=True).create({
'move_type': 'out_invoice',
'partner_id': cls.partner.id,
'x_fc_source_sale_order_id': so.id,
'x_fc_invoice_type': invoice_type,
'x_fc_adp_billing_status': billing_status,
'adp_exported': exported,
'adp_export_date': export_date,
'invoice_line_ids': [(0, 0, {
'name': 'Test line',
'quantity': 1.0,
'price_unit': amount,
'tax_ids': [(5, 0)], # clear taxes so amount_total == price_unit
})],
})
invoice.action_post()
invoice.with_context(skip_sync=True).write({'payment_state': payment_state})
return invoice
def test_dashboard_record_creates(self):
dashboard = self.Dashboard.create({})
self.assertTrue(dashboard.id, "Dashboard record should be creatable")
self.assertEqual(dashboard.name, 'Dashboard')
def test_role_filter_empty_for_manager(self):
dashboard = self.Dashboard.with_user(self.manager).create({})
self.assertEqual(dashboard._role_filter_domain(), [],
"Manager should see all cases (empty domain)")
def test_role_filter_restricts_for_salesrep(self):
dashboard = self.Dashboard.with_user(self.salesrep).create({})
domain = dashboard._role_filter_domain()
self.assertEqual(domain, [('user_id', '=', self.salesrep.id)],
"Sales rep should see only their own SOs")
def test_is_manager_true_for_manager(self):
dashboard = self.Dashboard.with_user(self.manager).create({})
self.assertTrue(dashboard.is_manager)
def test_is_manager_false_for_salesrep(self):
dashboard = self.Dashboard.with_user(self.salesrep).create({})
self.assertFalse(dashboard.is_manager)
# -------------------------------------------------------------------------
# Task 2 — Banner
# -------------------------------------------------------------------------
def test_banner_posting_period_label_format(self):
dashboard = self.Dashboard.with_user(self.manager).create({})
label = dashboard.posting_period_label
self.assertTrue(any(month in label
for month in ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']),
"Label should contain a month abbreviation")
def test_banner_posting_period_start_and_end_are_dates(self):
dashboard = self.Dashboard.with_user(self.manager).create({})
self.assertTrue(dashboard.posting_period_start)
self.assertTrue(dashboard.posting_period_end)
delta = (dashboard.posting_period_end - dashboard.posting_period_start).days
self.assertEqual(delta, 14)
def test_banner_submission_deadline_is_wednesday_6pm(self):
dashboard = self.Dashboard.with_user(self.manager).create({})
deadline = dashboard.submission_deadline_dt
self.assertTrue(deadline, "Deadline should be set")
# Stored in UTC; convert to user's TZ to assert the wall-clock weekday/hour
import pytz
tz = pytz.timezone(self.manager.tz or 'America/Toronto')
local = pytz.UTC.localize(deadline).astimezone(tz)
self.assertEqual(local.weekday(), 2, "Deadline should be Wednesday")
self.assertEqual(local.hour, 18, "Deadline should be 18:00 (6 PM)")
def test_is_pre_first_posting_false_when_today_is_past_base_date(self):
# Test runs after 2026-01-23 by default.
dashboard = self.Dashboard.with_user(self.manager).create({})
self.assertFalse(dashboard.is_pre_first_posting)
# -------------------------------------------------------------------------
# Task 3 — KPI tiles
# -------------------------------------------------------------------------
def test_kpi_ready_counts_waiting_invoices_not_exported(self):
self._make_invoice(self.manager, 'waiting', amount=500.0, exported=False)
dashboard = self.Dashboard.with_user(self.manager).create({})
self.assertEqual(dashboard.kpi_ready_count, 1)
self.assertAlmostEqual(dashboard.kpi_ready_amount, 500.0, places=2)
def test_kpi_ready_excludes_already_exported(self):
from datetime import date
self._make_invoice(self.manager, 'waiting', amount=500.0,
exported=True, export_date=date.today())
dashboard = self.Dashboard.with_user(self.manager).create({})
self.assertEqual(dashboard.kpi_ready_count, 0)
self.assertAlmostEqual(dashboard.kpi_ready_amount, 0.0, places=2)
def test_kpi_claimed_counts_exported_in_current_period(self):
dashboard = self.Dashboard.with_user(self.manager).create({})
in_period_date = dashboard.posting_period_start
self._make_invoice(self.manager, 'submitted', amount=700.0,
exported=True, export_date=in_period_date)
dashboard2 = self.Dashboard.with_user(self.manager).create({})
self.assertEqual(dashboard2.kpi_claimed_count, 1)
self.assertAlmostEqual(dashboard2.kpi_claimed_amount, 700.0, places=2)
def test_kpi_ar_counts_posted_unpaid_adp_invoices(self):
self._make_invoice(self.manager, 'submitted', amount=2000.0,
exported=True, payment_state='not_paid')
dashboard = self.Dashboard.with_user(self.manager).create({})
self.assertEqual(dashboard.kpi_ar_count, 1)
self.assertAlmostEqual(dashboard.kpi_ar_amount, 2000.0, places=2)
def test_kpi_ready_respects_role_filter(self):
self._make_invoice(self.manager, 'waiting', amount=500.0)
dashboard_rep = self.Dashboard.with_user(self.salesrep).create({})
self.assertEqual(dashboard_rep.kpi_ready_count, 0,
"Salesrep must not see manager's invoice")
# -------------------------------------------------------------------------
# Task 4 — Activities + bottlenecks
# -------------------------------------------------------------------------
def test_my_activities_count_zero_when_none(self):
dashboard = self.Dashboard.with_user(self.manager).create({})
self.assertEqual(dashboard.my_activities_count, 0)
def test_my_activities_count_picks_up_user_activity(self):
so = self.env['sale.order'].with_context(skip_status_validation=True).create({
'partner_id': self.partner.id,
'user_id': self.manager.id,
'x_fc_sale_type': 'adp',
})
self.env['mail.activity'].create({
'res_model_id': self.env['ir.model']._get('sale.order').id,
'res_id': so.id,
'res_model': 'sale.order',
'user_id': self.manager.id,
'activity_type_id': self.env.ref('mail.mail_activity_data_todo').id,
'summary': 'Test activity',
})
dashboard = self.Dashboard.with_user(self.manager).create({})
self.assertEqual(dashboard.my_activities_count, 1)
self.assertIn('Test activity', dashboard.my_activities_html or '')
def test_bottleneck_no_pod_count(self):
self.env['sale.order'].with_context(skip_status_validation=True).create({
'partner_id': self.partner.id,
'user_id': self.manager.id,
'x_fc_sale_type': 'adp',
'x_fc_adp_application_status': 'approved',
})
dashboard = self.Dashboard.with_user(self.manager).create({})
self.assertEqual(dashboard.bottleneck_no_pod_count, 1)
def test_bottleneck_no_response_count(self):
from datetime import date, timedelta
old_date = date.today() - timedelta(days=20)
self.env['sale.order'].with_context(skip_status_validation=True).create({
'partner_id': self.partner.id,
'user_id': self.manager.id,
'x_fc_sale_type': 'adp',
'x_fc_adp_application_status': 'submitted',
'x_fc_claim_submission_date': old_date,
})
dashboard = self.Dashboard.with_user(self.manager).create({})
self.assertEqual(dashboard.bottleneck_no_response_count, 1)
# -------------------------------------------------------------------------
# Task 5 — Other funder counts
# -------------------------------------------------------------------------
def test_other_funder_counts_segregate_by_sale_type(self):
SO = self.env['sale.order'].with_context(skip_status_validation=True)
SO.create({'partner_id': self.partner.id, 'user_id': self.manager.id,
'x_fc_sale_type': 'odsp'})
SO.create({'partner_id': self.partner.id, 'user_id': self.manager.id,
'x_fc_sale_type': 'wsib'})
SO.create({'partner_id': self.partner.id, 'user_id': self.manager.id,
'x_fc_sale_type': 'insurance'})
SO.create({'partner_id': self.partner.id, 'user_id': self.manager.id,
'x_fc_sale_type': 'muscular_dystrophy'})
SO.create({'partner_id': self.partner.id, 'user_id': self.manager.id,
'x_fc_sale_type': 'hardship'})
SO.create({'partner_id': self.partner.id, 'user_id': self.manager.id,
'x_fc_sale_type': 'adp', 'x_fc_client_type': 'ACS'})
dashboard = self.Dashboard.with_user(self.manager).create({})
self.assertEqual(dashboard.count_odsp, 1)
self.assertEqual(dashboard.count_wsib, 1)
self.assertEqual(dashboard.count_insurance, 1)
self.assertEqual(dashboard.count_mdc, 1)
self.assertEqual(dashboard.count_hardship, 1)
self.assertEqual(dashboard.count_acsd, 1)
def test_other_funder_counts_exclude_cancelled(self):
so = self.env['sale.order'].with_context(skip_status_validation=True).create({
'partner_id': self.partner.id, 'user_id': self.manager.id,
'x_fc_sale_type': 'wsib',
})
so.with_context(skip_status_validation=True).write({'state': 'cancel'})
dashboard = self.Dashboard.with_user(self.manager).create({})
self.assertEqual(dashboard.count_wsib, 0)
# -------------------------------------------------------------------------
# Task 6 — ADP + MOD workflow counts
# -------------------------------------------------------------------------
def test_adp_pre_approval_tile_counts(self):
SO = self.env['sale.order'].with_context(skip_status_validation=True)
SO.create({'partner_id': self.partner.id, 'user_id': self.manager.id,
'x_fc_sale_type': 'adp',
'x_fc_adp_application_status': 'waiting_for_application'})
SO.create({'partner_id': self.partner.id, 'user_id': self.manager.id,
'x_fc_sale_type': 'adp',
'x_fc_adp_application_status': 'application_received'})
SO.create({'partner_id': self.partner.id, 'user_id': self.manager.id,
'x_fc_sale_type': 'adp',
'x_fc_adp_application_status': 'ready_submission'})
SO.create({'partner_id': self.partner.id, 'user_id': self.manager.id,
'x_fc_sale_type': 'adp',
'x_fc_adp_application_status': 'needs_correction'})
dashboard = self.Dashboard.with_user(self.manager).create({})
self.assertEqual(dashboard.adp_waiting_app_count, 1)
self.assertEqual(dashboard.adp_app_received_count, 1)
self.assertEqual(dashboard.adp_ready_submit_count, 1)
self.assertEqual(dashboard.adp_needs_correction_count, 1)
def test_adp_post_approval_tile_counts(self):
SO = self.env['sale.order'].with_context(skip_status_validation=True)
SO.create({'partner_id': self.partner.id, 'user_id': self.manager.id,
'x_fc_sale_type': 'adp',
'x_fc_adp_application_status': 'approved'})
SO.create({'partner_id': self.partner.id, 'user_id': self.manager.id,
'x_fc_sale_type': 'adp',
'x_fc_adp_application_status': 'ready_delivery'})
SO.create({'partner_id': self.partner.id, 'user_id': self.manager.id,
'x_fc_sale_type': 'adp',
'x_fc_adp_application_status': 'ready_bill'})
SO.create({'partner_id': self.partner.id, 'user_id': self.manager.id,
'x_fc_sale_type': 'adp',
'x_fc_adp_application_status': 'on_hold'})
dashboard = self.Dashboard.with_user(self.manager).create({})
self.assertEqual(dashboard.adp_approved_count, 1)
self.assertEqual(dashboard.adp_ready_delivery_count, 1)
self.assertEqual(dashboard.adp_ready_bill_count, 1)
self.assertEqual(dashboard.adp_on_hold_count, 1)
def test_mod_tile_counts(self):
SO = self.env['sale.order'].with_context(skip_status_validation=True)
for status in ('awaiting_funding', 'funding_approved', 'contract_received',
'project_complete', 'pod_submitted'):
SO.create({'partner_id': self.partner.id, 'user_id': self.manager.id,
'x_fc_sale_type': 'march_of_dimes',
'x_fc_mod_status': status})
dashboard = self.Dashboard.with_user(self.manager).create({})
self.assertEqual(dashboard.mod_awaiting_funding_count, 1)
self.assertEqual(dashboard.mod_funding_approved_count, 1)
self.assertEqual(dashboard.mod_pca_received_count, 1)
self.assertEqual(dashboard.mod_project_complete_count, 1)
self.assertEqual(dashboard.mod_pod_submitted_count, 1)
# -------------------------------------------------------------------------
# Task 7 — Open-list action methods
# -------------------------------------------------------------------------
def test_action_open_adp_waiting_app_returns_correct_domain(self):
dashboard = self.Dashboard.with_user(self.manager).create({})
action = dashboard.action_open_adp_waiting_app()
self.assertEqual(action['res_model'], 'sale.order')
self.assertIn(('x_fc_adp_application_status', 'in',
['waiting_for_application', 'assessment_completed']),
action['domain'])
def test_action_open_bottleneck_no_pod_returns_correct_domain(self):
dashboard = self.Dashboard.with_user(self.manager).create({})
action = dashboard.action_open_bottleneck_no_pod()
self.assertEqual(action['res_model'], 'sale.order')
self.assertIn(('x_fc_proof_of_delivery', '=', False), action['domain'])
def test_action_open_mod_awaiting_funding_returns_correct_domain(self):
dashboard = self.Dashboard.with_user(self.manager).create({})
action = dashboard.action_open_mod_awaiting_funding()
self.assertEqual(action['res_model'], 'sale.order')
self.assertIn(('x_fc_mod_status', '=', 'awaiting_funding'), action['domain'])
def test_action_open_my_activities_returns_activity_model(self):
dashboard = self.Dashboard.with_user(self.manager).create({})
action = dashboard.action_open_my_activities()
self.assertEqual(action['res_model'], 'mail.activity')
# -------------------------------------------------------------------------
# Task 8 — Create-SO hotlinks
# -------------------------------------------------------------------------
def test_action_create_adp_so_has_default_sale_type(self):
dashboard = self.Dashboard.with_user(self.manager).create({})
action = dashboard.action_create_adp_so()
self.assertEqual(action['res_model'], 'sale.order')
self.assertEqual(action['view_mode'], 'form')
self.assertEqual(action['context']['default_x_fc_sale_type'], 'adp')
def test_action_create_mod_so_has_default_sale_type(self):
dashboard = self.Dashboard.with_user(self.manager).create({})
action = dashboard.action_create_mod_so()
self.assertEqual(action['context']['default_x_fc_sale_type'], 'march_of_dimes')
def test_action_create_odsp_so_has_division_default(self):
dashboard = self.Dashboard.with_user(self.manager).create({})
action = dashboard.action_create_odsp_so()
self.assertEqual(action['context']['default_x_fc_sale_type'], 'odsp')
self.assertEqual(action['context']['default_x_fc_odsp_division'], 'standard')
def test_all_create_so_actions_exist(self):
dashboard = self.Dashboard.with_user(self.manager).create({})
for method_name, expected_type in [
('action_create_adp_so', 'adp'),
('action_create_mod_so', 'march_of_dimes'),
('action_create_odsp_so', 'odsp'),
('action_create_wsib_so', 'wsib'),
('action_create_insurance_so', 'insurance'),
('action_create_mdc_so', 'muscular_dystrophy'),
('action_create_hardship_so', 'hardship'),
('action_create_private_so', 'direct_private'),
]:
action = getattr(dashboard, method_name)()
self.assertEqual(action['res_model'], 'sale.order')
self.assertEqual(action['context']['default_x_fc_sale_type'], expected_type,
f"{method_name} returned wrong default sale type")