25 Commits

Author SHA1 Message Date
gsinghpal
091f98e1f9 changes 2026-05-18 22:33:23 -04:00
gsinghpal
25f568f225 fix(portal): correct terminology — Sales Orders everywhere (revert Purchase Orders rename)
The customer's Purchase Order is the doc they send US — a separate
artifact, often a PDF attachment on the quote. What lives in our
system is the Sales Order we create in response. Labeling the SO
list as "Purchase Orders" in the customer portal was a wrong-side
mapping.

Reverts and renames in this commit:

- Sidebar item label: "Purchase Orders" → "Sales Orders" (key stays
  odoo_orders; URL still /my/orders). _FP_SIDEBAR_LAYOUT.

- Dashboard KPI tile: "Active POs" → "Active Sales Orders". Link
  hint: "View POs →" → "View orders →". Link target updated to the
  current /my/orders (the legacy /my/purchase_orders still redirects
  but we point at the canonical URL now).

- Dashboard panel: "Recent Purchase Orders" → "Recent Sales Orders".
  Empty state: "No purchase orders yet." → "No sales orders yet."
  View-all link target updated to /my/orders.

- Dashboard docs entries strip: "Purchase Orders" docs entry title
  → "Sales Orders"; URL → /my/orders.

- Removed the three Odoo template rename inherits from
  fp_sale_order_portal.xml (sale.portal_my_home_menu_sale,
  sale.portal_my_orders, sale.sale_order_portal_content). With those
  gone the stock templates emit Odoo's native "Sales Order(s)" and
  "Your Orders" wording on the list page header, breadcrumb, and
  detail page <h2> — which is now the correct terminology.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 00:30:55 -04:00
gsinghpal
4e54ecc32f fix(portal): sidebar values + Purchase Order naming on /my/orders detail
1. Odoo's portal_order_page route calls _get_page_view_values which
   doesn't touch _prepare_portal_layout_values, so our sidebar
   context (fp_sidebar_items, fp_partner_display_name) was missing
   on every Odoo detail page (SO, invoice, delivery, quote). Override
   _get_page_view_values to setdefault our two keys into the values
   dict — non-clobbering, covers every detail route.

2. Rename "Sales Order(s)" / "Your Orders" to "Purchase Order(s)" on
   the customer portal so the wording matches the sidebar item and
   the customer's perspective (they purchase from us). Inherits in
   fp_sale_order_portal.xml replace the relevant text nodes in
   sale.portal_my_home_menu_sale / sale.portal_my_orders /
   sale.sale_order_portal_content.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 00:22:36 -04:00
gsinghpal
b27f68b8d5 feat(portal): real-time search + filter pills on 4 FP list pages
Replaces the tab nav / portal.portal_searchbar on the 4 FP list
pages with the new fp_portal_list_controls macro (filter pills +
search input + sort dropdown) and drops portal_pager in favour of
client-side filtering of up to 500 records:

- Quote Requests (/my/quote_requests):
    filters: All / Active / Converted / Declined
    sorts:   Newest / Reference / Status
    extra search fields: contact_name, contact_email, line.part_number,
                         line.description, line.product_id.default_code

- Work Orders (/my/jobs, cards layout):
    filters: All / Active / Ready to Ship / Complete
    sorts:   Newest / Reference / Status
    extra search fields per card: part_catalog.part_number, part_catalog.name,
                                  sale_order.name, sale_order.client_order_ref,
                                  job.notes

- Certifications (/my/certifications):
    no filters (all rows are terminal CoC jobs)
    sorts:   Newest / Reference
    extra search fields: part name, processes (already in card text)

- Packing Slips / Deliveries (/my/deliveries):
    no filters (all rows are state=done)
    sorts:   Newest / Reference
    adds a visible Origin column (sale order ref) so customers can
    locate a slip by the SO it came from

Each route accepts ?filter_state=... and ?sortby=... query params,
returns up to 500 records, and passes result_total + clipped to the
template so the macro can render a "showing latest 500 of N" notice
when the cap is hit.

