docs(portal): add dashboard redesign spec + implementation plan

Spec covers the brainstormed design: jobs-forward layout, V2 stepper
with timestamps, EN Plating teal/gradient palette, 4 doc categories.
Plan decomposes implementation into 4 independently-deployable phases
(tokens+buttons -> dashboard -> jobs detail -> cosmetic sweep) with
27 tasks total.

Also adds .gitignore so .superpowers/ brainstorm artifacts stay
untracked.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-17 02:36:02 -04:00
parent 655b767127
commit eac337c058
3 changed files with 2880 additions and 0 deletions

10
fusion_plating/.gitignore vendored Normal file
View File

@@ -0,0 +1,10 @@
# Superpowers brainstorm session artifacts (mockups, HTML drafts).
# The companion server saves files here; not project source.
.superpowers/
# Local Odoo dev artifacts
*.pyc
__pycache__/
*.egg-info/
.idea/
.vscode/

View File

@@ -0,0 +1,278 @@
# Customer Portal Dashboard Redesign
**Module**: `fusion_plating_portal`
**Date**: 2026-05-17
**Status**: Design locked, awaiting implementation plan
**Surface**: `https://enplating.com/my/*`
## Problem
The existing `/my/home` is functional but visually bland and (until the 2026-05-17 hotfix) was not even reaching the browser because of a controller-inheritance bug. The customer's primary question on login — "where is my part right now?" — was buried inside one of six equal-weight cards. There is no per-job view of progress over time, no grouped document surface for QA managers, and no brand alignment with the marketing site at enplating.com.
This redesign reframes the portal around jobs, gives each job a customer-visible timeline with timestamps, and surfaces every job-related document in a single grouped panel. Branding follows enplating.com's teal palette.
## User stories
1. **As an aerospace QA manager**, when I log in I want to see the status of every active job without clicking into anything, so I can answer "where's PO-7783?" in under 5 seconds.
2. **As a buyer**, when I open a job's detail page I want to see exactly when each stage happened (date + time + operator name) so I can verify our SLA terms are being met.
3. **As a QA manager**, when I need a Certificate of Conformance for an audit I want every document for that job grouped by category (customer inputs, specifications, quality records, shipping) so I'm not hunting through a list.
4. **As any customer**, when I land on the portal I want it to look and feel like enplating.com — teal accents, gradient CTAs, brand typography — so it feels like the same company.
## Locked design decisions (from brainstorming 2026-05-17)
| Decision | Choice | Why |
|---|---|---|
| Design language | Modern SaaS — generous whitespace, rounded corners (`14px` cards, `9px` buttons, `9999px` badges), soft shadows | User picked "Modern SaaS" over Corporate B2B and Industrial in the design-direction screen. |
| Brand palette | EN Plating teal/green pulled live from enplating.com | User asked for "stay close to the site color schemes". |
| Primary card design | Numbered circular stepper + horizontal connecting lines + date/time stamp under each step label | User picked V2 (numbered stepper) and asked for V3's timestamps merged in. |
| Dashboard layout | Jobs-forward: welcome → 4-tile KPI strip → Active Work Orders hero (full V2 cards) → 3-col secondary panels (Certs / Packing Slips / Invoices) | User picked Layout B over the 6-equal-section grid. |
| CTA buttons | Gradient `#2eaf93 → #1a6b59` linear-135deg with brand-tinted box-shadow; danger is `#ef4444 → #b91c1c` gradient; secondary is flat with subtle `#fff → #f3f7f6` gradient | User asked for "color gradients on the buttons to make it modern and professional". |
| Stepper visuals | Completed circles get gradient fill; active circle uses brand teal stroke + `rgba(46,175,147,.2)` mint glow ring | Pulled from approved branded mockup. |
| Status badges | Pill with `width:7px;height:7px` coloured dot + soft glow halo. Status-to-colour map: In Progress → mint; QC → amber; Ready / Done → green; Hold → red. | Pulled from approved branded mockup. |
| Document categories | **From You** (customer-uploaded: PO, drawings) · **Specifications** (FP customer spec) · **Quality** (CoC, Fischerscope thickness report) · **Shipping** (packing slip, tracking) | Surfaced in the approved detail-page mockup and not contested. |
| Default landing | `/my/home` (replaces stock Odoo home; already the case after the 2026-05-17 hotfix that renamed `portal_my_home_dashboard()``home()`) | See Critical Rule 16 in CLAUDE.md. |
## Scope
**IN SCOPE — full structural + visual redesign:**
- `/my/home` — jobs-forward dashboard (template + controller already exist; need rewrite of template, controller data prep is mostly fine)
- `/my/jobs` — list page, redesigned to use V2 card shape per row (or compact variant for list density)
- `/my/jobs/<id>` — detail page using vertical timeline + grouped documents
- Brand token system: a single SCSS partial (`_fp_portal_tokens.scss`) defining teal palette, button gradients, shadows, spacing scale, typography scale — referenced by every portal SCSS file
- Reusable QWeb macros: `fp_portal_stepper`, `fp_portal_doc_chip`, `fp_portal_status_badge` — so every page renders consistent components
**IN SCOPE — cosmetic-only (new tokens applied, no structural change):**
- `/my/quote_requests` (list + detail)
- `/my/purchase_orders` (list + detail)
- `/my/fp_invoices` (list + detail)
- `/my/deliveries` (list + detail)
- `/my/certifications` (list + detail)
- `/my/configurator` (RFQ wizard)
- Portal sidebar (just colour tokens; layout untouched)
**OUT OF SCOPE — deferred:**
- Mobile-specific layout polish (the desktop design uses Bootstrap grid which collapses gracefully; explicit mobile breakpoints can ship in a follow-up)
- Dark-mode parity (Odoo 19 compiles two bundles; if the customer toggles dark on their profile, the portal will fall back to the default Bootstrap dark and look broken until we ship the `$o-webclient-color-scheme` branch — flagged but not blocking)
- Real-time push updates of job status (current refresh-on-load is acceptable; SSE / longpoll is a separate effort)
- In-page document preview (PDFs download via existing `ir.binary` stream; no embedded viewer)
- Internationalisation of the new strings (English / Canadian English; French follows the existing report_coc_fr pattern in a separate effort if needed)
## Architecture
### Surfaces and data sources
| URL | Controller method | Template | Primary data |
|---|---|---|---|
| `/my/home` | `home()` (existing override at portal.py:125) | `fp_portal_home_dashboard` (rewrite) | counts + recent 5 of each: quotes, POs, jobs, certs, deliveries, invoices (already prepared) |
| `/my/jobs` | `portal_my_jobs()` (existing at portal.py:512) | `portal_my_jobs` (rewrite) | `fusion.plating.portal.job` records for partner's commercial tree |
| `/my/jobs/<id>` | `portal_my_job()` (existing at portal.py:565) | `portal_my_job` (rewrite) | Single `fusion.plating.portal.job` + linked docs |
### Controller changes
**No new routes.** The data already pulled by `home()` covers the new dashboard surface (counts + recent records of each type). What needs adding:
1. `home()` needs to also compute the **welcome-line summary** ("6 active jobs · 2 awaiting your review · 1 ready to ship") — three new counts based on `fp.portal.job.state` values.
2. `portal_my_job()` needs a **timeline data prep helper** that walks the underlying `fp.job` (via `portal_job.job_id` or whatever the link is — verify during implementation) and emits a list of `{stage_label, started_at, completed_at, status, operator_name, notes}` records ordered by stage sequence.
3. `portal_my_job()` needs a **document-grouping helper** that takes the job's related records (sale_order_id, receiving_id, certificate_ids, picking_id, invoice_ids) and emits a dict keyed by the 4 categories, with each entry being a list of `{name, mimetype, size, url, uploaded_date}`.
Both helpers live as private methods on `FpCustomerPortal`. **Possible exception**: if the existing `fusion.plating.portal.job` doesn't already store per-stage timestamps in queryable fields, the timeline helper will need either (a) compute fields added on the model, or (b) the controller to walk chatter messages on the underlying `fp.job`. See "Open items" §1 — decided during Phase 3 implementation.
### Template structure
```
fusion_plating_portal/views/
├── fp_portal_dashboard.xml — rewrite of fp_portal_home_dashboard
├── fp_portal_templates.xml — rewrites of portal_my_jobs + portal_my_job
├── fp_portal_macros.xml — NEW: shared QWeb macros
│ ├── t-call="fp_portal_stepper" (job, stage_count)
│ ├── t-call="fp_portal_status_badge" (state)
│ ├── t-call="fp_portal_doc_chip" (doc, dense=False)
│ └── t-call="fp_portal_doc_group" (label, docs)
└── fp_portal_brand.xml — NEW: layout-level brand wrapper template (welcome strip, gradient CTA bar)
```
Existing files preserved:
- `fp_quote_request_views.xml` — only swap colour-coded badges to use the new tokens
- `fp_portal_configurator_templates.xml` — only swap button styles
- `fp_portal_breadcrumbs.xml`, `fp_sale_order_portal.xml`, `fp_menu.xml` — untouched
### SCSS structure
Per CLAUDE.md SCSS rules (no `@import` in custom SCSS; every file registered separately in `web.assets_frontend`; tokens loaded first):
```
fusion_plating_portal/static/src/scss/
├── _fp_portal_tokens.scss — NEW: brand colour, gradient, shadow, radius, spacing, typography vars (no rules)
├── fp_portal_buttons.scss — NEW: gradient button system (primary/secondary/ghost/danger + hover/focus/active states)
├── fp_portal_stepper.scss — NEW: circular stepper geometry + glow ring animations
├── fp_portal_cards.scss — NEW: card/widget shells (radius, shadow, hover lift)
├── fp_portal_badges.scss — NEW: status badge pill + dot + glow halo
├── fp_portal_timeline.scss — NEW: vertical timeline geometry (line, dots, padding)
├── fp_portal_dashboard.scss — NEW: jobs-forward grid + KPI tiles
└── fusion_plating_portal.scss — EXISTS: trimmed down, only catch-all rules left
```
Manifest assets registration order matters (tokens first, then dependencies, then leaf files):
```python
'assets': {
'web.assets_frontend': [
'fusion_plating_portal/static/src/scss/_fp_portal_tokens.scss',
'fusion_plating_portal/static/src/scss/fp_portal_buttons.scss',
'fusion_plating_portal/static/src/scss/fp_portal_badges.scss',
'fusion_plating_portal/static/src/scss/fp_portal_stepper.scss',
'fusion_plating_portal/static/src/scss/fp_portal_cards.scss',
'fusion_plating_portal/static/src/scss/fp_portal_timeline.scss',
'fusion_plating_portal/static/src/scss/fp_portal_dashboard.scss',
'fusion_plating_portal/static/src/scss/fusion_plating_portal.scss',
'fusion_plating_portal/static/src/js/fp_rfq_form.js',
],
},
```
### Brand tokens (single source)
`_fp_portal_tokens.scss` defines the design system:
```scss
// Brand palette (matches enplating.com live CSS variables)
$fp-teal-light: #2eaf93;
$fp-teal: #1a6b59;
$fp-teal-dark: #0e3d2f;
$fp-teal-deep: #0a3528;
$fp-mint: #cbf3e6;
$fp-mint-pastel: #f0fdf9;
$fp-aqua: #9ae5d4;
// Surfaces
$fp-page-bg: #f8fafb;
$fp-section-bg: #f3f7f6;
$fp-card-bg: #ffffff;
$fp-card-border: #e5e7eb;
// Text
$fp-text: #111827;
$fp-text-body: #374151;
$fp-muted: #6b7280;
$fp-muted-light: #9ca3af;
// Status (functional, not brand)
$fp-amber: #f59e0b;
$fp-amber-bg: #fef3c7;
$fp-amber-text: #92400e;
$fp-success: #22c55e;
$fp-success-text: #15803d;
$fp-danger: #ef4444;
$fp-danger-dark: #b91c1c;
// Gradients (3-stop ready; current designs use 2 stops)
$fp-gradient-primary: linear-gradient(135deg, $fp-teal-light 0%, $fp-teal 100%);
$fp-gradient-danger: linear-gradient(135deg, $fp-danger 0%, $fp-danger-dark 100%);
$fp-gradient-mint: linear-gradient(135deg, $fp-mint-pastel 0%, $fp-mint 100%);
$fp-gradient-icon: linear-gradient(135deg, $fp-mint 0%, $fp-aqua 100%);
$fp-gradient-secondary: linear-gradient(180deg, #fff 0%, $fp-section-bg 100%);
// Shadows
$fp-shadow-card: 0 1px 2px rgba(0,0,0,.03);
$fp-shadow-card-hover: 0 1px 3px rgba(0,0,0,.04), 0 4px 12px rgba(0,0,0,.04);
$fp-shadow-button: 0 1px 3px rgba(26,107,89,.25), 0 4px 12px rgba(26,107,89,.18);
$fp-shadow-danger: 0 1px 3px rgba(185,28,28,.25), 0 4px 12px rgba(185,28,28,.15);
$fp-glow-ring: 0 0 0 4px rgba(46,175,147,.20);
// Geometry
$fp-radius-pill: 9999px;
$fp-radius-card: 14px;
$fp-radius-button: 9px;
$fp-radius-chip: 8px;
$fp-radius-icon: 7px;
```
Per the SCSS gotcha in CLAUDE.md (rule 9, dark-mode awareness) the file should branch on `$o-webclient-color-scheme` for any colour that needs to flip in dark mode. Since dark mode is **deferred** in this spec, the branch is documented in `_fp_portal_tokens.scss` as a TODO with hex placeholders, not implemented.
### Document-categorisation logic
Per-category source maps:
| Category | Source records |
|---|---|
| **From You** (customer-uploaded) | `fusion.plating.portal.job.sale_order_id.message_attachment_ids` filtered to customer's uploads + `fusion.plating.portal.job.quote_request_id.line_ids.attachment_ids` (RFQ drawings) |
| **Specifications** | `fusion.plating.portal.job.x_fc_part_catalog_id.x_fc_customer_spec_id.attachment_id` + any spec linked via the SO line `x_fc_customer_spec_id` |
| **Quality** | **Prefer `fp.certificate` records linked to the job** (newer, post-Sub-12c; renders the full CoC with Fischerscope thickness pages already merged via the back-end merge — see CLAUDE.md S19); **fall back to** `fusion.plating.portal.job.coc_attachment_id` (legacy single-attachment field) only when no `fp.certificate` exists. Verify the exact linking field name (likely `x_fc_job_id` or via `sale_order_id`) during Phase 3. |
| **Shipping** | `stock.picking` records linked to the SO with state=done — their packing slip PDF and tracking_reference |
Implementation note: each lookup needs a defensive `try/except` (some attachments may be missing) and `sudo()` where required (customer doesn't have read on the quality_check raw model — they only see its rendered PDF). All model paths in the table above are spec-time assumptions — verify against the live model during Phase 3 and update the spec inline if anything has drifted.
### Welcome-line summary logic
`home()` already pulls `recent_jobs` and `job_count`. Add:
```python
active_jobs = Job.search_count([
('partner_id', 'child_of', commercial.id),
('state', 'in', ['received', 'in_progress', 'quality_check']),
])
awaiting_review = Quote.search_count([
('partner_id', 'child_of', commercial.id),
('state', '=', 'quoted'), # customer needs to accept/reject
])
ready_to_ship = Job.search_count([
('partner_id', 'child_of', commercial.id),
('state', '=', 'ready_to_ship'),
])
```
These three numbers populate the welcome line: "X active jobs · Y awaiting your review · Z ready to ship".
### Timeline data prep
Existing `fp.portal.job` has a 5-state machine: `received → in_progress → quality_check → ready_to_ship → shipped`. The detail page needs per-stage timestamps. Implementation approach:
1. Add helper `_fp_get_stage_timeline()` on `fp.portal.job` returning a list of 5 dicts: `{label, status, started_at, completed_at, operator_name, notes}`.
2. `status` is one of `done | active | pending`.
3. `started_at` / `completed_at` come from the existing chatter / write_date on related records (e.g., for `received` use `received_date`; for `in_progress` use the first `fp.job` confirm date; for `quality_check` use the QC check create_date; for `ready_to_ship` use the picking confirm date; for `shipped` use `actual_ship_date`).
4. `operator_name` pulled from the chatter event author where available; defaults to "EN Plating".
5. `notes` is a short human-readable sentence per stage — examples in the mockup: "2 cartons · 240 units counted matches PO", "Type II hard anodize, 0.002" target". For v1 these can be hardcoded templates per stage; v2 can render from the actual recipe step instructions.
### Mockups (single source of truth for visual fidelity)
Preserved in `.superpowers/brainstorm/1800-1778997036/content/`:
- `design-direction.html` — A/B/C aesthetic options (Modern SaaS won)
- `saas-refinements.html` — V1/V2/V3 card variants (V2 stepper + V3 timestamps won)
- `dashboard-layout.html` — A vs B layout (B jobs-forward won)
- `job-detail.html` — detail page using V3 layout (approved)
- `branded-dashboard.html` — final brand-applied dashboard with gradient buttons (approved)
The implementation should match `branded-dashboard.html` for the dashboard, and the branded version of `job-detail.html` (apply the same teal/gradient swap) for the detail page.
## Migration / deployment
1. Bump `fusion_plating_portal/__manifest__.py` version `19.0.2.3.0 → 19.0.3.0.0` (minor bump signals a UI-significant change).
2. Add new SCSS + macro XML files to the manifest `data` and `assets` lists.
3. Deploy to entech per the standard runbook (see CLAUDE.md "Deployment / odoo-entech" section).
4. Asset cache bust: `DELETE FROM ir_attachment WHERE url LIKE '/web/assets/%';` then restart, or just rely on the version bump.
5. Verify the dashboard renders correctly at `https://enplating.com/my/home` (logged in as admin, then optionally grant portal access to a real customer for end-to-end verification).
## Implementation sequencing
To keep diffs reviewable, ship in 4 ordered phases:
1. **Phase 1 — Tokens + button system** (1 file: `_fp_portal_tokens.scss` + `fp_portal_buttons.scss`, manifest update). Visible change: only buttons re-skin; rest of portal unchanged. Low blast radius; deployable on its own.
2. **Phase 2 — Macros + dashboard** (`fp_portal_macros.xml` + dashboard SCSS + `fp_portal_home_dashboard` template rewrite + welcome-summary counts in `home()`). Visible change: `/my/home` becomes the jobs-forward dashboard.
3. **Phase 3 — Jobs list + detail** (`portal_my_jobs` + `portal_my_job` template rewrites + timeline + doc-group helpers in controller). Visible change: clicking a job card now lands on the new detail page.
4. **Phase 4 — Cosmetic sweep across other `/my/*` pages** — apply the new badge / button / card tokens to quote requests, POs, invoices, deliveries, certifications, configurator. No structural changes.
Each phase bumps the patch version (19.0.3.0.0 → .1.0 → .2.0 → .3.0 → .4.0).
## Open items to verify during implementation
These are flagged as assumptions to confirm against the live model, not blockers:
1. **Stage timestamp sources** — the 5 stages on `fp.portal.job` may not all have explicit timestamp fields. Confirm: do we need to add fields, or pull from the underlying `fp.job` / chatter? If fields are missing, add them as `Datetime` on `fp.portal.job` with `compute=` from the underlying records.
2. **Operator name surfacing** — customers seeing operator names is a privacy / policy question. Default in the spec is to show first-initial + last-name (e.g., "D. Mendez"). Confirm with EN Plating before shipping.
3. **Stage-notes copy** — the example notes in the mockup ("Tank 4 · 45 min cycle · Day 3 of 7") are made up. Confirm what info is reasonable to share with the customer per stage.
4. **Document linking edge cases** — what shows when there are 0 documents in a category? Spec assumes a "Will appear when…" placeholder card per the approved mockup. Verify EN Plating doesn't want the placeholder hidden entirely when empty.
5. **Dark mode** — explicitly deferred. If a customer logs in with `color_scheme=dark` set, what should they see? Default Bootstrap dark fallback is ugly. Suggest: force the portal to `color_scheme=light` for `share=True` users, or add a `prefers-color-scheme: light` meta tag. Document the choice during Phase 1.
---
*Mockup references in `.superpowers/brainstorm/1800-1778997036/content/` — keep until implementation is verified.*