4161f04b0f45497ae18e887cc95df6b9ac277480
67 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
4161f04b0f |
feat(plating): hard-required fields on WO start — operator + bath + tank
User audit caught: in the workforce E2E run we had no idea which bath /
which tank ran the job. For aerospace traceability that's a deal-
breaker. Add a validation gate on mrp.workorder.button_start so
operators can't tap START without the data the shop floor MUST capture.
**Three new pieces on mrp.workorder:**
1. `_fp_is_wet_process()` — best-effort "does this WO involve a
chemistry bath?" check. Three signals in priority order:
a. A bath is already linked → definitely wet
b. The workcenter's FP work-centre supports a wet process family
(plating, pre/post-treatment, strip, passivation)
c. WO name contains a wet-process keyword (plat, nickel, chrome,
anodiz, zinc, etch, clean, rinse, strip, passivat, electroless…)
The keyword fallback is needed because most existing recipes have
no process_type_id set on their operation nodes.
2. `_fp_check_required_fields_before_start()` — runs before the
existing certification check. Rules:
• Every WO needs an assigned operator (x_fc_assigned_user_id).
Without it, productivity records can't be attributed and the
proficiency tracker has no employee to credit.
• Wet WOs additionally need x_fc_bath_id + x_fc_tank_id. So we
know exactly which chemistry bath ran the job and which physical
tank it sat in.
Raises a clear UserError listing the missing fields if any.
3. `x_fc_requires_bath` (compute, non-stored) — surfaces the wet check
to the form view so bath + tank fields render with `required=`.
**View changes:**
- `x_fc_assigned_user_id` is now `required="1"` on the form
- `x_fc_bath_id` + `x_fc_tank_id` use `required="x_fc_requires_bath"`
→ red asterisk only when the WO is actually wet
**Simulator updates** (scripts/fp_e2e_workforce.py):
- Hannah now explicitly assigns bath + tank to wet WOs during planning,
AND pre-issues operator certifications for the bath's process type
(real shop manager workflow).
- Two negative tests added that PROVE the gates fire:
• Test 1: strip the operator → button_start raises "missing Assigned Operator"
• Test 2: strip bath/tank on a wet WO → button_start raises "missing Bath/Tank"
**Final E2E:** 42 PASS / 2 WARN / 0 FAIL out of 44 checks.
Both remaining WARNs (bake-window auto-create, first-piece gate) are
expected behaviour — those are coating-driven and the test coating
intentionally doesn't trigger them.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
bbbd222b89 |
feat(plating): close 2 workflow gaps surfaced by workforce E2E simulation
Built a comprehensive simulator (scripts/fp_e2e_workforce.py) that
role-plays 10 employees driving an order quote → invoice using real
operator timers (button_start / button_finish with elapsed time.sleep).
Initial run: 31 PASS / 2 WARN / 0 FAIL exposed two gaps that would
hurt a real shop:
**Gap 1 — Thickness readings never reached the CoC**
The Fischerscope readings inspectors take during post-plate inspection
had no path to the CoC. The cert came out empty, useless for AS9100
or aerospace audits.
Fixes:
- New tablet endpoint `/fp/shopfloor/log_thickness_reading` so the
inspector can record one reading at a time during the inspection WO
(auto-numbers, defaults the operator, supports microscope image).
- mrp_production._fp_mark_done_post_actions now bulk-links any
orphan thickness readings (those with production_id=mo.id but no
certificate_id) to the freshly-created CoC. So inspectors can log
during inspection AND the cert PDF picks them up automatically.
**Gap 2 — Operator queue leaked other people's work + simulator missed it**
fusion.plating.operator.queue.build_for_user pulled EVERY ready /
in-progress WO regardless of assignment. Tom would see John's masking
WO in his "Up Next" list — bad for aerospace traceability where you
want strict per-operator accountability.
Fix: build_for_user now filters MRP WOs by
`(x_fc_assigned_user_id == user_id OR x_fc_assigned_user_id == False)`.
Operators see their own assigned tasks first, plus any unassigned
tasks anyone can grab. Other operators' assigned WOs no longer leak
through.
Also caught: simulator was using wrong field name on the queue model.
Fixed and added a "queue isolation" check that verifies no operator
sees another operator's assigned WOs.
After fixes: **39 PASS / 2 WARN / 0 FAIL** (out of 41 checks).
Remaining WARNs are both expected behaviour:
- bake-window auto-create: this coating doesn't require_bake_relief
(the recipe has an inline Oven step instead)
- first-piece gate: same — coating-driven, only fires when needed
Areas validated end-to-end:
- quote → SO with PO# carried into client_order_ref
- SO confirm → MO + portal job auto-created
- receiving qty prefill + accept
- 9 WOs generated from recipe + assigned to specific operators
- All 9 WOs ran with real elapsed timers + 17 productivity records
across 4 distinct operators
- MO done triggers CoC auto-issue with 5 thickness readings linked,
319 KB rich PDF, customer-slug filename
- Delivery auto-created with prefilled date + driver + CoC link
- Delivery delivered, 2 chain-of-custody entries
- Invoice posted (NOT auto-paid)
- All 5 customer notifications fired (so_confirmed +
parts_received + mo_complete + shipped + invoice_posted) with
correct attachments
- Portal job → complete, SO workflow_stage → invoicing
- Chemistry log persisted, operator proficiency tracked
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
fa82ce17dd |
feat(reports): sequence-sort the Print dropdown so FP reports are #1
Odoo 19's `ir.actions.actions._get_bindings` returns the print-menu bindings via `ORDER BY a.id` (insertion order) and only sequence-sorts the `action`-type bindings — `report`-type bindings are returned in raw SQL order. Result: FP reports installed after Odoo's stock ones appear at the BOTTOM of the dropdown, even when they're the customer-facing primary report (e.g. Timesheets above Quotation on sale.order). Two changes in fusion_plating_reports/models/ir_actions_report.py: 1. **Add `sequence` (Integer, default 100) to ir.actions.report** — gives every report a sortable knob. 2. **Override `ir.actions.actions._get_bindings`** to also sort the `report` slice by `(sequence, name.lower())`. super() returns the cached frozendict; we rebuild with the sorted reports. Then set sequences in fp_hide_default_reports.xml (lower = top): | Model | seq 10 (#1) | seq 15 (#2) | seq 20+ | |-----------------|--------------------------|--------------------------|-----------------------| | sale.order | FP Quotation Portrait | FP Quotation Landscape | FP Job Traveller (20) | | account.move | FP Invoice Portrait | FP Invoice Landscape | | | stock.picking | FP Packing Slip Portrait | FP Packing Slip Landscape| | | mrp.production | FP Job Traveller Portrait| FP Job Traveller Landscape| FP WO Margin (20) | | account.payment | FP Receipt Portrait | FP Receipt Landscape | | | fp.delivery | FP BoL Portrait | FP BoL Landscape | | | portal.job | FP CoC Portrait | FP CoC Landscape | | | fp.certificate | FP CoC English | FP CoC Français | | Odoo defaults stay at sequence 100 (default) → always at bottom. Verified on entech: sale.order print menu now shows Quotation Portrait → Quotation Landscape → Job Traveller × 2 → PRO-FORMA → Timesheets. Same pattern across all touched models. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
9a1ee4b369 |
feat(reports): hide Odoo's default PDFs where FP ships a branded one
Users were seeing both Odoo's stock PDFs and FP's branded equivalents in the Print dropdown side-by-side, and accidentally sending the wrong (unbranded, missing PO# / job ref / plating fields) PDF to customers. Add fp_hide_default_reports.xml that drops the Print-menu binding on: | Model | Hidden | FP replacement | |-----------------|-------------------------------------------------------------|---------------------------------| | sale.order | sale.action_report_saleorder | action_report_fp_sale_* | | sale.order | sale_pdf_quote_builder.action_report_saleorder_raw | action_report_fp_sale_* | | account.move | account.account_invoices | action_report_fp_invoice_* | | account.move | account.account_invoices_without_payment | action_report_fp_invoice_* | | stock.picking | stock.action_report_delivery | action_report_fp_packing_slip_* | | mrp.production | mrp.action_report_production_order | action_report_fp_job_traveller_*| | account.payment | account.action_report_payment_receipt | action_report_fp_receipt_* | Mechanism: set binding_model_id=False + binding_type=action — removes from the Print dropdown but leaves the report record + template intact. Fully reversible from Settings → Technical → Reports if anyone needs the stock PDF back. Intentionally NOT touched: - sale.action_report_pro_forma_invoice (no FP pro-forma yet) - account.action_account_original_vendor_bill (vendor bills, internal) - stock.action_report_picking / picking_packages / return_label_report (internal warehouse ops, not customer-facing) - mrp.action_report_finished_product / mrp.label_manufacture_template (production labels — ZPL, not customer-facing) - sale_timesheet.* (timesheet integration) Added sale_pdf_quote_builder to depends so the data file always finds that record when applied (it ships in entech's repackaged enterprise bundle and was already installed there). Verified on entech: re-running the print-menu audit shows zero stock Odoo customer-facing PDFs left where FP has an equivalent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
5994cec11b |
fix(plating): chatter action toolbar invisible in dark mode
The floating message-action toolbar (reaction / reply / star / link icons) appearing on hover renders white-icons-on-white-background in dark mode — Odoo's own dark.scss sets the icon hover color to white but never gives the toolbar itself a dark background. Result: the icons vanish entirely in dark mode. Add fp_chatter_dark.scss that branches at compile time on $o-webclient-color-scheme == dark (Odoo 19 compiles every SCSS file into both web.assets_backend with `bright` AND web.assets_web_dark with `dark`) and gives the toolbar: - Solid dark background (#2b2f33 fallback, var(--o-component-bgcolor)) - Subtle 1px white-alpha border + drop shadow so it floats nicely - Icon color rgba(255,255,255,.78) at full opacity (not 35%) - Brighter hover state with a subtle bg highlight Light bundle output is empty (the @if branch doesn't fire), so the light theme is untouched. Verified: dark bundle includes our rule with #2b2f33 marker present. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
eed4dc8a78 |
fix(plating): chatter HTML rendering + workflow stage banner UX
Two fixes from a single SO walkthrough screenshot:
**1. "Current stage" banner**
- Was placed `inside sheet` so it rendered at the BOTTOM of the form
where users miss it. Moved to `before form/header` (same xpath
pattern as the Account Hold banner) — now it's the first thing
visible above the SO header.
- Was still showing "Shipped — awaiting invoice" after the invoice
was posted because `_compute_workflow_stage` only advanced to
`complete` when shipped + ALL paid; an unpaid posted invoice left
the SO stuck on `shipped`. Added an `invoicing` branch: shipped +
has_posted_invoice → invoicing. Banner invisible-list now also
includes `invoicing` and `paid`, so the banner only shows for
in-progress steps.
**2. Chatter messages rendering raw HTML tags as text**
Odoo 19 escapes any string passed to `message_post(body=...)`
unless wrapped in `markupsafe.Markup`. We had ~10 places posting
HTML (`<a href>`, `<b>`, `<br/>`, `<code>`, `<pre>`) that all
showed up as `<a href=...>` literal text in the chatter.
Wrapped each one with `Markup(_(...))` so the tags render. Files
touched:
- fusion_plating_bridge_mrp/models/sale_order.py
(auto-MO failure code block, "Draft MO created" link,
"Job assigned to <b>" message)
- fusion_plating_bridge_mrp/models/mrp_production.py
("Recipe steps" pre/br block on each WO)
- fusion_plating_bridge_mrp/models/fp_proficiency.py
(operator promotion announcement)
- fusion_plating_configurator/models/fp_quote_configurator.py
(SO link, 3D model attached, drawing attached, save to catalog)
- fusion_plating_configurator/models/fp_part_catalog.py
(3D/drawing change tracking + propagation to linked quotes)
- fusion_plating_portal/models/fp_quote_request.py
(RFQ → SO link)
- fusion_plating_quality/models/fp_quality_hold.py
(hold status change)
- fusion_plating_shopfloor/controllers/manager_controller.py
(worker / tank / manager-takeover assignments)
Verified on entech: SO S00038 stage now reads `invoicing` (banner
hidden), and a freshly posted message shows `<a href>` and `<b>`
as actual link + bold instead of escaped text.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
cb9baa03ad |
fix(reports): collapse sig-row to one bordered table — kill duplicate borders
User reported "multiple unwanted vertical lines in the boxes" on the portrait BoL. Pixel analysis confirmed it: previous design had 3 separate `<div class="sig-box">` each with its own 1px border, with a 4-8px gap between adjacent boxes — visually those adjacent borders read as a doubled / "duplicate" line between cells. Fix: replace 3-box layout with a single `<table class="bordered sig-table">` containing 3 td cells. With border-collapse: collapse, adjacent cells share their border — so the row now shows 4 vertical lines (1 outer left + 2 internal dividers + 1 outer right) instead of 6 close-together border lines. - Dropped `.sig-box` class entirely (no per-box border anymore) - Added `.sig-table` + `.sig-cell` with explicit 1px borders so the layout works without depending on `.bordered` class inheritance - Applied to both portrait + landscape variants - Landscape sig-row was still using the OLD Bootstrap row+col-4 layout (never got replaced earlier) — also migrated to the new table layout Verified: page count unchanged (portrait 1, landscape 1), all labels and content present, structure clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
ed72ed496b |
fix(reports): compact landscape BoL so it fits on one page
Last fix kept signatures intact but the landscape BoL still overflowed to a second page (with the signature row pushed entirely to page 2). The real ask was for the landscape variant to fit on one page since landscape has plenty of vertical room. Aggressive landscape compaction: - Body font 11pt → 10pt, td font 10 → 9.5pt, th font 10 → 9pt - Cell padding 8/10px → 4/8px - Table margin-bottom 12px → 6px - h2 title 26pt → 18pt with tighter top/bottom margins - BoL # subtitle 14pt → 11pt - Shipper/consignee row height 120 → 70px - highlight-box (cert) padding 10px → 6/10, font 10 → 9pt - sig-box padding 12 → 8/10px - sig-line height 70 → 45px Verified with pypdf: landscape BoL now renders as exactly 1 page with cert + all 3 signature labels + company info all present. 137 KB clean PDF. Portrait variant left untouched (it already fit on one page and the bigger title is appropriate for portrait). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
b26aa45068 |
fix(reports): use table layout for BoL signature row, drop flex on sig-box
Last fix added page-break-inside: avoid but the boxes still split because wkhtmltopdf 0.12 ignores that rule inside flex containers, and BOTH the .sig-box (display: flex) AND the Bootstrap .row wrapper were flex. Replace both with non-flex equivalents: - .sig-box: dropped `display: flex` + `flex-direction: column` + `justify-content: flex-end`. Layout now uses padding + a fixed- height .sig-line block + the muted label below. Same visual result, but a plain block element so wkhtmltopdf honors the page-break rule. - Replaced `<div class="row">` + 3 `<div class="col-4">` (Bootstrap flex grid) with a `<table class="sig-table">` containing one row of three 33% tds. wkhtmltopdf treats table rows as atomic for page-breaking, so the whole signature row now stays on a single page. Verified with pypdf: page 1 has the cert statement, page 2 has all three signature labels together — no more sliced boxes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
b16486f66b |
fix(reports): keep BoL signature row intact across page breaks
Landscape BoL was splitting the signature row down the middle — boxes half on page 1, half on page 2. Two complementary fixes: 1. **Per-element rule**: added `page-break-inside: avoid` + `break-inside: avoid` to `.sig-box` (both portrait + landscape styles) so an individual signature box can never split across pages. 2. **Wrapper rule**: introduced `.fp-keep-together` utility + wrapped the BoL's certification statement + signature row in it, so the whole "sign here" block moves to the next page as one unit if it doesn't fit. Also applied `page-break-inside: avoid` to `table tr` so cargo lines don't split mid-row either. Lives in shared `report_base_styles.xml` so any FP template that opts into `.fp-keep-together` benefits automatically. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
7ad7481195 |
fix(bol): bigger title, shipper info, uniform headers, cargo qty, taller signatures
Five fixes applied to the Bill of Lading and (where relevant) all
report templates:
1. **Bigger title + BoL #** — portrait now uses h2 24pt (was h4 16pt),
landscape h2 26pt; BoL # ticker is 13/14pt instead of body size.
2. **Shipper info missing** — root cause: `_fp_build_delivery_vals`
was creating deliveries without `company_id`, so the BoL's
`<span t-field="doc.company_id.name"/>` rendered empty. Two fixes:
- Hook now sets `company_id = mo.company_id.id or env.company.id`.
- Template falls back defensively to `env.company` when
`doc.company_id` is empty (covers any legacy delivery that
somehow slips through without it).
- Backfilled 14 existing deliveries via SQL on entech.
3. **Uniform header backgrounds** — replaced mixed `info-header`
(gray) + default-th (brand black) headers with a single
`fp-header-primary` (brand black) across all sub-tables for a
consistent look.
4. **Cargo description alignment + missing column** — added a QTY
column (matches landscape variant), pulled from the linked MO
via job_ref → mrp.production.product_qty. Added `.fp-cell-mid`
utility class with `vertical-align: middle !important;` and
applied it to every cargo + info cell so values sit centred
instead of jammed against the top border.
5. **Signature box too short** — bumped `.sig-box` from 70 → 110 px
(portrait) / 130 px (landscape), `.sig-line` from 28 → 60/70 px,
added flex layout so the label sits at the bottom and signers
have a real space to write in. Lives in the shared
`report_base_styles.xml` so EVERY FP template benefits, not just
the BoL.
Verified: BoL portrait renders cleanly at 140 KB with full shipper
block + uniform headers + middle-aligned cargo cells.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
16a4bdddf3 |
fix(reports): BoL PDF — t-field needs dotted path, branch on delivery_address_id
The Bill of Lading template assigned a temp variable `<t t-set="dest" t-value="doc.delivery_address_id or doc.partner_id"/>` and then tried `<div t-field="dest" .../>`. Odoo 19 QWeb asserts t-field must be `record.field_name` (have a dot) — the temp variable form fails compilation and the report renders as a multi-page "Oops! Something went wrong" PDF stuffed with the traceback. Fix: branch with `t-if`/`t-else` and call `t-field="doc.delivery_address_id"` or `t-field="doc.partner_id"` directly. Same pattern in both header and second-page-header sections (lines 49/235). Verified: BoL render goes from 39 KB error page to 138 KB clean PDF. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
d351a2577b |
chore(receiving): port received_qty auto-prefill from live entech to main
The auto-prefill logic that fills received_qty from expected_qty on fp.receiving create was committed to the entech LXC but never made it back to main. Verified by a full quote→delivery→invoice walkthrough (scripts/fp_e2e_human.py) — receiving step now passes. Also adds the human-walkthrough E2E script that exercises every step: RFQ → quote → SO confirm → MO + portal job auto-create → receiving prefill → recipe → WO execution → MO done → CoC cert (rich PDF, no thickness duplicate) → delivery prefill + lifecycle → invoice (posted, not auto-paid) → notification log audit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
633427bcf8 |
fix(plating): CoC + invoice PDFs render full content
Three reported PDF bugs from the customer-facing email package: 1. Invoice body was empty — Odoo 19 sets display_type='product' on regular invoice/SO lines (was empty string in 18.0). Both report_fp_invoice.xml and report_fp_sale.xml only matched `not line.display_type`, so every product line was skipped. Fixed both portrait + landscape variants to also match display_type == 'product'. 2. CoC PDF was a bare 30 KB header — _fp_generate_cert_pdf was rendering action_report_coc, which is bound to portal_job and has minimal content. Rewrote to use the rich fp.certificate-bound report (action_report_coc_en / action_report_coc_fr based on cert.partner_id.lang) and slugged the filename to CoC-<Customer>-<CertName>.pdf so the email attachment reads nicely instead of CERT-00123.pdf. 3. Thickness cert was an exact duplicate of the CoC — the CoC template already embeds thickness readings. Skip thickness cert creation entirely when the customer also wants CoC; only create a standalone thickness cert when the customer opted out of CoC. Also: dispatcher in fp_notification_template now prefers portal_job.coc_attachment_id (the rich one we just generated) and falls back to rendering action_report_coc_en against fp.certificate by partner.lang — never the bare portal-job report. Versions bumped: bridge_mrp 19.0.6.0.0, notifications 19.0.4.0.0, reports 19.0.4.0.0. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
167c423bf5 |
feat(plating): close 5 end-to-end automation gaps
E2E test (quote → SO → MO → WOs → ship → invoice → payment) ran clean but flagged five gaps where the operator was filling in data the system already knew. Closes all five. #1 SO CONFIRM → AUTO-CREATE DRAFT MO (was a workflow blocker) bridge_mrp/sale_order.py: action_confirm() override + new _fp_auto_create_mo helper. Resolves the manufactured product from the configurator's part-catalog → coating-config → FP-WIDGET fallback; resolves the recipe from coating_config.recipe_id → part_catalog.recipe_id → first installed recipe. Idempotent: skips if any MO already exists for the SO. Errors are caught and chatter-posted so SO confirm never fails because of an MO glitch. #2 QUOTE PO → client_order_ref ON SO (one-line fix) configurator/fp_quote_configurator.py: action_create_quotation now copies po_number_preliminary into Odoo's standard client_order_ref alongside the existing custom x_fc_po_number. Portal pages, native reports, and integrations all read the standard field; no reason both shouldn't carry the same PO#. #3 MO DONE → AUTO-RENDER CoC + THICKNESS PDFs bridge_mrp/mrp_production.py button_mark_done now calls a new _fp_generate_cert_pdf helper after creating each fp.certificate. Renders fusion_plating_reports.action_report_coc to PDF, stores as ir.attachment, links to cert.attachment_id, AND cross-links to portal_job.coc_attachment_id + delivery.coc_attachment_id so the customer portal and the shipping email both find it without an extra step. Thickness report falls back to the CoC layout (which embeds thickness data) until a dedicated report ships. Errors are logged but never block MO completion. #4 RECEIVING received_qty PREFILL receiving/fp_receiving.py: create() prefills received_qty from expected_qty on draft. Operator only types when the count is wrong (the rare case). Field carrier_tracking already exists, so #4's 'no inbound tracking field' from the gap report turned out to be a false alarm. #5 DELIVERY scheduled_date + driver PREFILL bridge_mrp/mrp_production.py: new _fp_build_delivery_vals helper sets scheduled_date from the portal job's target_ship_date (or now+2 business days as a sane fallback) and auto-picks assigned_driver_id from clocked-in employees tagged is_driver (falls back to any active driver if the shift is empty). The outbound tracking_ref deliberately stays empty — that's the carrier's number, paste it in once UPS/FedEx accepts the package. Module bumps: configurator 19.0.5.0.0, bridge_mrp 19.0.5.0.0, receiving 19.0.2.0.0. Verified on entech: re-ran the E2E test against a fresh quote. Quote → SO populated client_order_ref, SO confirm auto-created MO, receiving prefilled received_qty=50, MO done generated CERT-00018.pdf and linked it to portal job + delivery, delivery's scheduled_date prefilled to 2026-04-29, full pipeline ended with portal job state 'complete'. The remaining 'gaps' in the static report are script artefacts (e.g. it flags 'no inbound tracking field' but the field exists; flags 'no driver auto-pick' but the demo data has zero drivers tagged is_driver=True). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
b288b9614b |
fix(configurator): rebalance two-column layout — no more empty right side
After the right-side preview panel was retired, the left column had Customer & Part / RFQ-PO / Geometry / Delivery & Fees stacked while the right side ran out of content after Rush Order — almost half the form was dead air. Reshuffled the groups so every row has peers. Old layout (4 rows, mostly half-empty): Customer & Part | RFQ / PO / Quantity & Options Geometry | Auto from 3D (often empty) Delivery & Fees | (empty) Calculated Price | Final Price New layout (every row balanced): Customer & Part | RFQ / PO Documents Quantity & Options | Auto from 3D (visible only with part catalog) Geometry | Delivery & Fees Calculated Price | Final Price Quantity & Options moved out of the RFQ/PO group (where it was shoehorned in via a <separator>) into its own group on the left of row 2. Auto from 3D becomes its right-side peer when present, or shrinks gracefully when absent. Delivery & Fees moves up one row to pair with Geometry instead of sitting alone. Net effect: form fits more above the fold and the estimator's eye doesn't have to chase fields across uneven columns. Bumped fusion_plating_configurator to 19.0.4.0.0. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
f3e01a342b |
feat(configurator): replace inline previews with smart button + Preview links
The Quote Configurator form devoted nearly half its width to a sticky
3D viewer + drawing PDF preview. That panel meant the actual fields
(geometry, dimensions, pricing) had to fight for real estate. Replaced
the inline previews with two affordances that take zero layout space:
1. New '3D Model' smart button at the top of the form, next to the
existing 'Drawings' button. Click to open the existing
fp_3d_viewer_open client action — same fullscreen modal the
'Full Screen' button used to launch from the side panel.
2. Inline 'Preview' link (eye icon) sits next to the 3D Model and
Drawing fields in the Customer & Part group. Click to open the
same modal preview as the smart button. Two paths to the same
content — power users grab the field-adjacent link mid-edit;
visual-thinkers grab the smart button up top.
Layout collapses to a single full-width column. The .o_fp_cfg_layout
wrapper is kept (display:block) so we have a stable hook in case a
side panel returns later for a different purpose. Old SCSS dance with
:has() selectors to fake-collapse the grid is gone.
Bumped fusion_plating_configurator to 19.0.3.0.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
4065c6891b |
feat(plant-overview): live debounced search + bigger search bar
The search bar required Enter to fire, which felt clunky on a shop
floor where managers expect cards to filter as they type. Switched
to a 200ms-debounced live search — fast enough to feel instant on
keystrokes, slow enough to skip the network call when someone is
mid-word.
Search bar visual weight bumped:
- Width 260px → 380px (320px on iPad, full width on phones)
- Height 48px → 52px
- Font-size base → md, weight medium
- Search icon nudged 14px → 16px from the edge with a 1.05rem size
- Placeholder uses the lighter $fp-ink-faint so the input feels
inviting rather than already-filled
Behaviour:
- Type → cards filter after 200ms of no input
- Enter → fires immediately (skips debounce) for power users
- Escape → clears the search (new shortcut)
- Clear button → unchanged
Bumped shopfloor to 19.0.14.0.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
9b3b674197 |
fix(shopfloor): suppress Odoo .o_kanban_record chrome inside fp kanbans
The Bake Window + First-Piece Gate cards looked rounded on their own, but Odoo's default .o_kanban_record wrapper painted its own background + border + box-shadow with sharper corners than our inner .o_fp_kcard — visible as a faint square ghost behind every card, especially obvious on the missed_window state where the red wash on the inner card didn't extend to the wrapper edges. Added a .o_fp_bw_kanban / .o_fp_fpg_kanban scoped override that zeroes the wrapper's background, border, box-shadow and padding, letting only our card surface render. Also drops the kanban group container's tinted bg for the same reason. Bumped shopfloor to 19.0.13.0.0. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
cad2f937cf |
feat(shopfloor): rebuild bake/gate kanban templates with .o_fp_kcard
Companion to commit |
||
|
|
f7f500f87a |
feat(shopfloor): match Bake Windows + First-Piece Gates kanbans to Plant Overview
The two standalone menu pages (Bake Windows, First-Piece Gates) were still on the older o_fp_card design from a pre-Plant-Overview pass — visually drifted from the polished kanban-pattern cards we settled on for Plant Overview. Pulling them onto the same design language without rewriting them as OWL client actions (the 'Option A' from chat). What changed ============ New shared SCSS — fp_kanbans.scss --------------------------------- Defines .o_fp_kcard as the base kanban card surface. Mirrors the Plant Overview .o_fp_po_card recipe: white $fp-card surface, 1px $fp-border, $fp-radius-md corners, soft $fp-elev-1 shadow, hover lift, 4px state stripe via ::before clipped by overflow:hidden. Sub-elements (title, sub, metric, meta line, footer chip) get their own classes so per-page tweaks stay surgical. Page-scoped wrappers (.o_fp_bw_kanban, .o_fp_fpg_kanban) carry the state/result → stripe colour mapping plus exception-state tints (missed_window + fail get a soft danger wash so the card stands out in a sea of normal ones). Bake Window kanban ------------------ Rebuilt template — title (window name), part_ref subtitle, big time-remaining metric (the operator's primary cue), meta line for lot/customer/qty, footer with oven badge + state chip. data-state attribute drives the stripe colour: awaiting_bake → warning bake_in_progress → info baked → success missed_window → danger + soft red wash scrapped → muted + dimmed First-Piece Gate kanban ----------------------- Rebuilt template — title (gate name), part_ref subtitle, bath + customer meta, inspector + first_piece_produced timestamp, footer with result chip and an optional 'Released' badge when the lot has been signed off. data-result attribute drives the stripe colour: pending → warning pass → success fail → danger + soft red wash Shopfloor manifest bumped to 19.0.12.0.0 and the new SCSS is registered in web.assets_backend after manager_dashboard.scss so the design tokens it references are already in scope. Plant Overview's existing .o_fp_po_card classes are deliberately untouched — the OWL client action and the new kanbans share the visual language but stay loosely coupled. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
f5f25f5716 |
fix(employee): rename 'Plating Certifications' tab to 'Operator Training'
The old label was easy to confuse with the customer-facing Certificate of Conformance (fp.certificate). Operators kept asking why a customer cert appeared on their employee profile. The tab is actually the operator's process-level training record (EN, chrome, anodize, etc.) that gates WO start in mrp_workorder.button_start — nothing to do with customer documents. Renamed the page string and added a one-line muted description so anyone landing on the tab understands what it's for. Also distinguishes it from the new 'Shop Roles' tab (coarser task tags used by Manager Desk auto-routing). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
da1ca06510 |
fix(employee-form): drop invalid color_field reference on Shop Roles m2m
The 'Tasks This Operator Can Do' many2many_tags widget declared
options="{'no_create_edit': True, 'color_field': 'color'}" but
fp.work.role doesn't have a color field — Odoo then tried to
fetch it on every employee form load and crashed with:
ValueError: Invalid field 'color' on 'fp.work.role'
Dropped the color_field option. Roles still render as tags, just
without the coloured chip background. (If we want coloured chips
later, add a Color integer field to fp.work.role and restore the
option — but the feature wasn't wired up anyway.)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
0f41eb136d |
fix(employee): handle Odoo 19 'in' operator + empty-list sentinel
Two compounding bugs in _search_x_fc_is_clocked_in surfaced when
fusion_clock's auto-clock-out closed all the demo open attendances:
1. Odoo 19 normalises ('=', True) into ('in', OrderedSet([True]))
before invoking the search method. The previous code only
handled '=' / '!=' and fell through to return [] for 'in' /
'not in' — which Odoo treats as 'no constraint' and matches
the entire table.
2. ('id', 'in', []) is also treated as no-constraint in some
Odoo versions; replaced with a [0] sentinel so the empty-
open-attendance case correctly matches nothing.
Rewrite reduces caller intent to a match_set of booleans, flips on
negative operators, then emits id IN / NOT IN against the cached
open-attendance employee ids. Variable signature accepts Odoo's
3-arg (records, op, val) form too in case the API shifts.
Verified on entech: clocked_in==True returns 3 (Carlos, James,
Marie); ==False returns the other 5.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
209b1974a7 |
feat(plating): seed 5 fresh MOs with mixed states + priorities
Stage filler gains step 6g — spins up five new manufacturing orders so the Manager Desk has a busy shop floor instead of the single in-flight MO that came out of the base seeder. Plan, in order of creation: WH/MO/00012 HOT Cyclone Manufacturing qty 25 unassigned WH/MO/00013 Urgent Westin Manufacturing qty 60 unassigned WH/MO/00014 Normal Honeywell Aerospace qty 18 auto-routed WH/MO/00015 Normal Amphenol Canada qty 40 routed + first WO started WH/MO/00016 Normal Magellan Aerospace qty 32 auto-routed Each MO is created via mrp.production.create() and confirmed through the bridge_mrp action_confirm() override, which auto-creates the portal job and generates ~9 WOs from the recipe with role-aware auto-routing. Post-create the script stamps priority on every WO and optionally clears assignments (HOT + Urgent) or starts the first WO (MO_00015) for variety. Recipe lookup was previously by code='ENP-ALUM-BASIC' which silently failed because the seed file uses code='ENP_ALUM_BASIC' (underscores) while the display name has dashes. Switched to "first available recipe of node_type=recipe" so the script works regardless of which spelling is canonical. Idempotent — bails early if there are already five-plus active MOs, so re-runs don't keep stacking new jobs. Verified on entech: Manager Desk now shows - 6 active MOs (was 1) - 23 unassigned active WOs (was 2) - 30 active+assigned WOs (was 6) - 2 WOs in progress now - all 7 operators with open queue (Marie 2, James 1, Carlos 8, etc.) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
2ce7bd3665 |
fix(manager-desk): include 'blocked' WOs + populate empty columns
Two complementary fixes — a real bug in the Manager Desk and demo
data that exercises the now-correct view.
The bug
=======
manager_controller.py used an explicit allow-list of WO states for
its Unassigned / Active columns and for the per-operator team load
count: ('pending','waiting','ready','progress'). That set MISSED the
'blocked' state Odoo emits when a WO's predecessor isn't done yet.
Result: an MO whose first WO is still running has all its downstream
WOs in 'blocked' state. They literally don't appear on the Manager
Desk — neither in "Needs a Worker" (even when unassigned) nor in
"In Progress" (even when assigned). The team load count also
under-reports because the operator's blocked queue is invisible.
Fix: switch all three domains from an allow-list to a deny-list
('done','cancel'). Same shape Plant Overview already uses, so the
two dashboards now agree on what "active" means.
Demo data
=========
Stage-filler gains two steps so the now-corrected view has obvious
data:
6e. _populate_active_wos walks the in-flight MO's blocked routing
and explicitly assigns the seven downstream WOs in sequence
order — Diego (training), Carlos (plating), James (demask),
Priya (oven), TWO unassigned (de-rack + post-bake — feed
"Needs a Worker"), Aisha (final inspection). Earlier
keyword-fuzzy matching missed WOs whose names didn't carry
the expected substring.
6f. _mark_so_awaiting_manager pushes two confirmed SOs to
receiving_status='inspected' + assigned_manager_id=False so
the "Awaiting Assignment" KPI is non-zero.
Verified on entech: 2 unassigned WOs, 6 active+assigned, 2
awaiting-assignment SOs. Six of seven operators carry at least one
open queue item; Marie has zero current load but a healthy past
completion history (she's on shift, between jobs).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
0315fee988 |
feat(plating): demo stage-filler — every workflow step now has data
Companion to fp_demo_seed.py. Bridges the gaps the original seeder
left after the team-skills + timer-audit + presence-aware Manager Desk
work landed (commit
|
||
|
|
0d12902ee7 |
feat(plating): in-Odoo notifications, timer audit, presence-aware Manager Desk, auto-promotion
End-to-end workflow tightening + the team / skills system. Three
phases bundled because they share the same touchpoints (button_start /
button_finish / Manager Desk dropdown).
PHASE 1 — In-Odoo notifications + timer audit
=============================================
Workers now get a bell-icon notification (Odoo Discuss inbox) the
moment a manager assigns them a WO. No email — operators check Discuss
between jobs, and the customer-facing notification dispatcher stays
out of the worker loop.
- mrp.workorder.write() override fires message_notify(message_type=
'user_notification') only when x_fc_assigned_user_id transitions to
a non-empty value (clearing or no-op writes don't ping)
- 4 new fields on the WO header surface what was previously buried in
time_ids: x_fc_started_by_user_id, x_fc_started_at,
x_fc_finished_by_user_id, x_fc_finished_at
- button_start stamps started_* once (subsequent pause/resume cycles
preserve the original); button_finish stamps finished_* every time
the WO closes
- New "Timer Audit" group on the WO form (Time & Cost tab)
PHASE 2 — Presence-aware Manager Desk
=====================================
Manager Desk now knows who's clocked in. Works with vanilla
hr_attendance and fusion_clock — both expose hr.attendance with an
open record while the operator is on shift.
- bridge_mrp depends on hr_attendance
- hr.employee.x_fc_is_clocked_in computed field (batched query — one
DB hit for the whole employee set, not N+1)
- hr.employee._fp_clocked_in_user_ids() classmethod for the dashboard
- manager_controller sends operators with is_clocked_in / role_ids /
lead_hand_role_ids per worker, plus presence dict {clocked_in: N,
total: M}; each WO carries role_id/role_name so the dropdown can
match qualified operators
Manager Desk OWL:
- Header gets a "Present 7 / 12" pill chip; tap to toggle hideOffShift
(off-shift hidden when active, accent colour when filter is on)
- New operatorsForWO(wo) helper sorts dropdown options into 4 buckets:
qualified+clocked-in → lead-hand+clocked-in → clocked-in untrained
(training mode) → off-shift (greyed; only shown when hideOffShift
is false). Each option carries a ●/○ dot prefix and a soft suffix.
PHASE 3 — Skills, lead-hand-per-role, auto-promotion
====================================================
The team grows organically: managers assign training tasks, operators
finish them, the system auto-promotes after N successful runs.
- fp.work.role.mastery_required (integer, default reads from the
company-level Default Mastery Threshold). Each role can override —
masking might need 1 success, electroless nickel 5.
- res.company.x_fc_default_mastery_threshold + res.config.settings
exposure under "Workforce Settings" in the Fusion Plating settings
block (default 3)
- hr.employee.x_fc_lead_hand_role_ids m2m, separate from
x_fc_work_role_ids — Sarah can be a lead hand for masking + racking
even if those aren't her primary roles. Manager-only group access.
- New fp.operator.proficiency model (one row per employee+role) with
completed_count, first/last_completed_at, promoted, promoted_at,
progress_label compute. SQL-unique on (employee, role).
- mrp.workorder.button_finish increments the (employee, role)
counter, then if count >= role.mastery_required AND not promoted,
adds the role to x_fc_work_role_ids and posts a "🎉 Promoted"
chatter line on the employee record. Wrapped in try/except so a
tracker glitch never blocks production.
- Promotion uses the WO's assigned_user_id, NOT env.user — credit
goes to the operator who was supposed to do it, even if a manager
finished on their behalf.
Employee form gets a "Shop Roles" tab (supervisor+):
- "Tasks This Operator Can Do" m2m
- "Lead Hand For" m2m (manager-only)
- Read-only Task Proficiency list with progress / promotion badges
Verified on odoo-entech: all fields land, default threshold = 3,
asset bundle regenerated as 9f38f05.
Module bumps: fusion_plating 19.0.4.0.0,
fusion_plating_bridge_mrp 19.0.4.0.0,
fusion_plating_shopfloor 19.0.11.0.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
c1d26f3168 |
fix(tablet): tighten layout for iPad + custom dropdown chevron
Operators run the Tablet Station on iPads (mostly landscape, sometimes portrait). The previous design pushed the dashboard panels below the fold on a 1024×768 viewport — meant a swipe before they could see their queue. Tightens spacing across the page without changing the visual language. What changed (all behind @media (max-width: 1180px)): - Page padding 24/32 → 16/20, gap between sections 24 → 16 - Hero title 32 → 24px, subtitle margin-top halved - KPI strip switches from auto-fit to fixed 6-column grid so all six KPIs stay on a single row instead of wrapping at iPad widths; per-tile padding 20 → 12/16, value font 44 → 24px, label 14 → 12px - Active WO banner padding 20 → 12/16 - Dashboard breakpoint to single-column lowered 1100 → 760px so iPad portrait still gets two columns of panels - Panel padding 20 → 16, panel-head padding-bottom 12 → 8 - Empty state padding 32/16 → 16/12 (the "All caught up" tile no longer eats 140px per panel) - Queue rows min-height 64 → 52, bake/gate rows 64 → 48 Station picker dropdown: - Native chevron suppressed via appearance: none and replaced with an inlined SVG arrow positioned with explicit right-edge inset. Stroke uses currentColor so it follows light/dark mode. - Right padding bumped from $fp-space-4 → $fp-space-7 to give the arrow breathing room — previously hugged the rounded corner. Station dropdown labels: - Append "(CODE)" after the name. The shop's five stations (Bake Oven Tablet / Inspection Kiosk / Plating Room Tablet 1 / Receiving Mobile / Shipping Desktop) all live in the same facility with no work_center, so without the code suffix the dropdown options looked similar at a glance. Bumped fusion_plating_shopfloor → 19.0.10.0.0. Asset bundle regenerated as bc28f73. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
6c4ff7751f |
feat(plating): comprehensive timezone fix across dashboards/PDFs/emails
Database stores datetimes naive-UTC, but the dashboards and emails were
showing UTC strings to users in EST/EDT — making 9pm Toronto look like 1am
the next day. Adds a single helper module + auto-detection on install.
Core changes (fusion_plating):
- New fp_tz.py helper: fp_user_tz, fp_format, fp_isoformat_utc, fp_time_ago
Resolves user.tz → company.x_fc_default_tz → UTC.
- res.company.x_fc_default_tz Selection (full pytz IANA list)
- res.config.settings exposes the company tz under a new "Regional
Settings" block in Settings > Fusion Plating
- post_init_hook auto-populates the tz on first install: tries admin
user → server /etc/timezone → America/Toronto fallback
- fp_process_node._to_dict now sends create_date/write_date as ISO with
explicit +00:00 marker so JS new Date() parses it as UTC and the
recipe tree editor's "time ago" math works correctly
Shop-floor controllers:
- shopfloor_controller.py: every fields.Datetime.to_string() and naive
.strftime() swapped for fp_format(env, ...) — due_at, bake times,
last_log_date, gates, server_time all now in user's tz
- _time_ago() removed; replaced with fp_time_ago helper which compares
tz-aware datetimes (the local one was naive-vs-naive and could be
off by hours)
- manager_controller.py date_planned: str(...)[:10] slice replaced
with fp_format MM/DD in user's tz
Notifications + reports:
- mail_template_data.xml: 5 .strftime() calls in body_html → babel
format_datetime / format_date with tz=(user.tz or company tz)
- report_fp_job_traveller.xml: rec.received_date (Datetime) gets
t-options="{'widget':'datetime'}" so Odoo's QWeb renders in user tz
Settings view layout:
- fusion_plating now owns the Settings page "Fusion Plating" app shell
- fusion_plating_certificates xpaths into it instead of redefining
(prevents app-name collision)
Verified on odoo-entech (LXC 111): post_init_hook detects
America/Toronto from /etc/timezone, MO date_start 2026-04-17 05:28 UTC
correctly displays as 2026-04-17 01:28 EDT.
Module versions bumped: fusion_plating 19.0.3.0.0,
fusion_plating_shopfloor 19.0.9.0.0, plus certificates / notifications /
reports → 19.0.3.0.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
e52477e2ba |
fix(plant-overview): priority stripe clips to card's rounded corners
The coloured priority stripe (4px vertical bar at the card's left
edge, set via ::before pseudo) extended past the top and bottom
rounded corners of the card — visible as sharp corners on cards with
Urgent or HOT priority (yellow/red stripe).
Cause:
.o_fp_po_card::before was positioned at left/top/bottom: -1px and
given its own border-radius, but the stripe's own radii didn't
match the card's 14px radius precisely, and the -1px offsets
pushed the stripe outside the card's curves.
Fix:
1. .o_fp_po_card gets overflow: hidden. Shadows are painted outside
the content box in CSS so box-shadow still renders fine, but any
child element (including ::before) now clips to the parent's
border-radius automatically.
2. Stripe ::before simplified to left/top/bottom: 0 — no more
negative offsets, no more independent border-radius rules.
The parent's overflow does the corner-matching.
Verified in /web/assets/5e85f15/web.assets_backend.min.css:
.o_fp_po_card { ...; overflow: hidden; ... }
.o_fp_po_card::before { content: ""; position: absolute;
left: 0; top: 0; bottom: 0; width: 4px; ... }
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
83271ee69e |
fix(shopfloor): pages own the scroll + sharp corner fix
Two problems after the previous round:
1) Mobile scroll still not working, even on a real phone.
Dug into /usr/lib/python3/dist-packages/odoo/addons/web/static/src/
webclient/webclient_layout.scss and found Odoo's mobile layout
switches scroll ownership at @media-breakpoint-down(md) (<768px):
Desktop: .o_content has overflow:auto — your content scrolls there
Mobile: .o_action gets overflow:auto, .o_content is overflow:initial
Our client action roots had `min-height: 100%` and relied on an
ancestor for scroll. That ancestor changes between breakpoints, and
somewhere in the transition scroll gets lost — the page fills but
can't scroll.
Fix: make each page OWN its scroll, like .o_content on desktop
kanban/list views. Three roots now have:
.o_fp_tablet / .o_fp_manager / .o_fp_plant_overview {
height: 100%;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
Scroll works regardless of which ancestor Odoo decides owns it at
any given breakpoint.
2) Sharp corner on column header at mobile widths.
The previous commit set `overflow: visible` on .o_fp_po_column at
<=900px trying to help scroll. But the column has border-radius: 20px
and contains .o_fp_po_col_header (which has its own background). When
overflow is visible, the header bg extends to the column's corners
without being clipped — you see squared corners on the mobile card.
Fix: keep `overflow: hidden` on .o_fp_po_column at every breakpoint
(that's what clips the rounded corners). Only lift `max-height` on
mobile so columns size to content naturally. Since the PAGE now owns
the scroll (see fix #1), the column doesn't need internal scroll —
no `overflow: auto` on the body is needed either.
Verified in compiled CSS at /web/assets/7ff5b28/web.assets_backend.min.css:
.o_fp_tablet { height: 100%; overflow-y: auto; ... }
.o_fp_manager { height: 100%; overflow-y: auto; ... }
.o_fp_plant_overview { height: 100%; overflow-y: auto; ... }
.o_fp_po_column { border-radius: 20px; overflow: hidden }
@media (max-width: 900px) .o_fp_po_column {
flex: 1 1 auto; min-width: 100%; max-width: 100%;
max-height: none; // no overflow override — hidden stays
}
Version bumped 19.0.6.0.0 -> 19.0.7.0.0 to force bundle hash change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
082c585e24 |
fix(shopfloor): mobile scroll works — remove nested scroll containers
User: "scrolling is not working" in Chrome DevTools mobile simulation.
Three actual problems:
1. Plant Overview columns had max-height: calc(100vh - 180px) +
overflow: hidden, with a nested overflow-y: auto on the column
body. Classic Trello kanban pattern — works on desktop, breaks
on mobile. You get two scroll containers fighting each other and
the PAGE itself can't scroll past the viewport height.
2. .o_fp_po_columns had overflow-x: auto on all widths. On the
phone-stack breakpoint (<600px) this was also still on, creating
another nested scroll container.
3. Draggable cards can swallow touch events on mobile because
touch-action defaults to "auto" and Chrome's mobile simulator
treats touch on draggable elements as potential drag-start.
Fixes — all at the <=900px breakpoint (tablets + phones):
.o_fp_po_column max-height: none; overflow: visible
.o_fp_po_col_body overflow-y: visible
.o_fp_po_columns flex-direction: column; overflow: visible
Plus .o_fp_po_card carries `touch-action: pan-y` unconditionally —
touch-scroll gestures never get hijacked by the draggable="true"
attribute. Desktop mousedown drag still works (HTML5 drag-drop
isn't touch-based by default).
Also added -webkit-overflow-scrolling: touch to all three page
roots (.o_fp_tablet, .o_fp_manager, .o_fp_plant_overview) and to
the internal scroll containers that remain on desktop — gives iOS
Safari proper momentum scroll (11 occurrences in the compiled
bundle).
Drag-drop JS preventDefault calls audited — they only fire on
dragover/drop (HTML5 drag events), which don't exist on touch by
default, so no touch interference there.
Verified via compiled CSS:
.o_fp_po_card { touch-action: pan-y; ... }
@media (max-width: 900px) .o_fp_po_column { overflow-x: visible;
overflow-y: visible; min-height: auto }
@media (max-width: 900px) .o_fp_po_col_body { overflow-y: visible }
Version bumped 19.0.5.0.0 -> 19.0.6.0.0 to force the bundle hash
to change. New URL: /web/assets/4a1b69e/web.assets_backend.min.css
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
afc01ec1d9 |
fix(shopfloor): proper dark-mode via \$o-webclient-color-scheme branch
Dug deeper after the user reported shop-floor pages staying white in
dark mode. Traced through Odoo 19 source:
_dependencies/web_enterprise/static/src/
webclient/color_scheme/color_scheme_service.js <- reads cookie
scss/primary_variables.scss \$o-webclient-color-scheme: bright
scss/primary_variables.dark.scss \$o-webclient-color-scheme: dark
Odoo compiles TWO separate CSS bundles:
web.assets_backend -> compiled with \$...scheme: bright
web.assets_web_dark -> compiled with \$...scheme: dark
(the .dark.scss files are layered in front of the light ones)
Our shop-floor SCSS is in web.assets_backend, which means it gets
compiled into BOTH bundles. But the previous CSS-variable fallback
chain (var(--fp-page-bg, var(--bs-tertiary-bg, #hex))) baked the
SAME hex fallback into both bundles, so cards stayed white in dark.
Odoo's own code doesn't redefine --bs-* CSS custom properties at
runtime either — it just bakes the dark palette straight into the
dark bundle via SCSS \$-variables during compile.
Fix: _fp_shopfloor_tokens.scss now branches at compile time:
\$o-webclient-color-scheme: bright !default;
\$_fp-page-hex: #f3f4f6; // light defaults
\$_fp-card-hex: #ffffff;
...
@if \$o-webclient-color-scheme == dark {
\$_fp-page-hex: #1a1d21 !global;
\$_fp-card-hex: #22262d !global;
...
}
\$fp-page: var(--fp-page-bg, \$_fp-page-hex);
\$fp-card: var(--fp-card-bg, \$_fp-card-hex);
The CSS-custom-property fallback stays so deployments can still skin
via --fp-* without touching SCSS; the underlying hex changes between
bundles.
Verified via odoo-shell:
LIGHT bundle: .o_fp_plant_overview { background-color: var(...#f3f4f6) }
.o_fp_po_card { background-color: var(...#ffffff);
border: ... #d8dadd }
DARK bundle: .o_fp_plant_overview { background-color: var(...#1a1d21) }
.o_fp_po_card { background-color: var(...#22262d);
border: ... #343942 }
Two separate bundle URLs generated:
/web/assets/a593157/web.assets_backend.min.css
/web/assets/a9dba7d/web.assets_web_dark.min.css
=== CLAUDE.md ===
Replaced the previous (incorrect) .o_dark_mode override advice with
a proper "Branch on \$o-webclient-color-scheme at SCSS compile time"
section, including the bundle names and the verify-via-odoo-shell
snippet. Future redesigns now have a single, correct pattern to
follow.
Version bumped 19.0.4.0.0 -> 19.0.5.0.0 to force asset hash change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
11f7791c5e |
fix(shopfloor): dark mode auto-inverts + Quick View button visible
Two fixes + a memory entry in CLAUDE.md.
=== Dark mode ===
User: "when I change the theme the whole background does not turn
dark like the other pages does". Digging through Odoo 19 source:
/_dependencies/web_enterprise/static/src/scss/
bootstrap_overridden.dark.scss
primary_variables.dark.scss
secondary_variables.dark.scss
Odoo doesn't flip dark mode via a runtime .o_dark_mode class on the
DOM — it compiles a SEPARATE asset bundle where $o-webclient-color-
scheme: dark is set, which redefines every --bs-* token with dark
values. When the user toggles dark mode, Odoo swaps the whole CSS
bundle.
So my previous :root[data-bs-theme="dark"] { --fp-page-bg: #13161a; }
block was DEAD CODE — nothing ever sets data-bs-theme on the root.
Fixed: tokens now fall through to Bootstrap's --bs-* semantic tokens
before hitting a hex default, so they auto-invert when Odoo swaps
bundles. Three-level fallback chain:
$fp-page : var(--fp-page-bg,
var(--bs-tertiary-bg, #f3f4f6));
$fp-card : var(--fp-card-bg,
var(--bs-card-bg,
var(--o-view-background-color, #ffffff)));
$fp-border : var(--fp-border-color,
var(--bs-border-color, #d8dadd));
$fp-ink : var(--fp-ink, var(--bs-body-color, #1f2937));
Dead .o_dark_mode block removed. No runtime selector needed.
=== Quick View button ===
User: "Quick View button color is white with white button in light
mode." Cause: Bootstrap's .btn-primary loads AFTER our custom CSS
in the bundle and resets color: #fff, background: var(--bs-btn-bg)
— which clobbered our $fp-accent / $fp-ink assignment because a
later rule at the same specificity wins.
Fix: split the primary button into its own rule with higher
specificity (.o_fp_manager .o_fp_manager_head_actions .btn.btn-primary)
and !important on the three key properties — so Bootstrap can't
shout us down. Hover uses brightness(1.08) for a subtle darken
without needing another color assignment.
=== CLAUDE.md additions ===
Added two new rules documenting the lessons so this isn't relearned:
Rule 8 — Odoo 19 forbids @import in custom SCSS (silent warning,
falls back to cached bundle). Register partials in the assets list
in load order; SCSS variables cascade through the bundle.
"Card Styling — Copy Odoo's Kanban Pattern" section explaining:
- Don't rely on --bs-border-color directly for card surfaces
- Chain through $fp-* → --fp-* → --bs-* → hex
- 3-layer contrast rule (page → container → card)
- Reference _fp_shopfloor_tokens.scss as canonical
"Asset Bundle Cache Busting" section with 4-step escalation path
for when CSS changes don't show up in browser.
Verified: bundle regenerated to /web/assets/b48ab17/web.assets_backend.min.css
(id 1945). Card rule compiled with full fallback chain visible.
Primary button carries !important modifier for bg/border/color.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
81277edb25 |
fix(shopfloor): explicit hex colors like Odoo's own kanban
Why the borders weren't showing: the previous approach used
color-mix(var(--bs-body-color) 4%, var(--o-view-background-color)) for
card/column backgrounds. Under Odoo 19 the resolved values for those
variables were nearly identical to var(--bs-body-bg), so the card
surfaces visually merged into the page. Same problem for borders:
var(--bs-border-color) can render extremely faint depending on theme.
Checked what Odoo's native kanban does — dug through the compiled
CSS and found:
.o_kanban_record { background-color: white;
border: 1px solid #d8dadd; }
.o_kanban_group { background: var(--KanbanGroup-background); }
Odoo uses EXPLICIT hex values and card-specific tokens, not the
generic body/border variables. Adopted the same approach.
New tokens in _fp_shopfloor_tokens.scss — all explicit, plus a
dark-mode override block keyed off [data-bs-theme="dark"] and
.o_dark_mode (Odoo 19 uses both):
light dark
------------------------ ------------------
--fp-page-bg: #f3f4f6 #13161a
--fp-column-bg: #e9ebef #1a1e24
--fp-card-bg: #ffffff #22262d
--fp-card-soft-bg: #f8fafc #1c2027
--fp-border-color: #d8dadd #343942
--fp-ink: #1f2937 #e5e7eb
--fp-ink-mute: #6b7280 #8a909a
shadow scale switched from color-mix to explicit rgba(0,0,0,...)
so it renders identically across browsers.
All three SCSS files updated via sed to swap
var(--bs-border-color) -> #{$fp-border}
...then $fp-border resolves to var(--fp-border-color, #d8dadd) — a
proper card-level border that is VISIBLE (28 refs to --fp-card-bg
and 35 refs to --fp-border-color confirmed in the compiled bundle).
Plant Overview specifically now has:
* Column: #f8fafc bg + #d8dadd border + shadow
(column is brighter than the page it sits on)
* Column HEADER: #ffffff inside the column, with bottom border
(clear separator between stages)
* Card: solid #ffffff bg + #d8dadd border + shadow
(brightest surface, pops off the column)
* Gap between columns: 16px so the column borders don't touch
Module version bumped to 19.0.3.0.0. Bundle regenerated at
/web/assets/0cd8bc1/web.assets_backend.min.css (1.45 MB, id 1939).
Verified by parsing compiled CSS:
.o_fp_po_card: background-color: var(--fp-card-bg, #ffffff);
border: 1px solid var(--fp-border-color, #d8dadd);
.o_fp_po_column: background-color: var(--fp-card-soft-bg, #f8fafc);
border: 1px solid var(--fp-border-color, #d8dadd);
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
2588a2b651 |
fix(plant-overview): real drop insertion indicator + small logo back
Three direct fixes responding to user feedback: 1. Drag-drop "simulation" — now works like Trello/Linear. As the cursor moves over a column, a live DOM placeholder node is INJECTED into the card list at the exact position the dragged card will drop. The placeholder is a 4px pulsing accent-coloured bar with a soft glow ring. Slides smoothly between cards as the cursor moves. Column body also gets a tinted background + inset accent outline for the "whole column is receptive" cue. Previous version only tinted the column — no indicator of WHERE the card would land. The new approach actually mimics the physical gesture: cards visually make room for the incoming card. 2. Customer logo restored at 32×32px. Removing it was the wrong call. It's back now as a small thumbnail avatar (rounded 10px corners, soft border, object-fit contain so wide logos don't squish). Sits to the left of the customer name in the card top row. Fallback icon for customers without a logo. Takes the same space as the step badge on the right — compact and organised. 3. Module version bumped 19.0.1.0.0 → 19.0.2.0.0 so the asset bundle content hash changes. The new compiled CSS is served at /web/assets/022171c/web.assets_backend.min.css (previously /web/assets/278b43c/...). Fresh URL forces browser to refetch — this is what was causing the "still no border" complaint. Verified in compiled CSS: o_fp_po_card_avatar, o_fp_po_drop_placeholder, o_fp_placeholder_pulse keyframes, o_fp_drop_target — all present. Zero SCSS warnings. Module upgrade clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
83a999afad |
style(shopfloor): borders back, real drop indicator, logo demoted
Three concrete fixes based on user feedback:
1. Card borders restored — every card / panel / KPI tile / queue row /
bake row / team card now has a thin 1px border (var(--bs-border-color))
ON TOP of the soft shadow. That's the classic SaaS card treatment
and solves the "jobs have no borders, they blend together" problem.
Hover lifts the border to the accent colour (~45% mix) so cards
feel responsive.
2. Plant Overview drop-zone indicator restored.
- Column body gets inset outline + tinted background on dragover
(.o_fp_drop_target class already added by onColDragOver in JS)
- A 56px dashed placeholder bar appears at the bottom of the column
via ::after on the drop target. That's the "here's where the card
will land" visual the user remembered.
- Dragged card gets scale(0.97) + slight rotation + opacity 0.4 for
a clearer "I'm picking this up" feedback.
3. Customer logo removed from Plant Overview cards.
The big company logo at the top of each kanban card was wasting
space. Customer NAME still shows (in bold, full-width, with text-
ellipsis), step badge pill stays on the right. No more wasted
real estate on visuals nobody looks at twice.
Extra polish while in there:
- Section headers (Tablet + Manager) now have a coloured icon badge
— a rounded square 36×36 with tinted background + accent-coloured
icon next to the H3 title. Adds visual weight without noise.
- Panel head gets a 1px bottom divider.
- Manager panels tint the icon badge per panel tone (amber for
Unassigned, green for In Progress, blue for Team).
- Header action buttons (Tablet scan/picker, Manager refresh/mode)
get proper borders + hover state.
- State dividers on bake/gate/hold rows preserved as inset shadows.
Verified: bundle rebuilt at /web/assets/278b43c/web.assets_backend.min.css
(1.45MB, id 1930). All key classes present: o_fp_drop_target,
o_fp_dragging, o_fp_po_parts_bar, o_fp_po_parts_fill, section-header
icon badges. Zero SCSS warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
067d1f01c8 |
redesign(shopfloor): clean slate — depth by shadow, no card borders
User feedback: the previous gradient-heavy look felt cluttered, job
cards had confusing heavy borders, the hierarchy was noisy. Wiped all
three SCSS files and both OWL templates and rebuilt from scratch with
a clean minimalist design language.
Design philosophy — the single source of truth:
* NO borders on cards — depth comes from elevation (shadow) + a
tiny surface-tint difference between page and card
* ONE accent colour (var(--o-action)); semantic red/amber/green only
for status pills and state bars
* Shadow-only cards: $fp-elev-1, $fp-elev-2, $fp-elev-3 built on
color-mix of foreground so they adapt to dark mode automatically
* Generous whitespace, 8pt spacing scale ($fp-space-1 through
$fp-space-10)
* Type-first hierarchy: 32px page titles, 44px KPI numbers, tabular
numerics so refreshing counts don't jitter
* Priority/state cues via narrow 4-6px coloured bars and small dots
— never via loud backgrounds or gradient washes
* All interactive elements at 48px touch minimum (shop-floor gloves)
New token file (_fp_shopfloor_tokens.scss) exports:
- $fp-space-1..10, $fp-radius-sm..xl, $fp-radius-pill
- $fp-page / $fp-card / $fp-card-soft surface tints
- $fp-ink / $fp-ink-soft / $fp-ink-mute / $fp-ink-faint text tiers
- $fp-elev-1..3 layered shadows
- $fp-text-xs..3xl type scale
- @mixin fp-pill, fp-focus-ring, fp-card, fp-hover-only
- fp-wash() function for state-coloured soft backgrounds
Tablet Station (fusion_plating_shopfloor.scss + shopfloor_tablet.xml):
- Clean hero: just the title, station chip, picker + scan button
- KPI cards: no gradient overlay, just a 10px coloured dot and big
44px number. Hover lifts with shadow
- Active WO: soft green wash background, no border, pulsing dot
- Panels contain queue/baths/bakes/gates/holds — all on the same
card surface with big rounded corners, no internal borders
- Queue rows: flat on a soft page-tinted background, hover slides
right 2px (no lift, cleaner)
- Bake/Gate/Hold rows: state-coloured inset shadow as a 4px stripe,
no border
- Empty states: centred with a 44px muted icon and friendly copy
Manager Desk (manager_dashboard.scss + manager_dashboard.xml):
- Matching hero with live dot that calmly pulses green during a fetch
- 4 KPI cards in the same language as the tablet
- Three panels (Unassigned / In Progress / Team) with coloured dots
next to their titles instead of top accent bars
- MO cards NO borders, subtle page-tint background, 4px left stripe
only for priority (red HOT, amber Urgent)
- Team cards: avatar + name + live load pill, hover slides right
- WO expanded rows use card-soft buttons/dropdowns for low contrast
Plant Overview (plant_overview.scss):
- Columns are now shadow-lifted cards on the tinted page background
- Kanban cards: no border, small shadow, lift on hover
- Priority stripe is an inset box-shadow (not a border) so hover
transform doesn't wobble
Backend contract preserved — OWL class names, prop signatures, RPC
endpoints, and stateBadge mapping all unchanged. Only visuals.
Verified:
* Bundle compiled to /web/assets/.../web.assets_backend.min.css
(1.45MB, id 1926)
* All 6 new classes present in compiled CSS
* Zero SCSS "forbidden import" warnings
* Zero Odoo module upgrade errors
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
6d1efc6c43 |
fix(shopfloor): register tokens SCSS in bundle, drop forbidden @import
Odoo 19 forbids local SCSS @import statements for security reasons and
silently falls back to the OLD cached CSS bundle when it sees them. My
redesign commit used:
@import "./fp_shopfloor_tokens";
in three SCSS files. Odoo logged
WARNING Local import './fp_shopfloor_tokens' is forbidden for
security reasons. Please remove all @import {your_file} imports
in your custom files.
...and the compiled bundle kept rendering the old look. That's what
the user saw.
Fix:
1. Add _fp_shopfloor_tokens.scss as the FIRST entry in
web.assets_backend in the manifest. Odoo concatenates the bundle
in order, so variables/mixins in the first file are visible to
every later file — native @import is not needed.
2. Strip the @import "./fp_shopfloor_tokens"; line from all three
consumer files (tablet, manager, plant overview).
Verified: asset bundle regenerated to /web/assets/.../web.assets_backend.min.css
(1.45 MB). Grepped the compiled CSS and all five new classes are present:
o_fp_tablet_header, o_fp_kpi_strip, o_fp_mgr_card, o_fp_live_dot,
o_fp_panel_unassigned. 8 radial-gradients baked in. Zero warnings in
the Odoo server log post-rebuild.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
298f5942eb |
refactor(shopfloor): modern redesign w/ gradients, theme-safe tokens
Shop-floor operators and managers live on these screens all day — the
old look worked but felt like a spec sheet. Pass through all 5 pages
with one design language: gradient KPI cards, hero banners, soft
shadows, rounded corners, large touch targets, friendly empty states.
Dark mode and light mode both look deliberate, not inverted.
New shared file: _fp_shopfloor_tokens.scss
A single source of truth for radii, elevation (shadows that respect
dark mode via color-mix on foreground), typography scale (tabular
numerics for KPIs, 18px base for shop-floor readability), animation
easings, semantic gradients (@mixin fp-grad), tone helpers
(@mixin fp-tone), focus ring, and the 44px touch-min token.
Every other SCSS file imports this — no duplicated colour math.
Gradients are built on color-mix(in srgb, var(--bs-foo) X%, transparent)
so they layer naturally on either the light or dark page background.
No @media-prefers-color-scheme forks needed.
Tablet Station (fusion_plating_shopfloor.scss):
* Hero banner with dual radial-gradient wash (brand + success), live
station chip, gradient focus ring on the picker.
* KPI cards (6-up on desktop, 2x3 on phone) get a subtle top accent
line, coloured gradient overlay, 40px headline number, faded icon
at corner. Tone variants (info/success/warning/danger/muted) drive
colour without extra CSS.
* Active WO banner is a green gradient pill with a breathing-dot
pulse — unmissable when something is running.
* Panels get top accents, queue rows get priority pills (HI/M/·),
bake/gate/hold rows get colour-coded left accent bars.
* Tiles have a 4px left stripe keyed to state + hover lift.
* Status chips are uppercase, pill-shaped, tone-tinted with
color-mix so they respect theme.
* Empty states now have a large 36px icon + friendly copy instead
of a one-liner.
* Focus rings use the shared @mixin fp-focus-ring.
Manager Desk (manager_dashboard.scss):
* Same hero treatment with radial gradient + live-dot pulse.
* 3 panels carry a coloured top accent bar — amber (Unassigned),
green (In Progress), blue (Team). Instant visual routing.
* KPI strip matches tablet.
* MO cards get a left priority stripe (red for HOT, amber for
Urgent), lift on hover, expand cleanly.
* Team avatars get a border + subtle tint background for depth.
* Worker/tank pickers have custom focus rings.
Plant Overview (plant_overview.scss):
* Header is now a gradient wash tied to the brand colour.
* Work-centre columns get a thin gradient top-stripe and pill-style
count badges.
* Cards have real depth (layered shadow), lift harder on hover,
change border colour on hover.
All three files share the same design tokens, so colours/shadows/
radii are identical across pages. Edit one place, everything updates.
Verified: backend asset bundle compiles clean (no SCSS errors), zero
warnings on module upgrade, asset cache cleared for fresh delivery.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
ae03e32b5d |
style(shopfloor): phone + iPad responsive across all 5 pages
Shop-floor workers and managers use phones and iPads on the line.
The existing layouts only stacked at 1100px / 1280px, which left
everything cramped on a 375px iPhone or 390px Android. Pass through
all 5 shop-floor screens with disciplined breakpoints and touch-first
sizing.
Breakpoint ladder (consistent across files):
1400px : manager WO row: worker/tank pickers drop to their own rows
1280px : manager grid 3 → 2 columns, Team spans both
1100px : tablet dashboard 2 → 1 column
900px : manager grid → 1 column; tablet + manager padding shrinks
768px : plant overview columns stack; first-piece & bake kanbans
already handled natively by Odoo
600px : PHONE — all columns stack, everything full-width, every
button min-height 44px (Apple HIG touch target), font
shrinks for denser phone screens
Manager Desk (manager_dashboard.scss):
- Header stacks into two full-width rows on phone, action buttons
flex-grow to share the row
- 3 column grid stacks earlier (900px instead of 800px) so iPad
portrait gets a clean single-column view
- WO rows: assign/tank pickers go full-width on their own rows at
1400px, then the whole row stacks to 1 column at 600px
- Cards min 56px tap zone
- Team avatars keep their layout but cap gap on phone
Tablet Station (fusion_plating_shopfloor.scss):
- Header: picker/scan button stack full-width on phone
- KPI strip auto-fit by default, forced 2×3 grid on phone so 6
tiles stay visible without scrolling past a wall of tall cards
- Queue rows: Start/Finish buttons drop to their own row on phone,
each flexing to 50% width → easy one-thumb tap
- Bake/Gate/Hold rows: full stack on phone, action buttons flex-grow
- Bath tile grid: 2-up on phone (not auto-fit)
- Active WO banner stacks, Open-WO button full-width
- Station picker and scan input go full-width
Plant Overview (plant_overview.scss):
- Columns stack at 768px (already there) + 600px padding shrink,
search input full-width, header wraps sensibly
- Cards get min-height 64px for touch
Touch-device hover suppression:
@media (hover: none) — hover highlights were sticking after tap on
phones/iPads. Block them for .o_fp_queue_row, .o_fp_tile,
.o_fp_tablet_card, .o_fp_mgr_card, .o_fp_team_card, .o_fp_po_card.
Asset cache cleared so phones pick up the new SCSS on next load.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
d29857078a |
fix(manager-desk): unstick the spinner + live updates that don't flash
Root cause of the stuck "Loading manager data..." spinner: the overview
endpoint included a search_count on sale.order.x_fc_workflow_stage,
which is a non-stored computed field. Odoo 19 raised:
ValueError: Cannot convert sale.order.x_fc_workflow_stage to SQL
because it is not stored
The controller silently logged the error; the JS caught and swallowed
the RPC failure, leaving state.overview=null forever. So the UI just
kept spinning while production changed around the manager.
Fixes:
1. Controller (manager_controller.py)
- "Awaiting assignment SOs" is now computed from STORED fields only:
state='sale' AND x_fc_receiving_status='inspected'
AND x_fc_assigned_manager_id=False
Same stage, legal SQL.
- Whole endpoint wrapped in try/except; failures return
{'ok': False, 'error': '...'} so the UI can surface them instead
of dying silently.
- Response carries a payload_hash (md5 of the JSON body minus
user_name). If the client sends back known_hash and nothing has
moved, the server returns {'unchanged': True, 'payload_hash': ...}
and the client skips the repaint entirely. Keeps the UI quiet
between polls.
2. OWL component (manager_dashboard.js)
- Poll cadence tightened from 30s → 8s (production-pace).
- Unchanged payloads don't mutate state.overview → no re-render,
no flash. Live dot just updates its tooltip.
- Changed payloads do an in-place MERGE of the overview (copying
scalars/arrays onto the existing reactive object) instead of
replacing it wholesale. OWL's diff only re-renders rows that
actually moved.
- isFetching guard so overlapping polls can't stack up.
- state.loadError surfaces backend errors in a red banner with a
Retry button — no more silent spinner.
3. UX
- Live dot next to the title: soft green at rest, bright green
pulsing during a fetch.
- "Updated Xs ago" subtitle uses a getter so the label freshens
between polls.
- Manual Refresh button next to Quick/Detailed toggle.
- Spinner only appears on the genuine first load; gone forever
once the first payload lands.
Verified: the old crashing query now runs clean on demo data; odoo
logs show zero errors for the last 5 minutes of polling.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
a660f1f05d |
fix(configurator): part-level saved descriptions (not generic)
The earlier description templates were global — same 8 generic texts
applied to any part. That's useless when a customer has 3,500 parts
and each part has 3–5 canned variants (standard, masked threads,
masked bore, rework, rush packaging). That's ~17,500 rows total, and
the variants ONLY make sense in the context of a specific part number.
Restructured so descriptions live on each part:
Model changes:
fp.sale.description.template.part_catalog_id (new M2O, indexed,
ondelete cascade) — the primary scoping field
fp.sale.description.template.partner_id — now a related store=True
field pulled from the part, so customer-level search still works
fp.part.catalog.description_template_ids (new O2M inverse) — the
5–10 canned descriptions attached to this specific part
fp.part.catalog.description_template_count (computed)
UI changes:
Part Catalog form: new "Descriptions" notebook page with inline
editable list (sequence + name + tag + description + usage_count).
5 variants take 30 seconds to enter.
Part Catalog form: new smart button "Descriptions" showing the count,
jumps to the full list filtered by this part.
Template list view: part_catalog_id column added, list ordered by
part first. Search view adds Part filter + Part-Specific /
Generic (No Part) filters + Group By Part.
Wizard changes:
description_template_id domain now prioritises part-specific, falls
through to partner, coating, or generic on a single dynamic domain.
_onchange_suggest_template priority: part → customer → coating →
none. No longer auto-picks a random global template when a part
has its own.
Smoke-tested on VS-HSA201-B (Amphenol):
5 canned variants seeded on the part form
Wizard with this part auto-suggested the lowest-sequence one
The part's Descriptions smart button shows "5"
Bulk data entry path for the client's 3,500 parts: either use the
inline list on each part form, or import via CSV with the new
part_catalog_id column (external_id or DB id).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
f340c87b6a |
feat(bridge_mrp): shop-role auto-routing + tablet worker mode (CHUNK 4/4)
Completes the worker-access story. Handoffs now route themselves.
New model fp.work.role with 8 seeded defaults (noupdate so shops can
rename/prune):
masking · racking · plating_op · demask · oven · derack ·
inspection · rework
Each one has a code, icon, description, sequence, active flag.
Config menu: Configuration → Shop Roles (manager-only).
Field additions:
hr.employee.x_fc_work_role_ids (Many2many) — tag workers with the
roles they perform. One-person shop: one employee, every role.
Specialised shop: one role per employee. Cross-trained: multiple.
fusion.plating.process.node.x_fc_work_role_id (Many2one) — tag
each recipe operation with the role that performs it.
mrp.workorder.x_fc_work_role_id (Many2one) — copied from the recipe
operation on WO generation.
Auto-assignment on WO generation:
_generate_workorders_from_recipe() now copies the operation's role
onto the WO, then calls _fp_pick_worker_for_role() which picks the
least-loaded employee (active WO count) with that role. WO lands in
their Tablet "My Queue" the moment the MO is confirmed. No manual
routing needed for the common case.
Tablet Station — worker mode:
/fp/shopfloor/tablet_overview now filters to WOs where
x_fc_assigned_user_id == env.user when the field is populated.
KPIs (WOs Ready / In Progress) reflect the logged-in worker's load,
not shop-wide totals. "My Queue" rows carry wo_state + can_start +
can_finish so inline Start/Finish buttons appear.
New JS handlers onStartWo / onFinishWo call /fp/shopfloor/start_wo
and /fp/shopfloor/stop_wo (finish=true). One-tap progression.
Views:
hr.employee form gets a "Shop Roles" notebook page with many2many_tags.
Process node form gets x_fc_work_role_id inline after work_center_id.
Work Order form shows role + assigned worker.
Smoke-tested end-to-end on WH/MO/00010:
Masking → Administrator (masking role)
Racking → Administrator (racking role)
E-Nickel → Andrew (plating_op, least-loaded tiebreaker)
Demask → Administrator (masking)
Oven bake → Andrew (oven)
Derack → Administrator (racking fallback)
Post-plate QA → Administrator (inspection)
80 existing WOs backfilled with role + worker via name-match.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
1c6a460ca1 |
feat(shopfloor): Manager Desk — assign workers, swap tanks, take over (CHUNK 2/4)
New client action "Manager Desk" under Shop Floor menu (manager-only).
Three-column dashboard designed for the shop manager's daily job:
Column 1 — Needs a Worker
MOs with active WOs missing user assignment. Each card expands to
show per-WO rows with:
- Assign Worker dropdown (pulls from group_fusion_plating_operator)
- Tank swap dropdown (all tanks with current bath)
- Take Over (claims for the manager in one click)
- Open (jump to WO form)
Column 2 — In Progress
MOs with workers actively running WOs. Shows who's on each step,
lets manager reassign or take over if someone steps away.
Column 3 — Team
Avatar grid of operators with live queue + in-progress counts.
Click to drill into that operator's full WO list.
KPI strip on top: Unassigned WOs, In Progress, Ready to Ship, Awaiting
Assignment SOs.
Quick / Detailed view toggle — Detailed auto-expands every card body.
New field mrp.workorder.x_fc_assigned_user_id (indexed, tracked) —
the worker currently owning this step. Will be the pivot the Tablet
Station filters on in Chunk 4.
Three new endpoints:
/fp/manager/overview — dashboard snapshot (30s auto-refresh)
/fp/manager/assign_worker — set user on a WO
/fp/manager/assign_tank — swap tank on a WO
/fp/manager/take_over — manager claims the WO (no-show coverage)
Controller is graceful when mrp module isn't installed (empty overview,
no crash) and when the bridge_mrp assignment field isn't present (falls
back to showing all active WOs as "unassigned").
Verified: 4 WOs assigned across 2 users, overview queries return the
expected counts, res.groups.user_ids (Odoo 19 API, not deprecated .users).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
095d9f487c |
feat(bridge_mrp): SO workflow stage + contextual buttons (CHUNK 3/4)
Sale Order form now guides the user through the next step without
making them navigate between screens.
New computed field sale.order.x_fc_workflow_stage with 12 stages:
draft → awaiting_parts → inspecting → accept_parts → assign_work
→ in_production → ready_to_ship → shipped → invoicing
→ paid → complete (+ cancelled)
Driven by SO.state + x_fc_receiving_status + MO state +
delivery.state + invoice payment state.
Five contextual header buttons (only 1-2 visible at any time,
fusion_claims pattern — invisible="x_fc_workflow_stage != 'foo'"):
Mark Inspecting → flips receiving to 'inspecting'
Accept Parts → flips receiving to 'accepted' + SO status
to 'inspected', unlocks manager assignment
Assign To Me & Release → manager claims the job, confirms all draft
MOs (which auto-generates WOs already)
Open Shop Floor → jumps to Plant Overview during production
Mark Shipped → closes open delivery records → triggers
auto-invoice per strategy
Info banner shows current stage + assigned manager on the sheet so
users always know where they are.
New fields:
sale.order.x_fc_assigned_manager_id (Many2one res.users, tracked)
mrp.production.x_fc_assigned_manager_id (Many2one, propagated on
MO confirm)
MO.action_confirm() now pulls the assigned manager from the SO (or
falls back to SO.user_id) and sets it on the MO — sets up the
Manager Dashboard (chunk 2) and role-based assignment (chunk 4) to
filter "my jobs" cleanly.
Smoke-tested across 10 demo SOs — stages compute correctly:
S00028 → ready_to_ship, S00027-25 → awaiting_parts,
S00023-20 → complete/shipped, S00029 → draft.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
28dd7fdd76 |
feat(certificates): per-customer document preferences (CHUNK 1/4)
Customers can now pick which shipping-time documents they actually want instead of the shop remembering it per account. Four booleans on res.partner (only shown on companies, not contacts): x_fc_send_coc (default True) Certificate of Conformance x_fc_send_thickness_report (default True) Thickness readings x_fc_send_packing_slip (default True) Packing slip PDF x_fc_send_bol (default False) Bill of Lading Surfaced in a "Plating Documents" page on the customer form. Two downstream gates: 1. fp.notification.template._collect_attachments() now reads the flags when attaching CoC / thickness / packing / BoL PDFs to the shipping confirmation email. Flags missing on the partner (e.g. legacy customers) fall back to the original defaults so nothing regresses. 2. mrp.production.button_mark_done() only auto-creates the quality documents the customer wants. A customer that unchecks both CoC and thickness gets zero certs auto-generated — shop can still create them manually if needed. Note: today a standalone thickness-only report template doesn't exist, so when a customer asks for thickness only (CoC off, thickness on) the dispatcher still attaches the CoC PDF (which carries thickness data) but with CoC creation gated off. A dedicated thickness-only template is a follow-up. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
f94be9dfa9 |
fix(part-catalog): upload slot + swapped Number/Name + smart buttons
Three fixes on fp.part.catalog form:
1. 3D Model upload actually works now. The old field exposed only a
Many2one search dropdown — no way to add a new file. Added a
Binary upload slot (model_upload + model_upload_filename) that
fires an onchange which wraps the bytes in an ir.attachment and
links it to model_attachment_id. The upload slot is hidden once a
model is already attached, so the current file stays visible.
Accepts STEP/STP/STL/IGES/IGS/BREP. Auto-runs the surface-area
calculation after attach, same as before.
2. Part Number is now the big <h1> title, Part Name is the smaller
field underneath. Matches how plating shops actually identify
parts (by customer part number, not a free-text name). Swapped
column order in the list view too — Part Number first, then Name.
3. Four smart buttons now on the part form:
- Customer → opens res.partner record
- Sale Orders (already existed)
- Work Orders → filtered mrp.workorder list across SOs for this part
- Quotes (already existed)
- Revisions → shown only when 2+ revs exist, opens the revision
tree filtered by root part
New compute fields workorder_count + revision_count feed the
statinfo widgets, with matching action_view_customer,
action_view_workorders, action_view_revisions handlers.
Verified on demo data:
VS-ESMC6H00801P01 → SO=2, WO=18, REV=2
VS-PQR8440 → SO=1, WO=9, REV=3
All counts light up, buttons drill in cleanly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
70fe10c214 |
fix(configurator): money fields now show $ everywhere
Root cause: pricing.rule records had currency_id=NULL because the
default=lambda only applies on new records. Monetary fields without a
currency silently render as plain numbers — no $ symbol.
Fixes:
1. currency_id now required=True on fp.pricing.rule, fp.treatment,
fp.customer.price.list, fp.quote.configurator, fusion.plating.quote.request
— so it can never be missing going forward.
2. post_init_hook + matching backfill helper in
fusion_plating_configurator/__init__.py pins the company currency
on any existing records that were created before the required flag.
Ran on upgrade → all 4 pricing.rule rows now have CAD/$.
3. Flipped two remaining Float money fields to Monetary:
- fp.job.consumption.unit_cost and total_cost (were Float digits=4/2)
- (mrp.workorder.x_fc_workcenter_cost_hour stays Float — it is a
related field from core mrp.workcenter.costs_hour which is Float)
4. Every Monetary field reference in views now has explicit:
widget="monetary" options="{'currency_field': 'currency_id'}"
Previously Odoo's default rendering dropped the $ in some contexts.
Touched: fp_pricing_rule_views (list + form), fp_treatment_views,
fp_customer_price_list_views (already done), fp_quote_configurator_views
(list + form shipping/delivery/calculated/override), fp_quote_request_views
(list + form), fp_job_consumption_views, mrp_production_views job-costing
group, direct-order wizard (already done earlier).
5. Unit / % suffix polish as we went: rush_surcharge_percent shows "%",
default_duration_minutes shows "min" on treatment form, treatment list
labels duration column.
Verified: all 4 pricing rules now render "$0.45", "$0.85" etc; 62 records
across 6 models all have currency_id populated; zero remaining Float $
fields in the codebase.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|