Hidden <td class="d-none"> cells inside each row carry extra terms
that aren't displayed but are matched by the JS textContent scan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 00:06:18 -04:00
gsinghpal
3a520564a7 fix(portal): account summary 500 — open_balance can't use t-field
t-field requires a record.field_name access pattern. open_balance is a
Python float (returned by _fp_account_summary_open_balance), not a
recordset attribute, so QWeb threw AssertionError at render time and
the page 500'd. Format the value in the controller via tools.formatLang
and render it as a plain string with t-out instead.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 23:36:10 -04:00
gsinghpal
04862e8a28 fix(portal): inject sidebar layout values into every FP portal render
Every FP portal route built `values = {...}` from scratch and called
`request.render(...)` directly, bypassing `_prepare_portal_layout_values`.
Our new `fp_sidebar_items` and `fp_partner_display_name` keys live in
that hook, so the sidebar template's `t-foreach` was a no-op on every
custom page (`/my/home`, `/my/jobs`, `/my/account_summary`, etc.) — the
sidebar rendered with the "My Account" fallback header and only the
Sign Out footer link visible.

Fix: each FP render now does
    values = self._prepare_portal_layout_values()
    values.update({...route-specific values...})
This puts the layout values in first (so `fp_sidebar_items` and
`fp_partner_display_name` always present), and the route's own
update wins on `page_name` and other collisions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 14:39:53 -04:00
gsinghpal
b92a396934 feat(portal): account_summary controller + 3 unit tests
New /my/account_summary route. Splits posted account.move into
Invoices (out_invoice) / Credit Memos (out_refund) / Statements
(V1 placeholder). Open Balance helper sums amount_residual across
open invoices for the partner's commercial tree.

Search filters name OR ref (customer PO). Sort options: date desc/asc,
amount desc/asc. Filter pills: open / closed / all.

Tests cover the tab partitioning, the open-balance sum, and the
search behaviour. Helpers use commercial_partner.env so they work
in both HTTP context and unit tests without requiring request.env.
Test scaffolding uses fp_from_so_invoice=True context flag and
invoice_payment_term_id to satisfy the fusion_plating_jobs and
fusion_plating_invoicing create/post gates.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 14:13:48 -04:00
gsinghpal
8225061dfa feat(portal): redirect 3 legacy URLs to consolidated homes (Sub-A IA)
- /my/fp_invoices       -> /my/account_summary
- /my/purchase_orders   -> /my/orders (Odoo default)
- /my/quote_requests/new (GET) -> /my/configurator/new
  (POST handler preserved for back-compat with the existing RFQ form
  button; will be removed after the form is fully retired)

Thin templates deleted: portal_my_fp_invoices, portal_my_purchase_orders.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 14:01:32 -04:00
gsinghpal
a99f9aa5ee feat(portal): _fp_sidebar_items helper + layout-values inject
Drives the sidebar from a single Python data structure
(_FP_SIDEBAR_LAYOUT). Active state resolved by page_name lookup OR
URL-prefix match (so Odoo default pages like /my/orders and
/my/account light up correctly). _prepare_portal_layout_values
extends super() so existing counter injection (fp_quote_request_count
etc.) keeps firing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 13:46:23 -04:00
gsinghpal
26fe41e7d4 fix(portal): sudo portal job queries so template traversal works for customers
Portal users have read access to fp.portal.job but NOT to fp.job.
The new job-card macro traverses job.x_fc_job_id -> fp.job to surface
part info, sale_order, ship-to address — that raised AccessError for
real customers (admins were fine due to inherited groups).

Adding .sudo() to the three Job queries in home(), portal_my_jobs(),
and the certifications panel mirror lookup. Domain still filters to
the customer's commercial partner tree, so sudo doesn't widen
visibility — it just lets the template walk past the portal-job
boundary to the privileged backend models.

