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>
22 KiB
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_managerorsales_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 whereuser_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
- User clicks Dashboard menu.
- Existing
action_fusion_claims_dashboardcreates a fresh TransientModel record. - Compute methods run (5 clusters — see §6).
- Form renders.
- OWL countdown widget tickets every 60 s, reading
submission_deadline_dtfrom the rendered field, formatting it client-side. - User clicks a tile → returns
ir.actions.act_windowopening a filteredsale.orderlist. - User clicks a quick-action pill → returns
ir.actions.act_windowopening a freshsale.orderform withdefault_x_fc_sale_typein context. - User clicks Refresh (form header button) → reloads the action.
5. Role filter
Central helper on fusion.claims.dashboard:
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-PODaction_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:
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_dtfromprops.record.data. - Ticks every 60 seconds via
setInterval. Cleared ononWillDestroy. - 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
<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
<field name="submission_deadline_dt"
widget="fc_posting_countdown"
nolabel="1"
readonly="1"/>
11. Manifest changes
'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_labelreads"Posting starts Jan 23".submission_deadline_dtset to first Wednesday at 18:00.- KPI tiles all show
$0 / 0(no posting period to bill against yet). is_pre_first_posting=Trueis 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
- Dashboard menu opens to a single page; old 4-panel UI gone.
- Banner shows current posting period and a live (ticking) countdown to Wed 6 PM cutoff.
- 3 KPI tiles render with correct dollar amounts for Ready / Claimed This Period / Total AR.
- 8 quick-action pills open a fresh SO form with the correct
x_fc_sale_typepre-applied. - All 17 workflow tiles show non-stale counts (verified by clicking a tile → resulting SO list count matches the tile number).
- Both bottleneck callouts compute and render; clicking opens the matching filtered SO list.
- Sales reps see only their own cases; managers see all.
- 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.
- Opening in light mode → no
- Asset bundles compile to distinct URLs in both themes (verified with the §9.4 snippet).
- 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.