docs(portal): session handoff + sub-A IA spec + plan

Captures everything the next Claude session needs to pick up cold:
  - Live module versions on entech (portal 19.0.3.7.0, jobs/reports
    versions, all 5 tests green)
  - What shipped this session (24+ commits, summarised by area)
  - Sub-A (IA + sidebar) brainstorm decisions locked, spec written,
    plan ready to execute (11 tasks, 4 phases)
  - What's deferred (sub-B multi-user, sub-C search, drafts, real
    statements, RMA portal, top-recurring-parts) and WHY — so next
    session doesn't re-litigate
  - Gotchas hit + fixed this session that aren't obvious from code
  - Deploy recipe (file copy + module upgrade + cache bust) used 20+
    times this session

CLAUDE.md's Recent Session Handoff section now points to the new
handoff doc; the previous handoff is kept as 'superseded but kept
for context' below it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-17 13:21:21 -04:00
parent 26fe41e7d4
commit 0593b70354
4 changed files with 1809 additions and 2 deletions

View File

@@ -3,9 +3,13 @@
## Project
Fusion Plating is a multi-module Odoo 19 ERP for electroless nickel plating and metal finishing shops. Built by Nexa Systems for EN Technologies (the client). Replaces Steelhead Software.
## Recent Session Handoff — 2026-05-17
## Recent Session Handoff — 2026-05-17 (Portal Redesign + Sub-A IA approved)
> **For the next Claude session.** All changes below are LIVE on entech (db `admin` on LXC 111 / pve-worker5). Local repo has uncommitted changes against base commit `9ebf89b`; run `git status` to see them. All durable conventions added during this session are folded into the Critical Rules below (rules 6a, 14, 14a, 14b) — read those FIRST before changing reports / stickers / signatures / SO line tax fields.
> **For the next Claude session.** Portal dashboard + jobs + detail + configurator are LIVE on entech at `fusion_plating_portal 19.0.3.7.0`. Sub-A (Portal IA + Sidebar) is brainstormed, spec'd, planned, NOT yet executed — pick up there. Full handoff (live state, decisions, gotchas, how-to-deploy, what's deferred): **[`docs/superpowers/handoffs/2026-05-17-portal-redesign-handoff.md`](docs/superpowers/handoffs/2026-05-17-portal-redesign-handoff.md)**. Approved plan ready to execute: **[`docs/superpowers/plans/2026-05-17-portal-ia-sidebar-plan.md`](docs/superpowers/plans/2026-05-17-portal-ia-sidebar-plan.md)** (11 tasks, 4 phases). Don't re-brainstorm; just execute.
### Previous handoff (pre-portal redesign — superseded but kept for context)
> All changes below are LIVE on entech (db `admin` on LXC 111 / pve-worker5). Local repo has uncommitted changes against base commit `9ebf89b`; run `git status` to see them. All durable conventions added during this session are folded into the Critical Rules below (rules 6a, 14, 14a, 14b) — read those FIRST before changing reports / stickers / signatures / SO line tax fields.
### Shipped this session

View File