Same pattern is already used in the same file for sale.order,
account.move, and stock.picking queries.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 12:39:26 -04:00
gsinghpal
2802fcf738 feat(portal): fix configurator 500, hide manual measurements, upgrade job card
1. Configurator step 2/3 500 fix: fp.coating.config was retired
   (Sub-11) but the controller still queried it -> KeyError. Swapped
   to fusion.plating.process.type (the real coating taxonomy on entech:
   Hard Chrome, EN Low Phos, Type I Anodize, etc). Step 2 template
   dropped dead refs (coat.process_type_id / spec_reference / thickness_*
   / certification_level), now shows code + process_family + description.
   Pricing helper relaxed: filters out rules keyed to the dead model
   and silently returns {'available': False} -> template shows 'Quote
   will be priced by EN Plating' instead of fake numbers.

2. Configurator step 1: manual measurements hidden per customer
   feedback. Length/Width/Height/Surface Area are kept as hidden 0s so
   the rest of the flow doesn't error; backend trimesh still auto-calcs
   surface area silently when STL is uploaded. Single file input split
   into two: separate Drawing (PDF) + 3D Model (STL/STP/STEP/IGES)
   uploads so customer can send both. Multi-upload session shape:
   attachment_ids list. Submit handler re-keys ALL uploads onto the
   new quote_request.

3. Job card upgraded: new fp_portal_job_card macro shared by dashboard
   + jobs list. Renders wrap div containing main anchor (whole card
   clickable -> detail page) + sibling actions footer (4 doc download
   quick-buttons: SO / WO / CoC / Packing + Repeat Order form).
   Forms-inside-anchor is invalid HTML so the footer lives as a
   sibling, not a child. Card now shows part name+number and ship-to
   address pulled inline from job.x_fc_job_id.sale_order_id chain.
   Same data also added to detail-page hero for consistency.

Version bump: 19.0.3.6.0 -> 19.0.3.7.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 12:34:06 -04:00
gsinghpal
153b980e2b fix(portal): correct group indices after adding work_order to docs panel
Regression I introduced when adding the WO Detail group: the
groups.insert(2, wo_group) ran BEFORE the SPECIFICATIONS / QUALITY /
SHIPPING appends, so groups[2] shifted from 'quality' to 'work_order'
mid-helper. Result: the CoC got appended to the work_order group's
docs and shipping doc went into quality. Test caught it.

Restructured to declare the 5-group list up front in display order
and use stable indices throughout (0=from_you, 1=specs, 2=work_order,
3=quality, 4=shipping). Added a code comment warning future editors
that reordering means updating every groups[N] reference.

