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) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-21 03:29:23 -04:00
parent f1cea2fb35
commit b2f483d67c
2 changed files with 433 additions and 0 deletions

1
fusion_claims/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.superpowers/

View File

@@ -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/<model>/<id>`, 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_<bucket>` (~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_<funder>_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)
- `13 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
<templates xml:space="preserve">
<t t-name="fusion_claims.PostingCountdown" owl="1">
<span t-att-class="'o_fc_countdown o_fc_countdown--' + state.level"
t-esc="state.text"/>
</t>
</templates>
```
### 10.4 Use in form view
```xml
<field name="submission_deadline_dt"
widget="fc_posting_countdown"
nolabel="1"
readonly="1"/>
```
## 11. Manifest changes
```python
'version': '<bump minor>', # 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.