From b2f483d67c61207984c20a2136d2cc4adf11f219 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Thu, 21 May 2026 03:29:23 -0400 Subject: [PATCH] docs(fusion_claims): add dashboard redesign spec Action-oriented dashboard replacing the existing 4-panel HTML overview: posting-week banner with live countdown, 3 KPI tiles, 8 funder hotlinks, ADP + MOD workflow flag tiles, role-aware filtering, dark-mode aware SCSS. Spec captures all design decisions from the brainstorm session; ready to hand off to writing-plans. Co-Authored-By: Claude Opus 4.7 (1M context) --- fusion_claims/.gitignore | 1 + ...26-05-21-fusion-claims-dashboard-design.md | 432 ++++++++++++++++++ 2 files changed, 433 insertions(+) create mode 100644 fusion_claims/.gitignore create mode 100644 fusion_claims/docs/superpowers/specs/2026-05-21-fusion-claims-dashboard-design.md diff --git a/fusion_claims/.gitignore b/fusion_claims/.gitignore new file mode 100644 index 00000000..7a95436d --- /dev/null +++ b/fusion_claims/.gitignore @@ -0,0 +1 @@ +.superpowers/ diff --git a/fusion_claims/docs/superpowers/specs/2026-05-21-fusion-claims-dashboard-design.md b/fusion_claims/docs/superpowers/specs/2026-05-21-fusion-claims-dashboard-design.md new file mode 100644 index 00000000..ebbc81fb --- /dev/null +++ b/fusion_claims/docs/superpowers/specs/2026-05-21-fusion-claims-dashboard-design.md @@ -0,0 +1,432 @@ +# Fusion Claims Dashboard — Design Spec + +**Date:** 2026-05-21 +**Module:** `fusion_claims` +**Status:** Design approved, ready for implementation plan +**Replaces:** the existing 4-panel HTML-field dashboard at `models/dashboard.py` + `views/dashboard_views.xml` + +--- + +## 1. Purpose + +Surface workflow flags, posting-week context, and per-funder hotlinks on a single dashboard so claims processors, sales reps, and managers can see at a glance what needs action today and how much money is in motion for the current ADP posting cycle. + +The existing dashboard is a case-count overview. The new dashboard is action-oriented: "what's stuck, what's due this week, what should I be doing." + +## 2. Audience and role behaviour + +Single dashboard used by three personas, with auto-applied role filter: + +- **Managers** (in `fusion_claims.group_fusion_claims_manager` or `sales_team.group_sale_manager`) — see all cases. +- **Office staff** — same as managers (they are typically in the manager group already, per the module's security model). +- **Sales reps** (only in `group_fusion_claims_user`) — see only SOs where `user_id = self.env.uid`. + +A small "Showing your cases" hint appears above the workflow tiles when the role filter is active (driven by computed `is_manager`). + +## 3. Scope + +**In scope:** +- Posting-period banner with live countdown to submission cutoff +- 3 KPI tiles: Ready to Claim, Claimed This Period, Total AR (ADP-portion) +- 8 quick-action hotlinks: + ADP, + MOD, + ODSP, + WSIB, + Insurance, + MDC, + Hardship, + Private +- "Your Activities" list (top 10 of current user's `mail.activity`) +- Two bottleneck callouts: Approved without POD, Submitted with no ADP response > 14 days +- ADP Pre-Approval workflow tiles (4): Waiting App, App Received, Ready Submission, Needs Correction +- ADP Post-Approval workflow tiles (4): Approved, Ready Delivery, Ready Billing, On Hold +- MOD workflow tiles (5): Awaiting Funding, Funding Approved, PCA Received, Project Complete, POD Submitted +- Other-funder count cards (6): ODSP, WSIB, Insurance, MDC, Hardship, ACSD +- Light + dark theme support via compile-time SCSS branching + +**Out of scope:** +- Charts / time-series graphs +- The existing 4 configurable HTML panels (removed) +- A "Recent Cases" power-user view (deferred — separate spec if needed) +- Auto-refresh on window focus (manual reload only) +- Per-user personalisation beyond the role filter (no saved layouts/filters) +- Push notifications, email digests (out of scope, handled elsewhere) + +## 4. Architecture + +### 4.1 Implementation pattern + +**Hybrid: form-view shell + computed fields + small OWL widget for the live countdown.** + +Server-rendered Bootstrap-grid form view sits on top of a TransientModel with ~36 computed fields. One OWL field-widget handles the live deadline countdown (ticks every 60 seconds, swaps colour as deadline approaches). + +The TransientModel name `fusion.claims.dashboard` is **preserved** — existing menu/action records continue to resolve. The model's internals are rewritten; old fields are dropped. + +### 4.2 Files + +| File | Action | Purpose | +|---|---|---| +| `models/dashboard.py` | **Rewrite** | TransientModel with ~36 computed fields + role-filter helper + ~24 action methods | +| `views/dashboard_views.xml` | **Rewrite** | Form view: banner → KPIs → quick-actions → 2-column grid | +| `static/src/scss/_fc_dashboard_tokens.scss` | **New** | Colour palette tokens, compile-time `@if $o-webclient-color-scheme == dark` branch | +| `static/src/scss/fc_dashboard.scss` | **New** | Layout + section styles, references tokens | +| `static/src/js/fc_posting_countdown.js` | **New** | OWL field widget for live countdown (~60 lines) | +| `static/src/xml/fc_posting_countdown.xml` | **New** | OWL template (~10 lines) | +| `__manifest__.py` | **Edit** | Bump version (asset cache-bust), add SCSS to **both** `web.assets_backend` AND `web.assets_web_dark`, add JS+XML to backend | + +### 4.3 Layout + +``` +┌──────────────────────────────────────────────────────────────┐ +│ BANNER: Posting Period: Mar 5 – 19 · [OWL: 3d to cutoff] │ +├──────────────────────────────────────────────────────────────┤ +│ KPI TILES (3-up): Ready | Claimed | Total AR │ +├──────────────────────────────────────────────────────────────┤ +│ QUICK ACTIONS: + ADP + MOD + ODSP + WSIB + Ins + ... │ +├────────────────────────┬─────────────────────────────────────┤ +│ LEFT COLUMN │ RIGHT COLUMN │ +│ Your Activities │ ADP Pre-Approval (4 tiles) │ +│ Bottlenecks │ ADP Post-Approval (4 tiles) │ +│ Other Funders (6) │ MOD (5 tiles) │ +└────────────────────────┴─────────────────────────────────────┘ +``` + +### 4.4 Data flow + +1. User clicks Dashboard menu. +2. Existing `action_fusion_claims_dashboard` creates a fresh TransientModel record. +3. Compute methods run (5 clusters — see §6). +4. Form renders. +5. OWL countdown widget tickets every 60 s, reading `submission_deadline_dt` from the rendered field, formatting it client-side. +6. User clicks a tile → returns `ir.actions.act_window` opening a filtered `sale.order` list. +7. User clicks a quick-action pill → returns `ir.actions.act_window` opening a fresh `sale.order` form with `default_x_fc_sale_type` in context. +8. User clicks Refresh (form header button) → reloads the action. + +## 5. Role filter + +Central helper on `fusion.claims.dashboard`: + +```python +def _role_filter_domain(self): + user = self.env.user + if (user.has_group('fusion_claims.group_fusion_claims_manager') + or user.has_group('sales_team.group_sale_manager')): + return [] + return [('user_id', '=', user.id)] +``` + +Every count/sum compute method prepends `_role_filter_domain()` to its domain. For `account.move` based counts (KPIs), the filter is applied through `x_fc_source_sale_order_id.user_id` (the linked SO's salesperson) because invoices don't have their own `user_id` to filter on in this module. + +`is_manager` (Boolean computed) exposed for the view to optionally show a "Showing your cases" hint. + +## 6. Field inventory (≈36 fields) + +### 6.1 Header / banner + +| Field | Type | Description | +|---|---|---| +| `posting_period_label` | Char | e.g. `"Mar 5 – Mar 19"` | +| `posting_period_start` | Date | Start of current posting cycle | +| `posting_period_end` | Date | Start of next cycle (exclusive) | +| `submission_deadline_dt` | Datetime | Wed 18:00 of posting week, Toronto TZ | +| `is_manager` | Boolean | Drives role-hint visibility | +| `is_pre_first_posting` | Boolean | True if today < `adp_posting_base_date` | + +Derived from helpers already on `adp.posting.schedule.mixin`. Dashboard `_inherit = ['adp.posting.schedule.mixin']`. + +### 6.2 KPI tiles + +| Field | Type | Source | +|---|---|---| +| `kpi_ready_amount` | Monetary | Sum of `account.move.amount_total` where `x_fc_adp_billing_status='waiting'` AND `adp_exported=False`, role-filtered via linked SO | +| `kpi_ready_count` | Integer | Same filter, count | +| `kpi_claimed_amount` | Monetary | Sum where `x_fc_adp_billing_status in ('submitted','resubmitted')` AND `adp_export_date >= posting_period_start` | +| `kpi_claimed_count` | Integer | Same filter, count | +| `kpi_ar_amount` | Monetary | Sum where `move_type='out_invoice'`, `state='posted'`, `payment_state in ('not_paid','partial')`, `x_fc_invoice_type='adp'` | +| `kpi_ar_count` | Integer | Same filter, count | +| `currency_id` | Many2one | Defaults to `company_id.currency_id` | + +### 6.3 Activities (left column) + +| Field | Type | Description | +|---|---|---| +| `my_activities_count` | Integer | `mail.activity` where `user_id=current_user` AND `res_model in ('sale.order','account.move','fusion.technician.task')` | +| `my_activities_html` | Html | Top 10 ordered by `date_deadline asc`, links via `/odoo//`, overdue rows tinted | + +### 6.4 Bottlenecks (left column) + +| Field | Type | Domain | +|---|---|---| +| `bottleneck_no_pod_count` | Integer | ADP cases `x_fc_adp_application_status in ('approved','approved_deduction')` AND `x_fc_proof_of_delivery=False` | +| `bottleneck_no_response_count` | Integer | ADP cases `x_fc_adp_application_status in ('submitted','resubmitted')` AND `x_fc_claim_submission_date < today - 14 days` | + +### 6.5 Other funders (left column) + +Each is an Integer count of active (non-terminal) cases: + +| Field | Domain | +|---|---| +| `count_odsp` | `x_fc_sale_type in ('odsp','adp_odsp')` excluding division-specific terminal states | +| `count_wsib` | `x_fc_sale_type='wsib'` excluding `case_closed`, `cancelled`, `denied` | +| `count_insurance` | `x_fc_sale_type='insurance'` excluding terminal states | +| `count_mdc` | `x_fc_sale_type='muscular_dystrophy'` excluding terminal states | +| `count_hardship` | `x_fc_sale_type='hardship'` excluding terminal states | +| `count_acsd` | `x_fc_client_type='ACS'` excluding terminal states | + +### 6.6 ADP Pre-Approval (right column, 4 tiles) + +| Field | Status filter | +|---|---| +| `adp_waiting_app_count` | `x_fc_adp_application_status in ('waiting_for_application','assessment_completed')` | +| `adp_app_received_count` | `x_fc_adp_application_status='application_received'` | +| `adp_ready_submit_count` | `x_fc_adp_application_status='ready_submission'` | +| `adp_needs_correction_count` | `x_fc_adp_application_status='needs_correction'` (rendered as urgent tile) | + +`adp_waiting_app_count` and `adp_needs_correction_count` are styled `--urgent` (red tint). + +### 6.7 ADP Post-Approval (right column, 4 tiles) + +| Field | Status filter | +|---|---| +| `adp_approved_count` | `x_fc_adp_application_status in ('approved','approved_deduction')` | +| `adp_ready_delivery_count` | `x_fc_adp_application_status='ready_delivery'` | +| `adp_ready_bill_count` | `x_fc_adp_application_status='ready_bill'` | +| `adp_on_hold_count` | `x_fc_adp_application_status='on_hold'` (rendered as urgent tile) | + +### 6.8 MOD (right column, 5 tiles) + +| Field | Status filter | +|---|---| +| `mod_awaiting_funding_count` | `x_fc_mod_status='awaiting_funding'` | +| `mod_funding_approved_count` | `x_fc_mod_status='funding_approved'` | +| `mod_pca_received_count` | `x_fc_mod_status='contract_received'` | +| `mod_project_complete_count` | `x_fc_mod_status='project_complete'` | +| `mod_pod_submitted_count` | `x_fc_mod_status='pod_submitted'` | + +## 7. Compute method clustering + +Five compute methods, each owning a logical section so an expensive query in one cluster doesn't recompute the rest: + +| Method | Fields populated | +|---|---| +| `_compute_banner` | 6 banner fields | +| `_compute_kpis` | 6 KPI fields + `currency_id` | +| `_compute_activities` | 2 activity fields | +| `_compute_workflow_counts` | 13 stage-tile fields (ADP + MOD) | +| `_compute_secondary_counts` | 8 fields (bottlenecks + other funders) | + +All compute methods are bound to non-stored `compute='_compute_*'` fields (no `@api.depends` since TransientModel records are throwaway — every dashboard open is a fresh record). Counts use `search_count()` not `search()` to avoid loading recordsets. + +## 8. Action methods (~24) + +### 8.1 `action_open_` (~16) + +Thin wrappers returning `ir.actions.act_window`. Where the module already has per-stage actions (e.g. `adp_claims_views.xml` defines `act_window_adp_ready_for_billing`), reuse them via `self.env.ref(...).read()[0]`. Otherwise build the action inline. + +Examples: +- `action_open_adp_waiting_app` — opens SO list filtered to `('x_fc_adp_application_status', 'in', ['waiting_for_application', 'assessment_completed'])` +- `action_open_bottleneck_no_pod` — opens SO list filtered to approved-without-POD +- `action_open_my_activities` — opens activity list filtered to current user + +### 8.2 `action_create__so` (8) + +One per funder hotlink. Each opens a fresh `sale.order` form with `default_x_fc_sale_type` in context: + +| Method | Context | +|---|---| +| `action_create_adp_so` | `{'default_x_fc_sale_type': 'adp'}` | +| `action_create_mod_so` | `{'default_x_fc_sale_type': 'march_of_dimes'}` | +| `action_create_odsp_so` | `{'default_x_fc_sale_type': 'odsp', 'default_x_fc_odsp_division': 'standard'}` | +| `action_create_wsib_so` | `{'default_x_fc_sale_type': 'wsib'}` | +| `action_create_insurance_so` | `{'default_x_fc_sale_type': 'insurance'}` | +| `action_create_mdc_so` | `{'default_x_fc_sale_type': 'muscular_dystrophy'}` | +| `action_create_hardship_so` | `{'default_x_fc_sale_type': 'hardship'}` | +| `action_create_private_so` | `{'default_x_fc_sale_type': 'direct_private'}` | + +User picks ODSP division on the SO form (we default to `standard`, they can change to `sa_mobility` or `ontario_works`). + +## 9. Theming (SCSS structure) + +### 9.1 File order + +Tokens load **first** in each bundle. SCSS variables defined in `_fc_dashboard_tokens.scss` must be in scope when `fc_dashboard.scss` is compiled. Odoo concatenates SCSS within a bundle in registration order, so the manifest registration sequence is load-bearing — see §11. + +### 9.2 `_fc_dashboard_tokens.scss` + +Single source of truth. Define light values at top level, override with `!global` inside `@if $o-webclient-color-scheme == dark`. Token names use the `$_fc-*` convention (underscore prefix for "private" partials). + +Light palette (22 tokens): + +``` +page-bg: #f7f7f8 card-bg: #ffffff card-border: #d8dadd +text: #2b2b2b text-muted: #6c7480 + +banner: linear-gradient(#eef2ff → #fce7f3) border: #c7d2fe text: #3730a3 +deadline-text: #b91c1c + +kpi-bg: #f0f4ff kpi-border: #c7d2fe kpi-num: #1e3a8a + +action-bg: #ecfdf5 action-border: #6ee7b7 action-text: #047857 + +tile-bg: #f3f4f6 tile-border: #e5e7eb tile-num: #111827 + +urgent-bg: #fee2e2 urgent-border: #fca5a5 urgent-num: #991b1b urgent-text: #7f1d1d + +activity-bg: #fefce8 activity-border: #fde047 +bottleneck-bg: #fef2f2 bottleneck-border: #fecaca +``` + +Dark palette overrides (cool blue monochrome banner per Round 3 selection): + +``` +page-bg: #1a1d21 card-bg: #22262d card-border: #3a3f47 +text: #e5e7eb text-muted: #9ca3af + +banner: linear-gradient(#1e293b → #1e3a5f) border: #3b82f6 text: #93c5fd +deadline-text: #fca5a5 + +kpi-bg: #1e293b kpi-border: #334155 kpi-num: #93c5fd + +action-bg: #064e3b action-border: #047857 action-text: #6ee7b7 + +tile-bg: #2d3138 tile-border: #3a3f47 tile-num: #f3f4f6 + +urgent-bg: #4a1414 urgent-border: #7f1d1d urgent-num: #fca5a5 urgent-text: #fecaca + +activity-bg: #3a2e0a activity-border: #854d0e +bottleneck-bg: #3a1414 bottleneck-border: #7f1d1d +``` + +### 9.3 `fc_dashboard.scss` + +Layout file. Re-exports each token as a CSS custom property scoped under `.o_fc_dashboard` so dev-tools can inspect/tweak live, then uses both the SCSS variable (for compile-time work like `darken()`) and the CSS variable (for runtime). Section classes: + +- `.o_fc_banner` — gradient + border, flex-row with deadline countdown on the right +- `.o_fc_kpi` (with `.o_fc_kpi__num`) — 3-up KPI tiles +- `.o_fc_pill` — quick-action button pills +- `.o_fc_activities`, `.o_fc_bottleneck` — left-column section backgrounds +- `.o_fc_tile`, `.o_fc_tile--urgent` (with `.o_fc_tile__num`) — workflow stage tiles +- `.o_fc_countdown--info` / `.o_fc_countdown--warning` / `.o_fc_countdown--danger` / `.o_fc_countdown--muted` — countdown widget colour levels (driven by OWL state) + +### 9.4 Verification + +After deploy, in `odoo-shell`: + +```python +env['ir.qweb']._get_asset_bundle('web.assets_backend').css() # light bundle URL +env['ir.qweb']._get_asset_bundle('web.assets_web_dark').css() # dark bundle URL +``` + +The two URLs must differ. If they're identical, the dark bundle didn't recompile — fix by deleting `ir.attachment` rows under `/web/assets/%` and restarting Odoo. + +## 10. OWL countdown widget + +### 10.1 Why a widget + +The rest of the dashboard is fine being recomputed on page open — case counts move slowly. The countdown ("3 days 4 hours to cutoff") needs to tick without a page refresh, and its colour needs to shift as the deadline approaches (info → warning → danger). + +### 10.2 Behaviour + +- Registered as a field widget under the name `fc_posting_countdown`. +- Reads `submission_deadline_dt` from `props.record.data`. +- Ticks every 60 seconds via `setInterval`. Cleared on `onWillDestroy`. +- Four levels with auto-shift: + - `> 3 days remaining` → **info** (banner text colour) + - `1–3 days` → **warning** (amber) + - `< 24 hours` → **danger** (urgent-num colour) + - `past deadline` → **muted** (text-muted colour), text reads "Cutoff passed" +- Uses Luxon for date math (already loaded by Odoo). + +### 10.3 Template + +```xml + + + + + +``` + +### 10.4 Use in form view + +```xml + +``` + +## 11. Manifest changes + +```python +'version': '', # e.g. 19.0.8.0.7 → 19.0.9.0.0 for asset cache-bust per CLAUDE.md §Asset Cache Busting + +'data': [ + # ...existing entries (data files load order unchanged)... + 'views/dashboard_views.xml', # rewritten +], + +'assets': { + 'web.assets_backend': [ + # ...existing entries... + 'fusion_claims/static/src/scss/_fc_dashboard_tokens.scss', # tokens FIRST + 'fusion_claims/static/src/scss/fc_dashboard.scss', + 'fusion_claims/static/src/js/fc_posting_countdown.js', + 'fusion_claims/static/src/xml/fc_posting_countdown.xml', + ], + 'web.assets_web_dark': [ + 'fusion_claims/static/src/scss/_fc_dashboard_tokens.scss', + 'fusion_claims/static/src/scss/fc_dashboard.scss', + # No JS in dark bundle — Odoo loads JS once from backend. + ], +}, +``` + +Token file is registered **before** layout file in **both** bundles. JS+XML only in backend. + +## 12. Edge cases + +### 12.1 Pre-first-posting + +If today < `fusion_claims.adp_posting_base_date` (default 2026-01-23), `_get_current_posting_date()` returns the base date itself. Treatment: + +- `posting_period_label` reads `"Posting starts Jan 23"`. +- `submission_deadline_dt` set to first Wednesday at 18:00. +- KPI tiles all show `$0 / 0` (no posting period to bill against yet). +- `is_pre_first_posting=True` is exposed; view shows a one-line info note above the KPIs. + +### 12.2 No invoices / empty system + +All counts compute to 0. KPI tiles render `$0.00`. Activities section renders an empty-state message ("No activities assigned"). Bottleneck section hides itself when both counts are zero. + +### 12.3 Sales rep with no assigned SOs + +`_role_filter_domain()` returns `[('user_id', '=', user.id)]`. All counts → 0. The form still renders; "Showing your cases" hint plus an empty-state message ("You have no assigned cases"). + +### 12.4 Portal user accidentally clicks dashboard menu + +The dashboard menu is already gated by `groups_id` on the existing menu item to `fusion_claims.group_fusion_claims_user` (internal users only). Confirm this is preserved in the rewritten `dashboard_views.xml`. + +### 12.5 Currency mix + +KPI sums assume a single company currency. `currency_id` defaults to `company_id.currency_id`. If invoices in another currency exist, they are summed in their own currency by Odoo's standard behaviour — out of scope to handle multi-currency for this dashboard. Document this limitation in the design note. + +## 13. Decisions explicitly excluded + +- **Auto-refresh on window focus** — considered, dropped to keep scope tight. Manual refresh via form header button is sufficient. +- **The 4 configurable HTML panels from the existing dashboard** — removed entirely. If a "Recent Cases" view is needed later, that's a separate spec. +- **Per-funder workflow tiles for ODSP / WSIB / Insurance / MDC / Hardship** — those funders get a count card only, not a row of stage tiles. Decision: keep the dashboard focused on the two highest-volume funders (ADP, MOD). +- **Toggle between "My Cases" and "All Cases"** — group-based auto-filter only. Sales reps see their cases, managers see everything, no switch. + +## 14. Acceptance criteria + +1. Dashboard menu opens to a single page; old 4-panel UI gone. +2. Banner shows current posting period and a live (ticking) countdown to Wed 6 PM cutoff. +3. 3 KPI tiles render with correct dollar amounts for Ready / Claimed This Period / Total AR. +4. 8 quick-action pills open a fresh SO form with the correct `x_fc_sale_type` pre-applied. +5. All 17 workflow tiles show non-stale counts (verified by clicking a tile → resulting SO list count matches the tile number). +6. Both bottleneck callouts compute and render; clicking opens the matching filtered SO list. +7. Sales reps see only their own cases; managers see all. +8. Light and dark themes render the dashboard without any invisible / low-contrast elements. Verified by: + - Opening in light mode → no `display:none`-like artifacts, all text readable. + - Switching to dark mode (user profile → Color Scheme → Dark → reload) → all colours shift to the dark palette, banner gradient is the cool blue monochrome. +9. Asset bundles compile to distinct URLs in both themes (verified with the §9.4 snippet). +10. No regression on existing dashboard menu item / action references — module loads cleanly, no XML resolution errors. + +## 15. Open questions / non-decisions + +None. All design choices are locked in. Implementation plan can proceed.