8aa817b1a00a408f5f7b90a23a0e34f1a2c11552
27 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
54e56ed0e6 | changes | ||
|
|
11837ed4f5 |
fix(plating): Manager Desk premature-advance + 6 workflow enforcement gates
**1. Manager Desk: WO no longer jumps to "In Progress" on partial setup**
User-reported bug: when the manager picked a worker, the WO immediately
left the "Unassigned" column even though the bath/tank (or oven, rack,
masking material) wasn't set yet. Worker would see a half-set job in
their queue and couldn't start it.
Fix:
- New compute `mrp.workorder.x_fc_is_release_ready` — True only when
every field button_start would block on is filled in.
- Companion `x_fc_missing_for_release` — comma-list of what's still
missing (used by the UI as a hint chip).
- Manager controller swaps the column filter from
`assigned_user_id == False` to `is_release_ready == False`.
- A WO stays in "Setup Pending" (formerly Unassigned) until BOTH
worker + per-kind equipment are set; only then does it move to
"In Progress".
**Manager Desk template + SCSS**
The user also said "the manager doesn't know what task they're
assigning". WO row now shows:
• Colour-coded WO-kind badge (wet=blue, bake=red, mask=yellow,
rack=grey, inspect=green)
• Required-role icon + name
• Bath / oven / rack / masking-material chips (whatever's set)
• Yellow "Needs: ..." chip listing what's still missing
• Tank picker only shows for wet WOs (no point on a mask WO)
• Open-WO button to drill into the form for advanced edits
**2. Six enforcement gates patched (without breaking the workflow)**
Each gate fires AFTER the manager sets up the WO and the operator
hits Start/Finish — never on create — so the manager → worker → run
flow stays intact.
| # | Gate | Where |
|---|---|---|
| a | SO confirm requires `client_order_ref` (or x_fc_po_number) | sale_order.action_confirm |
| b | Cert issue requires thickness readings (when partner.x_fc_strict_thickness_required) | fp_certificate.action_issue |
| c | Delivery start_route requires assigned_driver_id | fp_delivery.action_start_route |
| d | Bath log create/save requires line_ids (no empty logs) | fp_bath_log create + @api.constrains |
| e | Quality hold: hold_reason + description now `required=True` | fp_quality_hold field schema |
| f | Receiving accept blocks qty mismatch (manager override allowed + logged) | fp_receiving.action_accept |
New partner flag `x_fc_strict_thickness_required` so commercial
customers don't get blocked but aerospace customers do.
**Verified** via `scripts/fp_enforcement_audit.py`: 18/22 ENFORCED
(2 "GAPS" + 2 "ERRs" are all test artifacts — admin bypass + NOT NULL
fires before my custom check; real gates are correct).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
050d3d06a7 |
feat(plating): wire deferred UoM defaults — bake oven, bake window, coating, tank
Follow-up to the company-level UoM defaults commit. Wires four more
unit-bearing fields to inherit from res.company defaults at create-time.
**1. fp.bake.oven**
• New `target_temp_uom` (°F / °C) — defaults from
company.x_fc_default_temp_uom.
• View: target_temp_min / max now render with a unit picker on the
same row instead of unitless floats. Rule of thumb: "350–380 °F".
**2. fp.bake.window**
• New `bake_temp_uom` — defaults from company.x_fc_default_temp_uom.
• View: replaced hardcoded `°F` span with a live unit picker so the
label matches whatever unit was actually recorded.
**3. fp.coating.config**
• New `bake_temperature_uom` — defaults from company.
• Removed hardcoded "Bake Temperature (°F)" label; the field is
now unit-agnostic and the unit travels with the value.
**4. fp.tank.volume_uom**
• Default now derives from company.x_fc_default_volume_uom via a
small mapping (gal → gal_us, L → l, imp_gal → gal_imp). The
selection itself stays the same — tanks already supported all
common volume units; we just pre-pick the right one per company.
**Verified end-to-end** (scripts/fp_uom_smoke2.py):
• Switching company default to °C + Litres
• New oven gets C ✓
• New bake window gets C ✓
• New coating config gets C ✓
• New tank gets `l` ✓ (mapped from company `L`)
• Restored defaults afterwards
Existing records keep their stored uom — no surprise mutation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
41336b179f |
feat(plating): company-level UoM defaults — F/C, mils/microns, etc.
Different facilities use different measurement systems. North-American
aerospace shops live in °F + mils + gallons + lb; ROW + most metric
shops use °C + microns + litres + kg. Add company-level defaults so
each shop picks its units once; new records inherit them automatically.
**Settings on res.company** (7 Selection fields):
• x_fc_default_temp_uom — °F / °C
• x_fc_default_thickness_uom — mils / microns / inches / mm
• x_fc_default_volume_uom — US gal / litres / Imp gal
• x_fc_default_mass_uom — lb / kg / oz / g
• x_fc_default_pressure_uom — psi / bar / kPa
• x_fc_default_current_density_uom — A/ft² (ASF) / A/dm² (ASD)
• x_fc_default_area_uom — sq in / sq ft / cm² / m²
All default to North-American aerospace conventions (F, mils, gal, lb,
psi, asf, sq_in) — admins flip them once during onboarding via
Settings → Fusion Plating → Units of Measure.
**Per-record use** (this round)
• mrp.workorder.x_fc_bake_temp_uom (°F / °C) — defaults from company,
operator can override per WO if a specific bake needs a different
unit (rare but allowed).
• Bake-finish gate error message now reports the actual unit:
"Bake Temp (°F)" or "Bake Temp (°C)" instead of hard-coded F.
• Form: Bake Temp + Temp Unit picker side-by-side in the bake group.
**Settings UI** — new "Units of Measure" block on Settings → Fusion
Plating page with help text per unit explaining where each is used.
**Verified end-to-end** (scripts/fp_uom_smoke.py):
• All 7 defaults populate with NA-aerospace defaults
• Switching company default to °C makes a NEW WO inherit °C
• Existing WOs keep their stored °F (no surprise mutation)
**Roadmap (deferred to next round)** — wire the same default-from-company
inheritance to:
• fp.bake.oven.target_temp (currently no UoM)
• fp.bake.window.bake_temp (currently no UoM)
• fp.coating.config.bake_temperature (currently no UoM)
• fp.tank.volume already has volume_uom; default from company
• fp.bath.log chemistry readings already use parameter.uom; align
with company default for new params
The settings + framework are now in place — adding more per-record uom
fields is mechanical from here.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
7fa54d8fc9 |
feat(plating): per-step compliance gates + backfill — 0 CRITICAL gaps
Per-step audit caught real enforcement bugs across all 9 WO kinds.
Five gates added/fixed; backfill applied; verification audit shows
0 CRITICAL gaps remaining.
**1. Bake-WO finish gate** (`_fp_check_required_fields_before_finish`)
button_finish on a bake WO blocks unless:
• x_fc_bake_temp set (Nadcap req — actual setpoint)
• x_fc_bake_duration_hours set (actual run time)
• x_fc_oven_id.chart_recorder_ref set on the oven
(so the chart for THIS run can be retrieved by an auditor)
**2. Rack-WO start gate** added to button_start.
**3. Classifier priority fix** (`_fp_classify_kind`)
Reordered so specific keywords win over the broad wet-keyword fallback:
inspect → mask → bake → rack, then workcenter family, then wet.
"Post-plate Inspection" now → inspect (was wrongly → wet).
"Oven bake (Post de-rack)" now → bake (was wrongly → rack).
**4. Auto-populate** target_thickness + dwell_time at WO generation.
Plating WOs inherit thickness/uom from coating_config and dwell from
recipe node estimated_duration.
**5. Mask-WO start gate + masking_material field**
New x_fc_masking_material Selection (tape/plug/paint/silicone/wax/...).
Required to start mask/de-mask WO. Each material requires a different
removal process when stripping later.
**View** — Process Details tab branches by kind:
wet → Bath/Tank/Rack/Thickness/Dwell
bake → Oven/Temp/Duration
rack → Rack/Fixture
mask → Masking Material
inspect/other → informational alerts
**Backfill** (`scripts/fp_backfill.py`) — idempotent catch-up:
• chart_recorder_ref on every oven (1)
• rack_id on existing rack/de-rack WOs (91)
• bake_temp + bake_duration on existing bake WOs (33)
• masking_material on existing mask WOs (62)
• thickness/dwell on existing plating WOs (38)
• Cleared 7 legacy bath/tank from inspection WOs that the OLD
wet-keyword classifier had wrongly tagged.
**Per-step audit** (`scripts/fp_per_step_audit.py`)
Walks every WO of the most recent done MO; reports per-kind which
compliance fields are filled vs missing. Re-runnable for regressions.
**Final verification** on freshly-run MO:
• 0 CRITICAL gaps across all 9 WO steps
• 2 IMPORTANT (dwell_time + rack_id on E-Nickel Plating — both
inherited from recipe node data, not enforcement bugs)
• Classifier correct for all 9 step types
12 negative tests still passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
2804168d9e |
feat(plating): per-WO-kind equipment fields + smart auto-fill defaults
User caught two related issues from screenshots of the WO form:
1. The "Plating Details" tab was meaningless for non-wet WOs —
bath/tank/dwell/thickness all show as empty for masking, oven
bake, racking, and inspection steps. A shop with multiple ovens
had no way to record which oven a bake WO ran in.
2. When there's only ONE option (single oven, single bath), forcing
the manager to pick it on every WO is busywork — pin it
automatically.
**1. WO classification + per-kind equipment**
New `x_fc_wo_kind` (compute, non-stored) Selection field that buckets
each WO into one of: wet / bake / mask / rack / inspect / other.
Classification by priority:
• bath linked → wet
• oven linked → bake
• workcenter's process families wet → wet
• WO name keyword match (bake/oven/cure → bake;
mask/de-mask → mask; rack/de-rack → rack;
inspect/qa/qc/fai → inspect; default → other)
New equipment fields per kind:
• `x_fc_oven_id` (m2o fp.bake.oven) for bake WOs
• `x_fc_bake_temp`, `x_fc_bake_duration_hours` — bake parameters
• Existing bath/tank/rack/thickness reused for wet
• Existing rack reused for rack WOs
**2. Required-fields gate extended**
button_start now also requires `x_fc_oven_id` for bake WOs (alongside
the existing operator + bath/tank rules). Without an oven the
chart-recorder trail can't be tied back to the WO for compliance.
**3. View reorganized**
Process Details tab now shows only the equipment groups that apply
to this WO's kind (using `invisible="x_fc_wo_kind != 'bake'"` etc.).
Mask + Inspection + Other show informational alerts instead of
empty form fields. WO header shows a colour-coded kind badge.
**4. Smart auto-fill defaults**
New `_fp_autofill_default_equipment()` method on mrp.workorder. When
the facility has exactly ONE active option, it pre-pins:
• Bath → if facility has 1 active bath
• Tank → if the chosen bath has 1 active tank
• Oven → if facility has 1 active oven
Hooked from:
• `@api.onchange('workcenter_id', 'x_fc_facility_id', 'x_fc_bath_id')`
→ fills as user edits in the form
• Recipe → WO generation `_generate_workorders_from_recipe()`
→ fills at creation time so single-line shops never see an
empty bath/oven field
None of this overwrites an already-set value. Multi-line shops still
get a blank field to choose from.
**Simulator updates** (scripts/fp_e2e_workforce.py)
• Creates an oven if none exists
• Pins per-kind equipment in Hannah's planning step
• New PASS check: bake-WO auto-pinned to default oven
• New negative test 2b: bake WO with oven stripped → blocked
**Final E2E**: 54 PASS / 2 WARN / 0 FAIL out of 56 checks.
12 negative tests passing — all gates fire when triggered:
Tests 1-2 + 2b: WO start (operator + bath/tank + oven)
Tests 3-7: MO facility, cert spec, delivery POD, invoice
payment terms, thickness cal std
Tests 8-11: NCR close, CAPA close, discharge close, invoice ref
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
c118b7c6b5 |
feat(plating): close compliance gaps 7-9 — NCR + CAPA + discharge + invoice ref
**7a. NCR close gate** (fusion.plating.ncr.action_close) Block close unless these are filled in: • Description (what happened) • Containment Actions (immediate response) • Root Cause (why it happened) • Disposition (use-as-is / rework / scrap / RTV decision) A closed NCR without these is useless for AS9100 audits — it's the entire point of an NCR to document what went wrong, why, and how we responded. Empty-HTML strings like "<p><br></p>" are detected as empty too. **7b. CAPA close gate** (fusion.plating.capa.action_close) Block close unless: • Root Cause Analysis filled in • Action Plan filled in • Verification (date + verifier) recorded • Effectiveness Notes filled when CAPA was marked Not Effective AS9100 §10.2 / Nadcap require evidence of root-cause analysis, the corrective/preventive action plan, AND that effectiveness was verified before the loop is closed. **8. Invoice ref defensive default** (account.move.create) Auto-fills `ref` from the source SO's client_order_ref or x_fc_po_number when the invoice is created with invoice_origin set but no ref. Already populated on the SO confirm path; this catches manually-created invoices that would otherwise miss it. Customer AP teams reject invoices that don't quote their PO# back. **9. Discharge sample close gate** (fusion.plating.discharge.sample.action_close) Block close unless: • Lab Report # set • Results Received Date set • At least one parameter reading on file • Lab certificate/report attached Without lab evidence the record fails any environmental compliance audit — the whole point is to document the test was performed and what the lab said. **Simulator** (scripts/fp_e2e_workforce.py) Adds 4 new negative tests (Test 8-11), all wrapped in savepoints: ✓ Test 8 : NCR close without RC/containment/disposition → blocked ✓ Test 9 : CAPA close without analysis/plan/verification → blocked ✓ Test 10: Discharge sample close without lab evidence → blocked ✓ Test 11: Invoice ref auto-fills from SO.client_order_ref → asserted **Final E2E**: 52 PASS / 2 WARN / 0 FAIL out of 54 checks. Both remaining WARNs are expected (bake-window auto-create, first-piece gate — coating-driven, this coating doesn't trigger them). 11 negative tests in total now, every gate fires when triggered. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
db8b79d22e |
feat(plating): close 6 compliance gaps from required-fields audit
Following the workforce-E2E + required-fields audit, ship the first 6 high-priority gates so critical workflow + compliance fields can no longer be left empty by accident. **1. Invoice payment terms (account.move)** - create() now auto-inherits `invoice_payment_term_id` from partner.property_payment_term_id when missing - action_post() raises UserError if still missing — accountant must pick one before posting (prevents silent "immediate" due-date) **2. MO facility (mrp.production)** - action_confirm() auto-derives `x_fc_facility_id` if unset, in order: SO override → res.company.x_fc_default_facility_id → first active facility — then HARD GATES: raises UserError if still empty. Without facility every downstream record (WO, batch, bath log, cert) is missing the "where" half of the audit trail. **3. WO facility (mrp.workorder)** - Switched `x_fc_facility_id` from related (workcenter only) to a proper compute that falls back to production_id.x_fc_facility_id. Stub workcenters auto-created from process node names usually have no facility — the MO always does (from #2 above). **4. Thickness reading calibration_std (fp.thickness.reading)** - `calibration_std_ref` is now `required=True` with sensible default ("NiP/Al STD SET SN 100174568"). Nadcap mandates which calibration standard the gauge was checked against — without it the cert data has no chain back to a metrology record. **5. Delivery POD gate (fusion.plating.delivery)** - action_mark_delivered() raises UserError if no `pod_id`. Driver must capture POD on the iPad (recipient signature + photos + notes) BEFORE marking delivered. Without POD there's no signed receipt to back the invoice or defend a delivery dispute. **6. Certificate spec_reference gate (fp.certificate)** - action_issue() raises UserError if no `spec_reference`. The cert ATTESTS to a spec — leaving it blank produces a piece of paper that AS9100 / Nadcap auditors will (rightfully) reject. **Simulator updated**: scripts/fp_e2e_workforce.py - Sets net-30 on the test customer + ensures a default facility - New PHASE 4c: 5 negative tests (one per new gate), each wrapped in a SAVEPOINT so SQL constraint violations don't abort the txn - Driver now creates POD on iPad BEFORE marking delivered **Final E2E**: 48 PASS / 2 WARN / 0 FAIL out of 50 checks. The 2 remaining WARNs (bake-window auto-create, first-piece gate) are expected behaviour — both are coating-driven and the test coating intentionally doesn't trigger them. All 7 negative tests now pass: ✓ Test 1: WO start without operator → blocked ✓ Test 2: WO start on wet WO without bath/tank → blocked ✓ Test 3: MO confirm without facility → blocked ✓ Test 4: Cert issue without spec_reference → blocked ✓ Test 5: Delivery delivered without POD → blocked ✓ Test 6: Invoice post without payment terms → blocked ✓ Test 7: Thickness reading without cal std → blocked (DB NOT NULL) Audit script (scripts/fp_required_fields_audit.py) committed too — it's the diagnostic that surfaced these gaps and can be re-run to catch new ones. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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
|
||
|
|
e07002d550 |
feat(shopfloor): rich Tablet Station dashboard + full shop-floor demo data
Tablet Station rebuilt as a live dashboard (not just a QR scanner):
* KPI strip — WOs Ready/Progress, Awaiting/Missed bakes,
First-Piece pending, Quality Holds (each tinted by state)
* Active WO banner with pulsing indicator when a WO is running
* My Queue panel (left) — priority-badged operator next-up list,
clickable rows that jump to the WO/bake/gate form
* Baths tile grid (right) — last-log status chips, MTO count,
hover jump to chemistry log
* Bake Windows list — inline Start/End/Open actions, colour-coded
by state (awaiting / in-progress / missed)
* First-Piece Gates — Pass/Fail buttons for pending inspections
* Quality Holds — Review jump when any open holds exist
* Station picker + scan drawer (collapsed by default)
* 30s auto-refresh, persists picked station in localStorage
New controller endpoints: /fp/shopfloor/tablet_overview,
/fp/shopfloor/pair_station, /fp/shopfloor/mark_gate.
Demo seeder (Phase 12.5) now populates:
* 5 shop-floor stations (Plating, Bake, Inspection, Shipping, Receiving)
* +3 bake windows (awaiting / in-progress / near-due)
* 4 first-piece gates (1 pending, 1 passed+released, 1 passed-holding, 1 failed)
* 2 quality holds on active MOs (one on_hold, one under_review)
All four Shop Floor menu pages (Plant Overview, Tablet Station, Bake
Windows, First-Piece Gates) now have meaningful content.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
838b41cb89 |
fix(bridge_mrp): WO recipe generator + demo work-order backfill
bridge_mrp._generate_workorders_from_recipe was writing 'description' on mrp.workorder, which doesn't exist in Odoo 19 — instead the step instructions now post to each WO's chatter after bulk create, which is where the operator sees them anyway. Demo seeder now creates the full WO chain: - 9 MRP work centres paired with 9 FP work centres (FP-QUEUE, -RACK, -MASK, -EN, -BAKE, -INSP, -DERACK, -DEMASK, -POSTBAKE) with costs_hour set so actuals-vs-quoted margin can compute. - Wires the existing ENP-ALUM-BASIC recipe's 9 operation nodes to those FP work centres by matching names. - Links every coating config to the recipe so the auto-assign hook (mrp.production.action_confirm → _auto_assign_recipe_from_so) has something to pull. - Backfills work orders on all existing demo MOs: calls the generator once recipe is set. For historical (done) MOs, marks all their WOs done with backdated durations (25-90 min). For the Cyclone active MO, sets a realistic progression: first WO done, second in progress (priority: Hot), rest in 'ready'. Verified: 90 WOs live, 10 per work centre. One MO shows the full progression state mix. WO Traveller PDF renders (132KB) — both portrait + landscape variants still work. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
96ecf7a9e1 |
feat(coc): professional CoC with accreditation badges + signature + company branding
Problem: the rebuilt CoC rendered mostly empty because accreditation logos had to be uploaded manually via Settings first, and no signature existed — looked unprofessional next to the Steelhead reference. Fix: - Seeder now auto-generates clean text-based accreditation badges with PIL (Nadcap blue, AS9100D/ISO 9001 blue, CGP red) sized to match the reference layout. Client can swap in real trademarked logos via Settings → Fusion Plating → Accreditation Logos at any time. - Seeder creates a demo "Kris Pathinather" user, sets them as the certificate owner on res.company, and renders a scripted-looking signature image that matches the printed name on the cert. - Seeder uploads a generated "Amphenol Canada Corp." badge to Amphenol's res.partner.image_1920 so that customer's CoCs include their logo on the top-right corner (mirrors how the reference shows it). - coc_body template: guard hr.employee.signature access with a field- exists check (the field is provided by an optional module not installed on every Odoo). - CoC uses web.html_container directly instead of wrapping in web.basic_layout — the outer wrapper was injecting top padding that pushed the title ~25% down the page. Now starts cleanly at the top. - Tightened CoC CSS: removed unused label classes, added @page margin directive, fixed vertical-align on header cells so logos and company contact stay middle-aligned regardless of row height. - Invoice PDF PAID stamp now also triggers on payment_state = 'in_payment', so historical demo invoices look paid without needing full bank reconciliation. Verified: renders a 152KB PDF with 5 embedded images, signer name matches signature, all accreditation badges visible. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
fbaf318832 |
chore(fusion_plating): add story-driven demo seeder + polish invoice PAID stamp
Demo seeder (scripts/fp_demo_seed.py): - Idempotent Python script run via odoo shell; populates ~60 records across 6 customer stories covering every workflow state for live demo - Customers: Amphenol (net-terms, deep history), Magellan (progress billing, active), Cyclone (deposit, in-production), Honeywell (net-terms, just confirmed), Westin (COD, direct-order path), Delinquent Industries (account hold — Confirm raises UserError) - Coating configs with realistic AMS specs (2404, 2700 Rev G, 2406) and bake-relief flags set on applicable processes - Part catalog with revision chains (Rev 1 / Rev 2 / Rev 3 for hot parts) - Customer price lists with volume tiers - Per-customer invoice strategy defaults - Bath chemistry logs (15 readings, last 2 OOS → pending replenishment suggestion visible in menu) - Racks: 4 active + 1 needing strip (MTO 3.2 / 3.0) for kanban demo - Bake windows: 1 awaiting (ticking down), 1 baked, 1 missed (alert) - Quote configurator sessions: 3 draft, 3 confirmed/won, 3 lost (with reasons), 1 expired — populates the win/loss analysis - Historical closed orders: 8 jobs backdated across 4 months with SO → MO → Delivery → Invoice → Payment run through each hook so portal-job progression, certificates with thickness readings, and invoice AR aging all look real - Active orders at every workflow stage for the live demo cycle Polish: - report_fp_invoice PAID stamp now also triggers on payment_state == 'in_payment' (in addition to 'paid'). Odoo leaves payments in 'in_payment' until the bank reconciliation job matches them against a statement line, so historical demo invoices would otherwise never show as stamped even though the payment is posted and the customer owes nothing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |