# -*- 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")