Test updated to expect 5 groups, asserting both 'work_order' and
'quality' keys are present + pending state in each.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 12:12:20 -04:00
gsinghpal
6cad69cb86 feat(portal): customer PO/uploads + WO Detail PDF + hover-underline fix
1. From-You group now surfaces ANY ir.attachment attached to the
   linked sale.order (sudo'd) so customer-uploaded PO + drawings
   appear automatically. Each shows file name + upload date + size,
   downloads via /web/content/<id>?download=true. Falls through to
   the Sales Order Confirmation entry as before.

2. New 'Work Order' document group between Specifications and Quality,
   surfacing the EN Plating WO Detail PDF via new route
   /my/jobs/<id>/wo_detail. Sudo'd render of report_fp_job_wo_detail_
   template so the template can read backend fp.job + recipe nodes.
   Placeholder rendered when there's no linked backend job yet.

3. Hover underline gone: Bootstrap Reboot puts
   text-decoration: underline on a:hover for every anchor, which read
   as buggy on our flat chips / pill buttons / dashboard cards. Added
   a catch-all selector list in fp_portal_buttons.scss that pins
   text-decoration: none across hover/focus/active for every brand
   element. Hover signal lives in color + shadow only.

Version bump: 19.0.3.5.0 -> 19.0.3.6.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 12:06:41 -04:00
gsinghpal
49013c64fb feat(portal): pulse animation, repeat-order button, 5-panel dashboard
1. Pulse animation on the active step indicator:
   - New @keyframes fp-pulse-teal / fp-pulse-amber in stepper.scss
   - Applied to .o_fp_step_active / _warn and .o_fp_timeline_active
     .o_fp_timeline_dot so dashboard stepper + detail-page timeline
     breathe in sync. 1.8s ease-in-out, ring grows 4px -> 9px and
     fades 20% -> 6% opacity. Two color variants so QC (warn) keeps
     its amber meaning.
   - prefers-reduced-motion: reduce kills the animation for users
     who opted out.

2. Repeat Order button on /my/jobs/<id> detail page:
   - New POST /my/jobs/<id>/repeat route that creates a draft
     fusion.plating.quote.request seeded with the user's contact +
     the job's quantity, posts a chatter link back to the original
     job, redirects to the new RFQ for review/submit.
   - Button placed in the detail footer next to 'Back to all jobs',
     CSRF-protected via the form's csrf_token hidden field.

3. Dashboard expanded from 3 secondary panels to 5 (Recent Quote
   Requests + Recent Purchase Orders added) so every previously-
   designed customer page is reachable from /my/home.
   - Auto-fit grid: 3+2 / 2+2+1 / single column depending on width.
   - Every panel header gets a 'View all ->' link to its list page
     (Quote Requests / POs / Certs / Deliveries / Invoices).
   - Empty-state for Quote Requests gets an inline 'Get a quote ->'
     CTA so first-time customers know where to start.

Version bump: 19.0.3.4.0 -> 19.0.3.5.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 03:56:53 -04:00
gsinghpal
ba6f39375a fix(portal): full timestamp format + interpolated middle stages
Two changes to _fp_get_stage_timeline:

1. Format: 'May 16, 2026 \xb7 9:14 AM' (full year + space + uppercase
   AM/PM) instead of 'may 16 \xb7 9:14a'. Matches the mockup the
   user approved. Date-only render kicks in when the timestamp has
   no time component (backfilled/interpolated midnight values), so
   we don't show fake '12:00 AM' next to a date we only know to the
   day.

2. Linear interpolation: records that pre-date Task 16's per-stage
   Datetime hook had empty middle-stage timestamps. The new fallback
   spreads done stages evenly between received_at (or received_date)
   and now() so old records show a plausible progression instead of
   gap-toothed empty rows. Records created post-hook hit the real
   captured values and never reach the interpolation branch.

Helper imports datetime + time at module level since we need
datetime.combine for Date->Datetime conversion in the fallback chain.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 03:49:54 -04:00
gsinghpal
cbed74e5eb fix(portal): fallback to existing Date fields when stage Datetime is null
Records created before Task 16 (per-stage Datetime fields + write
snapshot hook) have NULL for received_at/shipped_at/etc. SQL backfill
copies received_date -> received_at; this commit adds a runtime
fallback so if any record slips through (manual edits, future
imports) the timeline still surfaces what's available.

Also render date-only ('May 16, 2026') when the timestamp has no
time component, so backfilled-from-Date records don't show the
misleading 'may 16 · 12:00a' fake time.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 03:43:59 -04:00
gsinghpal
669ba0fd8a fix(portal): dedicated /my/jobs/<id>/so_confirmation route with sudo render
The FP sale report template (report_fp_sale_portrait) walks into
fp.part.catalog records, which portal users don't have ACL on -
they'd hit 'You are not allowed to access Fusion Plating - Part
Catalog' when rendering. Standard /report/pdf/ route runs as the
authed user, so the template traversal fails.

Mirror the portal_download_coc pattern: gate on _document_check_access
for the portal job (customer can only ever reach their own data),
then render the report via ir.actions.report.sudo()._render_qweb_pdf
so the QWeb template traversal bypasses ACL. Return the PDF as an
attachment with a friendly filename.

Updates _fp_group_documents to point the From-You SO Confirmation
link at this new route instead of /report/pdf/ directly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 03:31:25 -04:00
gsinghpal
8e172132e7 fix(portal): use FP custom sale report for SO Confirmation download
Standard sale.report_saleorder hit the sale_pdf_quote_builder
header/footer merge bug (CLAUDE.md MEMORY.md gotcha) and produced
garbled PDFs on FP-customised sale orders. Switching to
fusion_plating_reports.report_fp_sale_portrait which is the
customer-facing FP template and bypasses the merge gate. Added
?download=true so the browser saves the PDF instead of trying to
embed it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 03:24:56 -04:00
gsinghpal
28220f0732 fix(portal): 5 hotfixes - /my route, button sizing, clickable cards, state sync, SO doc
1. /my now serves the FP dashboard (stock Odoo home was leaking
   through because parent route declared ['/my', '/my/home'] but my
   override only listed /my/home).
2. Button padding bumped to .5rem 1rem + font 1rem so o_fp_btn matches
   Odoo's standard Bootstrap button rhythm. Ghost button drops its
   custom padding override.
3. .o_fp_job_card on /my/home + /my/jobs is now an <a> wrapping the
   whole card area — full row is the click target, not just the WO
   number. Inner <a> on job.name dropped to avoid nested anchors;
   focus-visible outline added for keyboard nav.
4. fp.job.write() now mirrors state -> fp.portal.job.state via new
   _FP_JOB_STATE_TO_PORTAL_STATE map (confirmed->received,
   in_progress->in_progress, done->ready_to_ship). Fixes the bug where
   completed backend jobs left the portal stuck on 'in_progress'.
   'on_hold' and 'cancelled' intentionally not mirrored — manager
   choice what to surface.
5. Sales Order Confirmation now surfaces in the 'From You' group on
   the job detail page, pulled via job.x_fc_job_id.sale_order_id ->
   /report/pdf/sale.report_saleorder/<id>. Falls back to the upload
   placeholder when no SO is linked.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 03:13:00 -04:00
gsinghpal
6cf826268b feat(portal): rewrite /my/jobs/<id> detail page with timeline + doc panel
Two-column grid: vertical timeline (5 stages with per-stage timestamps)
on the left, grouped document panel (4 categories) on the right. Hero
header carries WO ref + part / qty / ETA / tracking facts.

Controller adds stage_timeline, doc_groups, and timeline_spine_pct
to the render context. Spine fill = done + half-credit for the
active stage (so the spine visually leads the eye to where the work
is happening).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 02:51:33 -04:00
gsinghpal
4da123c2d3 feat(portal): _fp_group_documents helper for detail-page doc panel
V1 surfaces only the fields directly on fp.portal.job (CoC + packing
list). Other 2 groups (From You, Specifications) render placeholder
rows. V2 will wire in sale.order linking for full doc surfacing.

Also adds _fp_size_label helper for friendly file-size strings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 02:49:18 -04:00
gsinghpal
8c6718e352 feat(portal): _fp_get_stage_timeline helper for detail-page timeline
Builds a 5-entry list (label, status, started_at, time_label, notes)
ordered by stage. Labels match the dashboard stepper exactly
(Received/Inspected/Plating/QC/Shipped) so the two surfaces tell
the same story. Inspected and Plating share in_progress_started_at
since state in_progress means both transitions happened.

Time labels use lowercase am/pm matching the mockup typography.
'complete' state correctly shows all 5 stages as done (caught by
new test).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 02:48:42 -04:00
gsinghpal
c2590a99ff feat(portal): welcome-line summary counts on /my/home + tests
Adds active_job_count, awaiting_review_count, ready_to_ship_count
to the dashboard context. Tests verify partition is correct across
the fp.portal.job and fp.quote.request state machines.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 02:41:17 -04:00
gsinghpal
655b767127 fix(portal): override stock /my/home with FP rich dashboard
The custom dashboard at fusion_plating_portal was rendering a 6-card
view at /my/home, but a method-name mismatch left the parent
portal.CustomerPortal.home() route active instead. Rename the
override to home() so Python MRO does the override naturally, and
add CLAUDE.md Critical Rule 16 documenting the gotcha so future
controller-override work doesn't trip on it.

Version bump: 19.0.2.2.0 -> 19.0.2.3.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 02:35:52 -04:00
gsinghpal
7c7ef06057 folder rename 2026-04-16 20:53:53 -04:00