@@ -0,0 +1,153 @@
# Portal Redesign — Session Handoff (2026-05-17)
> **Read this first.** This session ran long; the next session picks up here. Everything below is intentionally short. Authoritative details live in the linked spec / plan files.
## TL;DR
Customer-portal redesign across two long sessions. Dashboard + jobs + detail page + configurator are LIVE on entech. The next step (sidebar nav + page audit + Account Summary view) has an APPROVED PLAN ready to execute — do not re-brainstorm, just execute.
**Immediately actionable:** execute [`docs/superpowers/plans/2026-05-17-portal-ia-sidebar-plan.md`](../plans/2026-05-17-portal-ia-sidebar-plan.md) via `superpowers:subagent-driven-development` or `superpowers:executing-plans`. User was offered both at handoff time and chose subagent-driven (preferred). 11 tasks across 4 phases.
## Live state on entech (2026-05-17)
| Module | Version live | Notes |
|---|---|---|
| `fusion_plating_portal` | `19.0.3.7.0` | Dashboard, job cards, configurator, detail page, doc downloads, repeat order, animations — all shipped |
| `fusion_plating_jobs` | `19.0.10.8.0` + write-hook + create-init | fp.job → fp.portal.job state-sync hook on write, initial state derive on create |
| `fusion_plating_reports` | `19.0.11.15.0` | Customer Acceptance / Authorized Representative signature blocks removed from `report_fp_sale_portrait/landscape` |
| All 5 portal unit tests green | | `--test-tags=fp_portal` |
Branch: `main`. Local repo is many commits ahead of `origin/main`; user has not been asked to push (per system-prompt safety default). Run `git log --oneline origin/main..HEAD` at session start to see what's outstanding.
## What shipped this session (high-level)
1. **Dashboard rebuild**`/my/home` → jobs-forward layout (KPI tiles → Active Work Orders hero → 5 secondary panels). Welcome line summarises status in plain words. EN Plating teal brand palette with gradient CTAs.
2. **Job card upgrade** — shared `fp_portal_job_card` macro (used by `/my/home` + `/my/jobs`). Wrap div + inner anchor + sibling actions footer (4 doc download chips + Repeat Order POST form). Part info + ship-to address pulled inline. Pulse animation on the active step circle + matching detail-page timeline dot.
3. **Detail page** — V2 stepper + V3 timestamps + 5-group document panel (From You / Specifications / Work Order / Quality / Shipping). Sales Order Confirmation, Work Order Detail, CoC, Packing Slip all sudo-render from the FP custom reports. Hero shows part + ship-to.
4. **Configurator fixes**`/my/configurator/coating` 500 fixed (`fp.coating.config``fusion.plating.process.type`). Manual measurements hidden in step 1. Split single-file upload into Drawing (PDF) + 3D Model.
5. **Sale report cleanup** — Customer Acceptance / Authorized Representative signature block removed.
6. **Misc**`/my` route added, button sizing normalised, hover-underline suppressed globally, sidebar of legacy stuff redirected, dashboard expanded to 5 panels (Quote Requests + Purchase Orders added).
24+ commits this session, all on `main`. Browse `git log --oneline -30` for the full sweep.
## What's queued for execution
**Sub-A (Portal IA + Sidebar):** plan ready, not yet executed. Brainstorm decisions baked in:
| Decision | Choice |
|---|---|
| Sidebar shape | **B** — Dashboard top, then grouped Activity / Documents / Account sections |
| Account Summary tabs | 3 (Invoices / Credit Memos / Statements) + Open Balance pill in header |
| Statements V1 | Placeholder card ("Coming soon") — real statement generation deferred |
| Legacy URL redirects | `/my/fp_invoices``/my/account_summary`; `/my/purchase_orders``/my/orders` (Odoo default); `/my/quote_requests/new` GET → `/my/configurator/new` |
| Future Users / Search slots | Omit from V1 (no "coming soon" placeholders); add when sub-B/sub-C ship |
Spec: [`docs/superpowers/specs/2026-05-17-portal-ia-sidebar-design.md`](../specs/2026-05-17-portal-ia-sidebar-design.md)
## What's deferred (do NOT re-litigate in next session)
These were explicitly scoped OUT during brainstorming. Open new brainstorm sessions for each when their turn comes:
- **Sub-B Multi-user account management** — invite teammates, role per user, per-action ACLs. Will add a Users item under the Account section of the sidebar.
- **Sub-C Portal search** — global search across jobs / quotes / invoices / certs. Search input slot above Dashboard in the sidebar.
- **Saved drafts (RFQ)** — user mentioned wanting drafts during configurator. Three scoping options proposed (minimal/medium/big); awaiting user direction. Not part of sub-A.
- **Real Statements generation** — account.followup integration OR cron-precomputed monthly PDFs. Decide during sub-A Phase 3 implementation or defer to its own follow-up.
- **Top Recurring Parts / Favorites / SerialNumber Lookup** — competitor-style features; deferred until customer demand confirmed.
- **RMA customer portal** — sub-12 RMA backend exists; portal exposure is its own sub-project.
## Gotchas that bit us this session
Future Claude will hit these too unless documented. Most are already inline in CLAUDE.md or MEMORY.md. Worth a re-skim before touching the portal:
1. **`fp.coating.config` is retired** (Sub-11 cleanup). Use `fusion.plating.process.type` as the customer-facing coating taxonomy. Multiple `*.py` files still reference the dead model in COMMENTS — don't pattern-match from those.
2. **Portal users can't read `fp.job` directly.** Controllers that return `fp.portal.job` records to a template MUST `sudo()` the search if the template traverses `job.x_fc_job_id`. Same pattern is already used for `sale.order`, `account.move`, `stock.picking`. Domain still filters to commercial partner tree.
3. **`sale_pdf_quote_builder` gates on `report_name == 'sale.report_saleorder'`** (already in MEMORY.md). For customer-facing SO PDFs on the portal, render the FP custom `fusion_plating_reports.report_fp_sale_portrait` instead, and use a dedicated portal route that sudo-renders so the QWeb template can walk into `fp.part.catalog` etc.
4. **Forms inside anchors is invalid HTML.** When making a whole card clickable AND embedding a Repeat-Order form inside, use a wrap div + inner anchor (main click target) + sibling actions footer (form lives here). Don't nest `<form>` inside `<a>`.
5. **Groups list indexing drift.** `_fp_group_documents` builds the docs panel by appending to `groups[N]`. If you reorder the initial list or insert a new group mid-helper, every `groups[N]` reference shifts. The code has an inline warning comment now; respect it.
6. **Per-stage timestamps are NULL on records created before the write hook deployed.** `_fp_get_stage_timeline` has a Date-fallback chain (received_date → received_at; actual_ship_date → shipped_at) plus linear interpolation for middle stages. Records created post-hook get real datetimes from the `fp.job.write()` mirror.
7. **Stepper SCSS — `.o_fp_step_line` MUST stay nested inside `.o_fp_stepper`** (inline comment in the SCSS warns about this). When `flex:1` isn't applied because the rule slipped outside the parent, circles cluster on the left of the row.
8. **Stepper labels align via absolute positioning per-unit** (not as a separate flex container). Wider labels like "Inspected" overflow equally to both sides of their circle's centre. Don't revert to the dual-container approach.
9. **`fp.portal.job` state-sync map** uses `_FP_JOB_STATE_TO_PORTAL_STATE` in `fusion_plating_jobs/models/fp_job.py`. `on_hold` and `cancelled` deliberately NOT mirrored to the customer-facing state. Manager decision what to surface.
## How to deploy (entech LXC 111 on pve-worker5)
Same recipe used 20+ times this session. Per file:
```bash
cat K:/Github/Odoo-Modules/fusion_plating/<module>/<path> | \
ssh pve-worker5 "pct exec 111 -- bash -c 'cat > /mnt/extra-addons/custom/<module>/<path>'"
```
Then upgrade module + run tests:
```bash
ssh pve-worker5 "pct exec 111 -- bash -c 'systemctl stop odoo && \
su - odoo -s /bin/bash -c \"/usr/bin/odoo -c /etc/odoo/odoo.conf -d admin \
-u fusion_plating_portal --test-tags=fp_portal --stop-after-init 2>&1 | tail -25\" && \
systemctl start odoo'"
```
Bust asset cache for SCSS/JS changes:
```bash
ssh pve-worker5 "pct exec 111 -- su - postgres -c \
\"psql -d admin -c \\\"DELETE FROM ir_attachment WHERE url LIKE '/web/assets/%';\\\"\""
```
Service status / version check:
```bash
ssh pve-worker5 "pct exec 111 -- bash -c 'systemctl is-active odoo'"
ssh pve-worker5 "pct exec 111 -- su - postgres -c \
\"psql -d admin -t -c \\\"SELECT latest_version FROM ir_module_module \
WHERE name='fusion_plating_portal';\\\"\""
```
## How to start the next session
1. Open Claude Code in `K:\Github\Odoo-Modules\fusion-plating` (or `K:\Github\Odoo-Modules\fusion_plating` — both work, the dash dir has only 3 modules but it's the active working dir for the user's terminal).
2. First message: "Resume the portal sub-A IA work — execute the approved plan from this session."
3. New session should:
- Read `CLAUDE.md` (auto-loaded) — the "Recent Session Handoff" section at the top points back to this file
- Read this handoff doc
- Read the plan: `docs/superpowers/plans/2026-05-17-portal-ia-sidebar-plan.md`
- Invoke `superpowers:subagent-driven-development` (or `executing-plans` for inline mode)
- Execute the 11 tasks across 4 phases
4. Optional but useful: re-run the existing test suite first to confirm starting from green: `--test-tags=fp_portal --stop-after-init`.
## Brainstorm artifacts
Visual companion mockups for this session live in `.superpowers/brainstorm/*/content/` (gitignored). Useful for visual comparison if needed:
- `design-direction.html` — Modern SaaS / Corporate / Industrial picker
- `saas-refinements.html` — V1/V2/V3 card variants
- `dashboard-layout.html` — 6-card grid vs jobs-forward
- `job-detail.html`, `branded-job-detail.html` — detail page mockups
- `branded-dashboard.html` — final brand-applied dashboard
- `sidebar-structure.html` — flat vs grouped vs hybrid (chose grouped)
Brainstorm server idles out after 30 min. Restart command:
```bash
"C:/Users/gur_p/.claude/plugins/cache/claude-plugins-official/superpowers/5.0.7/skills/brainstorming/scripts/start-server.sh" \
--project-dir "K:/Github/Odoo-Modules/fusion_plating"
```
(Run in background; URL appears in `.superpowers/brainstorm/*/state/server-info`.)
## Critical files modified this session
If the next session needs to read context fast:
- `fusion_plating_portal/controllers/portal.py` — most changes here
- `fusion_plating_portal/controllers/portal_configurator.py` — coating model swap + dual upload
- `fusion_plating_portal/views/fp_portal_dashboard.xml` — jobs-forward layout
- `fusion_plating_portal/views/fp_portal_templates.xml` — jobs list + detail rewrites
- `fusion_plating_portal/views/fp_portal_macros.xml``fp_portal_job_card`, `fp_portal_stepper`, `fp_portal_status_badge`, `fp_portal_doc_chip`, `fp_portal_doc_group`
- `fusion_plating_portal/static/src/scss/_fp_portal_tokens.scss` — brand tokens
- `fusion_plating_portal/static/src/scss/fp_portal_*.scss` — 7 partials (buttons, badges, cards, stepper, timeline, dashboard, legacy catch-all)
- `fusion_plating_portal/models/fp_portal_job.py` — per-stage Datetime fields + write/create snapshot hooks
- `fusion_plating_jobs/models/fp_job.py` — fp.job → fp.portal.job state-sync hook
- `fusion_plating_portal/tests/test_portal_dashboard.py` — 5 tests, all green
## What user feedback is still outstanding
Nothing concrete waiting on user. Last thing the user did was approve the plan and say "create a handsoff script so i start a new session" — i.e., they want to pause here. Next session resumes execution.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,235 @@
# Customer Portal — Information Architecture + Sidebar Nav
**Module**: `fusion_plating_portal` (touches `portal.portal_layout` inherit)
**Date**: 2026-05-17
**Status**: Design locked, awaiting implementation plan
**Surface**: every `/my/*` page on `https://enplating.com`
**Sub-project**: A (of A/B/C); B = multi-user, C = portal search — deferred to separate brainstorms.
## Problem
The post-2026-05-17 portal redesign gave us a credible dashboard + jobs-detail page, but the navigation between pages is still "scroll the standard Odoo portal cards and hope you find the right entry point." Eight distinct customer surfaces (`/my/home`, `/my/jobs`, `/my/quote_requests`, `/my/configurator`, `/my/purchase_orders`, `/my/fp_invoices`, `/my/deliveries`, `/my/certifications`) and there's no persistent way to move between them. The customer's competitor screenshot (Mobility Specialties Inc / Drive Medical) shows the right pattern: a sticky left sidebar that lists every section, current page highlighted, secondary "Company Account" group at the bottom.
This spec restructures the portal around that sidebar pattern, audits the existing pages (replace thin custom pages with Odoo defaults where the default is better), and adds one missing page — a consolidated **Account Summary** with tabbed Invoices / Credit Memos / Statements + an Open Balance pill — that the existing thin `/my/fp_invoices` page doesn't deliver.
## User stories
1. **As a returning customer**, I want a persistent sidebar showing every section so I can jump between Quote Requests and Work Orders without going through the dashboard.
2. **As an accounting clerk**, when I open the portal I want a single Account Summary page with Open Balance + filterable invoices + credit memos + downloadable monthly statements — without hunting through three separate menu items.
3. **As any customer**, I want the active page visually marked so I always know where I am.
4. **As a mobile user**, the sidebar should collapse to a hamburger so the page content gets the screen.
## Locked design decisions (from brainstorming 2026-05-17)
| Decision | Choice | Why |
|---|---|---|
| Decomposition | A first (IA), B (multi-user) + C (search) deferred to separate brainstorms | Sidebar + pages are the foundation; building search before pages exist or a Users tab before the sidebar shape is locked would be rework. |
| Sidebar shape | Option B — Dashboard at top, then 3 grouped sections (Activity / Documents / Account) | 10 items needs grouping to scan; matches how the redesigned dashboard already groups (KPI tiles → jobs hero → secondary panels). |
| Account Summary tabs | 3 tabs: Invoices · Credit Memos · Statements, plus an "Open Balance: $X" pill in the page header | Mirrors competitor; one summary number front-of-mind, three drilldowns. |
| Future placeholders | NEITHER "Users (soon)" nor a search input shown in the sidebar today | Empty placeholders add visual noise; ship them when sub-B / sub-C land. |
| Sidebar persistence | Sticky on scroll; visible on every `/my/*` page (including Odoo defaults via `portal.portal_layout` inherit); sub-pages keep their parent highlighted | Industry standard. Consistency means the customer never loses their place. |
| Mobile collapse | Below 768px the sidebar collapses to a hamburger button in the page header; opens as a slide-in drawer | Standard portal pattern, no content rearrangement needed. |
| Single quote-creation path | `/my/quote_requests/new` redirects to `/my/configurator/new` | Two paths to the same outcome confuses customers; the configurator is the more complete flow. |
| Sign Out placement | Bottom of sidebar, separated by a hairline border | Matches competitor; gets sign-out off the page chrome. |
## Scope
**IN SCOPE — pages restructured / new:**
- `/my/home` — keep dashboard, gets sidebar
- `/my/jobs` — keep list, gets sidebar
- `/my/jobs/<id>` — keep detail, gets sidebar (highlight parent)
- `/my/quote_requests` — keep list, gets sidebar
- `/my/quote_requests/<id>` — keep detail, gets sidebar
- `/my/quote_requests/new`**REDIRECT** to `/my/configurator/new`
- `/my/configurator` — keep landing, gets sidebar
- `/my/configurator/new`, `.../coating`, `.../estimate` — keep wizard, gets sidebar
- `/my/purchase_orders`**REDIRECT** to Odoo default `/my/orders`; controller + template deleted
- `/my/fp_invoices`**REDIRECT** to new `/my/account_summary`; controller + template deleted
- `/my/account_summary`**NEW** tabbed page (this spec)
- `/my/deliveries` — keep, gets sidebar
- `/my/certifications` — keep, gets sidebar
- `/my/account` — Odoo default, gets sidebar
- `/my/orders/<id>` — Odoo default, gets sidebar
**IN SCOPE — chrome:**
- New `fp_portal_shell` template that inherits `portal.portal_layout` and wraps every `o_portal` page body with a sticky 240px sidebar on the left.
- Sidebar SCSS partial (`fp_portal_sidebar.scss`) — brand-teal active state, mint gradient highlight, hairline section dividers.
- Mobile breakpoint: hamburger toggle + slide-in drawer below 768px.
- All Odoo default portal pages (`/my/account`, `/my/orders`, `/my/orders/<id>`, `/my/invoices/<id>`, etc.) get the sidebar via the `portal.portal_layout` inherit — zero per-page edits.
**OUT OF SCOPE — deferred to other sub-projects:**
- Multi-user account management (sub-project B): Users tab in sidebar, invitation flow, per-action ACLs.
- Portal search (sub-project C): global search input above Dashboard, search-result page.
- Saved drafts (separate brainstorm — needs its own scoping).
- Top Recurring Parts / Favorites / SerialNumber Lookup (defer until customer demand confirmed).
- RMA customer portal (sub-project after RMA backend ships).
**OUT OF SCOPE — explicit non-goals:**
- Top-bar navigation, breadcrumbs redesign, footer changes — none of these are part of A.
- Restyling Odoo default `/my/account` or `/my/orders/<id>` page BODIES. We give them the sidebar via the layout inherit, but their content stays Odoo-standard.
## Architecture
### Sidebar shell
```
fusion_plating_portal/views/fp_portal_shell.xml
└── inherits portal.portal_layout
└── injects .o_fp_portal_shell wrapper that contains:
├── <aside class="o_fp_portal_sidebar"> (sticky, 240px)
│ └── partner header + 4 sections + sign-out
└── <main class="o_fp_portal_main"> (existing portal body)
```
Per Odoo's `portal.portal_layout` extension pattern, we inherit and use `<xpath expr="//div[@id='wrap']" position="replace">` (or `position="inside"` on the right anchor — TBD during implementation) to wrap the existing layout. The sidebar is a single shared template (`fp_portal_sidebar`) rendered above the existing portal page body.
Active-state marker: each sidebar `<a>` reads the current `page_name` from the template context (already set by every FP route — `fp_dashboard`, `fp_jobs`, etc.) and applies `o_fp_sidebar_active` when matched. Falls back to URL prefix match for Odoo default pages (`/my/orders` → Purchase Orders highlighted, `/my/account` → Profile highlighted).
### Sidebar items (final list)
```
ACME AEROSPACE <-- partner.commercial_partner_id.name
─────────────────────────────────────────
🏠 Dashboard /my/home
ACTIVITY
📄 Quote Requests /my/quote_requests
+ Get a Quote /my/configurator
🛒 Purchase Orders /my/orders (Odoo)
⚙️ Work Orders /my/jobs
DOCUMENTS
📑 Certifications /my/certifications
📦 Packing Slips /my/deliveries
💰 Account Summary /my/account_summary (NEW)
ACCOUNT
👤 Profile /my/account (Odoo)
─────────────────────────────────────────
↪ Sign Out /web/session/logout
```
Section headers (`ACTIVITY` / `DOCUMENTS` / `ACCOUNT`) are display-only, not links. The whole list is rendered from a single Python data structure in the template context (passed by a small helper on `FpCustomerPortal`), so adding the future Users / Drafts / Search items is a one-line addition.
### Account Summary page
**URL**: `/my/account_summary`
**Controller method**: `portal_account_summary(self, **kw)` on `FpCustomerPortal`
**Template**: `portal_my_account_summary` in `fp_portal_account_summary.xml` (new file)
**Page structure:**
```
[ Account Summary ] Open Balance: $4,820.00
─────────────────────────────────────────────────────────────────────────────
[ Invoices ] [ Credit Memos ] [ Statements ]
─────────────────────────────────────────────────────────────────────────────
Showing: Open · Closed · All [Search PO or #__________ ] [Sort ▾]
─────────────────────────────────────────────────────────────────────────────
# | Status | Posted On | PO # | Due Date | Balance | View PDF
─────────────────────────────────────────────────────────────────────────────
0035180274 | ● Open | May 13, 2026 | 53469 | Jun 12, 2026 | C$305.73 | View PDF
...
◀ Prev 1 2 3 4 5 Next ▶
```
**Data sources (per tab):**
| Tab | Model + domain | Notes |
|---|---|---|
| Invoices | `account.move` where `move_type='out_invoice'`, `partner_id child_of commercial`, `state='posted'` | Today's `/my/fp_invoices` already does this; relocated here. |
| Credit Memos | `account.move` where `move_type='out_refund'`, `partner_id child_of commercial`, `state='posted'` | Surfaces RMA credits when sub-12 RMA flow runs. Tab shows empty state with "No credits yet" when partner has none. |
| Statements | Generated PDF per month via `account.followup` or a custom QWeb cron — **decided during implementation; preferred = use account.followup report directly per-customer with date filter** | Tab UI: month picker + Download button. |
**Open Balance pill** = sum of `amount_residual` across all open `out_invoice` records (regardless of tab). Computed in the controller, shown in the page header.
**Search box** = case-insensitive substring match on `name` (invoice number) OR `ref` (customer PO). Server-side filter, not JS.
**Sort options:** Newest → Oldest (default), Oldest → Newest, Largest balance, Smallest balance.
**Filter pills:** `Open` (residual > 0) / `Closed` (residual = 0) / `All`.
**Pagination:** 10 per page, server-side via `portal_pager`.
Invoice detail = existing Odoo `/my/invoices/<id>` page (no rewrite); the table's "View PDF" link goes to `/my/invoices/<id>?report_type=pdf&download=true` per Odoo's standard portal pattern.
### Mobile behavior
```scss
@media (max-width: 768px) {
.o_fp_portal_sidebar {
transform: translateX(-100%);
transition: transform 0.2s ease;
position: fixed; top: 0; left: 0; bottom: 0;
z-index: 1040;
}
.o_fp_portal_sidebar.o_fp_open {
transform: translateX(0);
}
.o_fp_portal_hamburger { display: inline-flex; }
}
@media (min-width: 769px) {
.o_fp_portal_hamburger { display: none; }
}
```
Hamburger button lives in the page header (above the main content). Click toggles `o_fp_open` on the sidebar via 5-line vanilla JS (no framework). Backdrop click closes the drawer.
## Files
**NEW:**
- `fusion_plating_portal/views/fp_portal_shell.xml``portal.portal_layout` inherit + sidebar markup
- `fusion_plating_portal/views/fp_portal_account_summary.xml``portal_my_account_summary` template
- `fusion_plating_portal/static/src/scss/fp_portal_sidebar.scss` — sidebar styling (sticky, active state, sections, mobile drawer)
- `fusion_plating_portal/static/src/js/fp_portal_sidebar.js` — hamburger toggle (vanilla JS, no OWL)
**MODIFY:**
- `fusion_plating_portal/controllers/portal.py`
- NEW route `portal_account_summary` at `/my/account_summary`
- DELETE route `portal_my_fp_invoices` (the thin invoice list at `/my/fp_invoices`)
- REPLACE route `portal_my_purchase_orders` body with `return request.redirect('/my/orders')`
- REPLACE the GET handler for `portal_my_quote_request_new` with `return request.redirect('/my/configurator/new')` (or delete entirely if the configurator already exposes the equivalent form)
- NEW helper `_fp_sidebar_items(self)` returning the sidebar data structure (consumed by `fp_portal_sidebar` template via inherited `_prepare_portal_layout_values`)
- Extend `_prepare_portal_layout_values()` to inject `fp_sidebar_items` + `fp_partner_display_name` into every portal page's context so the sidebar renders correctly on Odoo default pages too.
- `fusion_plating_portal/views/fp_portal_templates.xml` — delete `portal_my_fp_invoices` template body (route is gone). Remaining templates (jobs list, jobs detail, deliveries, certifications) get the sidebar **for free** via the `portal.portal_layout` inherit; no per-template edits.
- `fusion_plating_portal/views/fp_portal_dashboard.xml` — dashboard template gets the sidebar via the layout inherit; no edits needed.
- `fusion_plating_portal/__manifest__.py` — version bump + register the new XML/SCSS/JS files. Add `fp_portal_shell.xml` near the TOP of the `data` list (loaded before any template that uses sidebar variables).
**DELETE (or stub):**
- The `portal_my_fp_invoices` template body and the `portal_my_purchase_orders` template body. Routes redirected, templates unused. Keep route stubs so existing bookmarks 302 cleanly instead of 404.
## Migration / backward compatibility
| Old URL | New behavior |
|---|---|
| `/my/fp_invoices` | 302 → `/my/account_summary` |
| `/my/purchase_orders` | 302 → `/my/orders` |
| `/my/quote_requests/new` | 302 → `/my/configurator/new` |
No DB migration. No template namespace changes that break inherits. The page audit removes routes from the controller and templates from the data list; Odoo's module-upgrade cycle handles the ORM-side cleanup.
## Open items to verify during implementation
1. **`portal.portal_layout` extension pattern** — confirm the cleanest xpath for injecting the sidebar wrapper without breaking Odoo's existing portal CSS (`#wrap`, `.o_portal`). Likely `position="before"` on the main content slot. If unclear, fall back to inheriting at the `website.layout` level and writing a wholly new shell template.
2. **Statements tab data source** — decide between (a) inline render of `account.followup` report per requested month, vs (b) precomputed monthly statement PDFs stored as attachments. Latter is simpler for V1; cron generates last-month statement on the 1st.
3. **Mobile hamburger placement** — header anchor: a small button at the top-left of the main content area (above the page title) on mobile only. Confirm during Phase 4 visual pass.
4. **Page-name → active-item mapping** — most FP routes set a clean `page_name` (e.g., `fp_jobs`, `fp_dashboard`). Odoo defaults don't; we'll match by URL prefix (`/my/orders``purchase_orders` item). One-helper `_fp_resolve_active_sidebar_item(url, page_name)` keeps the mapping in one place.
5. **Account Summary Statements scope** — confirm whether monthly statements are something EN Plating currently generates, or if this is a new artifact we need to define a template for. If the latter, that's a separate small spec.
## What ships in a "done" state
- Every `/my/*` page (FP + Odoo default) shows the new sidebar.
- Active page is visually marked.
- Sidebar collapses to hamburger drawer below 768px.
- `/my/account_summary` exists with 3 tabs, Open Balance pill, search + filter pills + sort + pagination.
- 3 legacy URLs (`/my/fp_invoices`, `/my/purchase_orders`, `/my/quote_requests/new`) 302-redirect to their new homes.
- Unit tests cover the new account_summary controller (3 tabs return the right counts, filter/search produce the right subset, Open Balance sums residuals correctly).
- Module version bumped, deployed to entech, all 5 existing portal tests still green plus 3+ new tests for Account Summary.
---
*Sub-projects B (multi-user) and C (portal search) are tracked separately — they'll consume the sidebar slot conventions (insertion under ACCOUNT for Users, above DASHBOARD for the search input) defined here.*