Files
Odoo-Modules/fusion_plating/docs/superpowers/specs/2026-05-17-portal-ia-sidebar-design.md
gsinghpal 0593b70354 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>
2026-05-17 13:21:21 -04:00

17 KiB

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/newREDIRECT to /my/configurator/new
  • /my/configurator — keep landing, gets sidebar
  • /my/configurator/new, .../coating, .../estimate — keep wizard, gets sidebar
  • /my/purchase_ordersREDIRECT to Odoo default /my/orders; controller + template deleted
  • /my/fp_invoicesREDIRECT to new /my/account_summary; controller + template deleted
  • /my/account_summaryNEW 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

@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.xmlportal.portal_layout inherit + sidebar markup
  • fusion_plating_portal/views/fp_portal_account_summary.xmlportal_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/orderspurchase_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.