Commit Graph

399 Commits

Author SHA1 Message Date
gsinghpal
e0eacc2530 feat(promote-customer-spec): Phase D — reports + tablet payload include spec
Reports updated to print Specification (with revision via display_name):
- report_fp_sale.xml — header sections show "SPECIFICATION" instead
  of "COATING CONFIG", reads doc.x_fc_customer_spec_id (added on
  sale.order via quality inherit, computed from line.customer_spec_id)
- report_fp_wo_sticker.xml — propagates _spec alongside _coating
- fusion_plating_reports/report_fp_job_traveller.xml — header row
  now shows Specification (falls back to coating)
- fusion_plating_jobs/report_fp_job_traveller.xml — same fall-back
- fusion_plating_jobs/report_fp_job_sticker.xml — _spec added

sale.order.x_fc_customer_spec_id added as a stored compute on
sale.order (in quality) so reports can render order-level spec.
Mirrors the line's first spec; updates on line edit.

Tablet payload (shopfloor_controller.py):
- spec_label added to the job payload dict
- defensive 'customer_spec_id' in job._fields check (shopfloor doesn't
  depend on quality — circular if added)

Portal: deferred (same circular-dep issue, more substantial UI rewrite
needed; Phase E backlog item).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 01:30:05 -04:00
gsinghpal
c637f82ae2 feat(promote-customer-spec): Phase C — pricing, quality, job, cert re-keyed
Pricing:
- Quality inherit on fp.pricing.rule adds customer_spec_id + recipe_id
- Quality inherit on fp.quote.configurator adds customer_spec_id field
  + extends _find_matching_rule with priority chain:
    spec (+8) > recipe (+6) > coating (+4) > material (+2) > cert (+1)
- View inherit surfaces both new pickers on the rule form

Quality points:
- fp.quality.point now has customer_spec_ids + recipe_ids M2M filters
- Matcher (_matches + _find_matching) accepts new args
- Hook overrides on SO confirm + job confirm/done + step finish
  pass spec/recipe context through to the matcher
- View surfaces both new M2M widgets

Job:
- jobs/sale_order.py wires x_fc_customer_spec_id from SO line to
  fp.job.customer_spec_id on action_confirm

Cert:
- Quality inherit on fp.certificate adds customer_spec_id field +
  create() override auto-fills spec_reference from spec.code+revision
  Resolution priority: explicit spec_reference > cert.customer_spec_id
  > SO line spec (with print_on_cert) > legacy coating fallback

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 01:23:06 -04:00
gsinghpal
7cafab1b9f feat(promote-customer-spec): Phase B — two-picker SO line UX
Spec-side picker (x_fc_customer_spec_id / customer_spec_id) added on:
- sale.order.line (via quality inherit — onchange autofill, create()
  fallback to part default, _prepare_invoice_line carry)
- account.move.line (via quality inherit — invoice rendering)
- fp.part.catalog (via quality inherit — x_fc_default_customer_spec_id)
- fp.direct.order.line (via quality inherit — wizard picker + autofill)
- fp.direct.order.wizard (action_create_order post-creates spec on SO line)

Thickness picker switched to fp.recipe.thickness (replaces coating-scoped):
- sale.order.line.x_fc_thickness_id comodel + domain rewired to recipe
- account.move.line + fp.delivery same
- fp.direct.order.line.thickness_id same

View inherits in quality add Specification picker next to legacy
Primary Treatment column on:
- SO form line tree
- part catalog Default Treatments block
- direct-order wizard line tree + drawer

Wizard files (fp.contract.review.client.email.wizard) pulled from
entech into the repo — they were ahead of the repo. Quality __init__
now imports wizards/.

Legacy x_fc_coating_config_id + treatment_ids remain visible during
transition; Phase E removes them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 01:16:25 -04:00
gsinghpal
c96f27b96c feat(promote-customer-spec): NADCAP recipe lock (Phase A+)
Per client review: NADCAP-qualified recipes need manager-only edit
permission. Word-doc external approval workflow stays outside ERP;
this is the in-app enforcement.

- New field fp.process.node.is_locked (recipe root)
- write() override blocks non-manager edits when recipe root is_locked
  Lock checks via recipe_root_id so child ops/steps are also protected
  Manager bypass via group + env.su (sudo) bypass for system jobs
- Amber "LOCKED — Manager Edit Only" ribbon at top of recipe form
- Toggle on Specification & Bake page under "Change Control (NADCAP)"
- Spec doc updated with Decision 6.5 + backlog from client review:
  approvals list, doc control auto-sync, oven recorder sync, SOP
  word-doc workflow, final-inspection signoff on cert

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 00:55:07 -04:00
gsinghpal
406cac1362 feat(promote-customer-spec): Phase A — recipe + spec foundation
- Add fp.recipe.thickness model (replaces fp.coating.thickness, scoped to recipe root)
- Add spec metadata + bake-relief fields to fusion.plating.process.node (recipe root):
  phosphorus_level, thickness_min/max/uom, thickness_option_ids,
  requires_bake_relief + bake_window_hours/temperature/duration
- Add recipe_ids M2M + print_on_cert to fusion.plating.customer.spec
- Add applicable_spec_ids reverse M2M as inherit in fusion_plating_quality
  (avoids circular dep — core can't reference customer.spec which lives in quality)
- Surface new fields on recipe form ("Specification & Bake" notebook page)
- Surface recipe linkage on customer spec form

Pure additive. Foundation for Phases B-E.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 00:50:17 -04:00
gsinghpal
13fd0712d9 docs(fusion_plating): add Promote Customer Spec design + implementation plan
- Spec: retire fp.coating.config + fp.treatment, promote fusion.plating.customer.spec
- Two-picker SO line UX (Specification + Recipe), aerospace-correct audit posture
- Plan: 5 phases (foundation, SO line, pricing/quality/job/cert, reports/tablet/portal, removal)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 00:23:22 -04:00
gsinghpal
f07e1bcce1 fix(chatter): wrap HTML message_post bodies in Markup() — 4 sites
Four message_post calls were passing strings with HTML tags as
plain `body=_(...)` instead of `body=Markup(_(...))`. Odoo escapes
non-Markup strings, so the chatter rendered "<b>QA Review failed</b>"
as literal text instead of bolding it.

Original bug surfaced via the Contract Review (QA-005) flow:
  body: "&lt;b&gt;QA Review failed&lt;/b&gt; by Garry Singh. Awaiting
  client information.&lt;br/&gt;&lt;b&gt;Reason:&lt;/b&gt;&lt;br/&gt;
  &lt;div data-oe-version=\"2.0\"&gt;Need to get updated
  drawing...&lt;/div&gt;"

Audit scan turned up three more identical patterns:

  fusion_plating/models/fp_parent_numbered_mixin.py:118
     "Issued <strong>%s</strong> to ..."
  fusion_plating_jobs/models/sale_order.py:282
     "Confirmed quote <strong>%s</strong> as <strong>%s</strong>."
  fusion_plating_quality/models/fp_contract_review.py:430
     "<b>QA Review failed</b> by ... <b>Reason:</b><br/>%(reason)s"
  fusion_plating_quality/models/fp_contract_review.py:524
     "<b>QA Review completed</b> by ... <b>Special Instructions
      captured:</b><br/>%(notes)s"

Fixes:
- Wrapped each body=_(...) with Markup(_(...)) using the
  Markup(template) % values pattern (auto-escapes the substituted
  values; user-supplied free text stays safe).
- For Html-field substitutions (qa_failure_reason,
  special_instructions), explicitly wrapped the value in Markup()
  so already-formatted HTML editor content (with data-oe-version="2.0"
  wrapper divs) flows through without being re-escaped.
- Added `from markupsafe import Markup` to the two files that
  didn't already import it (mixin + contract_review).

Drift cleanup: pulled the 180-line newer fp_contract_review.py
from entech to the local repo (added action_qa_review_failed,
action_open_client_email_wizard, action_view_client_emails,
action_complete_after_info, awaiting_info state, qa_failure_reason
+ special_instructions Html fields, etc. that had been edited on
entech without being committed).

Tested by re-posting via odoo shell on review 10: body now stores
"<b>QA Review failed</b>..." with literal HTML tags instead of
the double-escaped "&lt;b&gt;..." entities. Old chatter records
with the bad escape stay as-is in the audit trail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 08:41:39 -04:00
gsinghpal
e7c6960de9 feat(sticker): restore customer-name secrecy cover (ABC-MANU)
Body Customer row now prints a 3-and-4 short code instead of the
full company name. Operators see "ABC-MANU" on the floor; visiting
customers / unauthorised passers-by can't immediately tell whose
parts are on which rack.

Rule (per user's reference design):
  - First 3 chars of first word + "-" + first 4 chars of second word
  - Single-word names → just first 3 chars
  - All uppercase
  - Strips non-alphanumeric per word so "St. John's Mfg." doesn't
    leak punctuation into the slice

Logic lives in the shared inner template, so all 4 variants pick
it up automatically:
  sale.order     External + Internal Sticker
  fp.job         External + Internal Job Sticker

Verified on fp.job 2635: Customer row now reads "ABC-MANU" (was
"ABC Manufactoring").

Doesn't use the orphaned x_fc_short_code field on res.partner
(that field has no column or compute — broken Studio remnant).
A future spec can replace this inline computation with a proper
stored+inverse field if customers want per-partner overrides.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 08:24:07 -04:00
gsinghpal
cd763fa1d7 chore(sticker): rename External action labels for the variant split
Print menu now shows External + Internal as paired entries:

  sale.order:  External Sticker      / Internal Sticker
  fp.job:      External Job Sticker  / Internal Job Sticker

XML IDs unchanged (action_report_fp_so_sticker /
action_report_fp_job_sticker) so existing bookmarks and
binding_model_id records keep working. print_report_name strings
also updated so the downloaded filename matches the new label.

DB verification:
  fp.job      | External Job Sticker
  fp.job      | Internal Job Sticker
  sale.order  | External Sticker
  sale.order  | Internal Sticker

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 08:07:50 -04:00
gsinghpal
f40f44aafd feat(sticker): add Internal Job Sticker variant on fp.job Print menu
Mirror of the SO Internal variant for fp.job. Same body fields,
same per-box loop; Notes column reads x_fc_internal_description
from the first linked SO line (job.sale_order_line_ids[:1]).
Operator on the shop floor sees ops-internal notes without those
ever appearing on the customer-facing External sticker.

Verified on fp.job 2635 with seeded internal_description: Notes
column reads "INTERNAL JOB: handle with care, no rework on this
batch" — confirms the Job Internal variant's override path mirrors
the SO Internal variant's.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 08:06:12 -04:00
gsinghpal
63bf271725 feat(sticker): add Internal Sticker variant on sale.order Print menu
Same 3-cell + body layout as External; Notes column reads
x_fc_internal_description (Sub 2 internal-description field on the
SO line) instead of line.name. Shop floor gets ops-facing notes
without leaking them to the customer-facing variant.

New action record action_report_fp_so_sticker_internal — binds to
sale.order, appears in the Print menu next to the existing External
sticker. New template report_fp_so_sticker_internal that pre-sets
_notes_content before t-calling the shared inner.

Verified on SO-30019 with a seeded internal_description: Notes
column reads "INTERNAL: rework if any dings on flange. Buff per
WI-104." — confirms the override path is wired through the
defaults-block initialiser, the inner's fallback chain, and the
new outer template.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 08:04:29 -04:00
gsinghpal
974b8a5152 feat(sticker): wire _qty_total in SO + Job External outers
Activates the per-box loop landed in the prior commit. SO External
reads line.product_uom_qty; Job External reads job.qty. Inner
template now renders one sticker per physical box, marking each
with "X / N" in the Qty row.

Verified on fp.job 2635 (qty temporarily set to 3): 3-page PDF
with Qty rows "1 / 3", "2 / 3", "3 / 3" — each page identical
otherwise (same WO#, same QR, same body fields).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 08:02:32 -04:00
gsinghpal
0a32ed2da7 feat(sticker): per-box render loop + Notes override hook
Inner sticker template gains two parameters that outer templates
pre-set:

  _qty_total — total qty for the line/job. Inner wraps the body
    in t-foreach="range(int(_qty_total or 1))" so a qty=5 line
    produces 5 consecutive single-box stickers. Qty row in the
    body switches from "5" to "1 / 5", "2 / 5", ... "5 / 5".
    When _qty_total is missing/0/1, the Qty row keeps showing
    the plain integer (regression-free).

  _notes_content — Notes column source. Existing inner code
    hard-read _line.name; new code accepts an outer override
    and falls back to _line.name. External outers don't set it
    (unchanged behaviour); the new Internal outers (Task 4+5)
    pre-set it to x_fc_internal_description.

Defaults template initialises both new vars to False so the
inner's "outer-supplied OR fallback" pattern doesn't NameError
when called from existing outers that haven't been updated yet.

Verified regression-free: fp.job 2635 (qty=1) renders identically
to its pre-Task baseline — Qty row shows plain "1", Notes from
line.name as before.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 08:00:22 -04:00
gsinghpal
e4681a58c6 fix(jobs): split fp.jobs by thickness + serial on SO confirm
The _fp_auto_create_job grouping key was (recipe, part, coating).
Lines that shared all three but differed in thickness (or serial)
silently collapsed into one fp.job — the second line's thickness/SN
was lost, and any downstream cert printed the first line's values
across both batches. Silent mis-attestation = compliance hole.

Extended the key tuple to (recipe, part, coating, thickness, serial).
Single-line SOs and same-(thickness, SN) multi-line SOs collapse
identically to before. Only lines that previously merged when they
shouldn't have now split into their own fp.jobs.

TDD via test_so_confirm_splits_by_thickness:
  - seeds the part with default_process_id so both lines hit the
    `if recipe:` branch (where the bug lived — the no_recipe branch
    already split correctly per line)
  - confirms 2 jobs after action_confirm with each carrying its
    own thickness via the linked SO line

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 07:57:56 -04:00
gsinghpal
135cbd3a5c docs: implementation plan — sticker multi-part / per-box / Internal+External
7 tasks, bite-sized steps with exact code + commands. TDD on the
backend grouping change (new test_so_confirm_splits_by_thickness);
deploy-and-render-PDF on the QWeb template changes. Each task
self-contained, pushes to entech LXC 111 via the standard pct
exec + cat-pipe path, bumps the module version, and commits.

Task 7 is verification-only — creates a multi-line test SO with
two different thicknesses, renders External + Internal stickers
on both the SO and each spawned fp.job, confirms the box loop
and the Notes variant pattern both work.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 07:47:40 -04:00
gsinghpal
3182ca3c39 docs: design spec — sticker multi-part / per-box / Internal+External
Three problems on the box-sticker stack rolled into one spec:

1. Backend: _create_fp_jobs grouping key collapses lines with
   different thicknesses or SNs into one job. Silent compliance
   hole. Fix: add thickness_id + serial_id to the key tuple.
2. No per-box stickers: a line with qty=5 prints 1 page showing
   "Qty: 5". Want 5 pages with "1 / 5", "2 / 5", ... "5 / 5".
3. No Internal variant: sticker always reads line.name (customer
   facing). Want a parallel variant that reads
   x_fc_internal_description (Sub 2 internal description field).

Renaming: existing actions keep their XML IDs (bookmarks /
binding_model_id records survive). Labels become:
  sale.order:  External Sticker      + Internal Sticker      (new)
  fp.job:      External Job Sticker  + Internal Job Sticker  (new)

All three changes share the same inner template, same files —
ship together. No data migration required; existing fp.jobs are
protected by the idempotency guard.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 07:41:53 -04:00
gsinghpal
677e460438 fix(sticker): wire SN # + Thickness to the correct Sub 5 fields
The values were structurally blank because the variable
resolution was reading the wrong field names:

  Was:  _line.x_fc_serial_number    (doesn't exist)
        _line.x_fc_thickness        (doesn't exist)
  Now:  _line.x_fc_serial_id.name           (M2O fp.serial)
        _line.x_fc_thickness_id.display_name (M2O fp.coating.thickness)

Sub 5 shipped these as Many2one registries (fp.serial,
fp.coating.thickness) — the sticker was guessing at flat
Char-field equivalents that were never created.

Verified on SO-30019: SN # now prints "65767", Thickness now
prints "0.3-0.5 mils" (the en-dash in display_name mojibakes
to "â€"" through wkhtmltopdf's font path on entech, so we
replace en-dash + em-dash with ASCII hyphen-minus before
render — ASCII-only is what label printers want anyway).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 07:25:42 -04:00
gsinghpal
c7b794f604 fix(sticker): drop SO-line sequence suffix + bump Notes type
SO sticker (report_fp_so_sticker):
  Was: "SO-30019 / 10"  (the "/ 10" was line.sequence — Odoo's
       default increment-by-10 — meaningless to the operator)
  Now: "SO-30019"
Multi-line SOs are disambiguated by the body fields (Part #,
Customer, etc.) which already differ per sticker, so the
suffix wasn't earning its keep.

Notes column size bumps:
- Label 44pt -> 48pt
- Content 30pt -> 36pt (+20%) — easier to read from across
  the line. Line-height tightened 1.15 -> 1.1 to keep the
  multi-paragraph wrap inside the body band.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 07:21:09 -04:00
gsinghpal
64c61dcca8 feat(sticker): much bigger text + QR +30%
wkhtmltopdf renders CSS font-size at a smaller physical scale
than the em-square math predicts (a "30pt" cell text was only
~4mm tall visually). Pushing all type up significantly so it
actually reads at scan/print distance:

Text bumps:
- Body field text 30pt -> 50pt (+67%, label + value)
- WO# 56pt -> 72pt (+29%)
- Notes label 30pt -> 44pt
- Notes content 22pt -> 30pt (+36%)
- Muted rev tag 22pt -> 30pt
- Body cell padding 0 10px -> 0 8px (a touch more horizontal
  room for long values now that the font is bigger)

QR + 30% as asked:
- Wrapper 280 -> 365px (+30.4%). Image 368 -> 480px, offset
  -44 -> -58px (recomputed for the new quiet-zone crop).

Header re-balanced for the bigger content:
- Height 25% -> 32% (fits the +30% QR + bigger WO# + bigger
  logo at 135px)
- Body band: 75% -> 68% (rows now ~9.6mm tall; line-height
  1.0 keeps the 50pt body text snug inside)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 23:48:40 -04:00
gsinghpal
649b75d4a1 feat(sticker): bigger field labels + values + notes text
Trimmed the header from 30% to 25% of page height to free up
vertical room for the body band's 7 rows. Each row is now
~10.45mm tall (was 9.88mm), so the field font fits comfortably
at the bigger size.

Size bumps:
- Body field text 26pt -> 30pt (label + value, +15%)
- Muted rev tag 18pt -> 22pt
- Notes label 26pt -> 30pt
- Notes content 19pt -> 22pt (+16%, wraps cleanly to 2 lines
  when the customer description runs long)

Header re-fit (smaller cells, same content):
- Header height 30% -> 25%
- WO# font 62pt -> 56pt
- Logo max-height 135 -> 105px
- QR wrapper 340 -> 280px (image 447 -> 368px, offset -53 ->
  -44px to keep the quiet-zone crop math right)
- High-def 600x600 QR source unchanged — still prints crisp
  at the smaller wrapper size

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 23:41:17 -04:00
gsinghpal
8aa817b1a0 feat(sticker): bigger text, bigger high-def QR, drop "WO #" prefix
WO# cell now just renders the number (e.g. "WO-30019") since the
"WO" is already baked into the doc index format — the redundant
prefix was eating cell width without adding information.

Size bumps:
- WO# 44pt -> 62pt (text is shorter so the cell can carry the
  extra weight)
- Body field text 22pt -> 26pt, line-height 1.1 -> 1.0 so the
  bigger font still fits 7 rows in the body band
- Notes label 22pt -> 26pt, content 16pt -> 19pt
- Logo max-height 120 -> 135px
- Muted rev tag 16pt -> 18pt

QR upgrades (both "bigger" and "high def" as asked):
- Source resolution 300x300 -> 600x600. At 300dpi print across
  a 28.8mm wrapper, effective output is ~515ppi vs the prior
  ~256ppi. Scanners on the floor will read it cleanly even at
  steeper angles / scuffed labels.
- Wrapper 290 -> 340px (+17%). Image 390 -> 447px, offset -50
  -> -53px (recomputed quiet-zone crop: 600 * 0.12 = 72px
  margin -> 456px effective QR data -> 340 * 600/456 = 447
  scaled image -> (447-340)/2 = 53px offset).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 23:38:05 -04:00
gsinghpal
80d1cc5639 feat(sticker): 3-cell header + right-side Notes column + new field list
Restores the original ENTECH sticker layout from the operator's
screenshot reference:

Header (3 horizontal cells, divided by vertical rules):
  [Logo]  |  WO #WO-30019  |  [QR]

Body (left side = field table, right side = Notes column):
  PO #:        587854         | Notes:
  SN #:        -              | <customer-facing description>
  Customer:    ABC Manufact.  |
  Part #:      9876... Rev A  |
  Due Date:    May 17, 2026   |
  Thickness:   -              |
  Qty:         1              |

Changes from previous (stacked-left) layout:
- Header: 1-row 3-cell (Logo 28% | WO# 44% | QR 28%) replaces
  the 2-cell w/ logo+WO# stacked on left.
- Body: 2-region (66% / 34%) replaces single 7-row table.
  Notes column now spans full body height on the right.
- Fields: SN # and Thickness added; Process row removed.
- Labels: "PO (RO)" -> "PO #", "Part Number" -> "Part #".
- Notes content: switched from SO.x_fc_internal_note to the SO
  line's `name` (= customer-facing description per Sub 2 Q6).
- SN # reads _line.x_fc_serial_number (Sub 5 field).
- Thickness reads _line.x_fc_thickness with coating.thickness
  fallback (Sub 5 field, defensive 'in _fields' check).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 23:33:18 -04:00
gsinghpal
2db789d7dd feat(sticker): bigger QR + double-height Notes row
Both changes the operator asked for, applied to the original
ENTECH stacked-left layout (no other structural changes):

- QR wrapper 380px → 460px (image 510px → 620px, offset -65 → -80
  to keep the white quiet-zone cropped). Roughly +21% surface area.
- Notes row height 14.28% → 24% (~2x). Other 6 rows shrink
  proportionally from 14.28% to 12.67% each so the band still
  totals 100%. Notes value also gets white-space: normal +
  vertical-align: top so the operator's handwriting room sits at
  the top of the cell and a long internal note can wrap.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 23:25:37 -04:00
gsinghpal
7a02382623 fix(reports): WO Margin model name must match report_name + '_template' suffix
The previous fix swapped t-field -> t-esc so the QWeb error stopped,
but the report still printed blank. Root cause: Odoo looks up the
report data model via env['report.<report_name>'], but our model was
named 'report.fusion_plating_jobs.report_fp_job_margin' while the
action's report_name is 'fusion_plating_jobs.report_fp_job_margin_template'.
The model lookup missed, _get_report_values never fired, and the
template rendered with no 'rows' in scope — empty foreach -> empty
page.

Renamed the model to report.fusion_plating_jobs.report_fp_job_margin_template.

Verified: PDF size jumped from 1229 bytes (blank) to 125880 bytes
(fully populated). HTML now contains 'Job Margin', 'Step Breakdown',
and the actual WO name.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 19:08:38 -04:00
gsinghpal
449f29fc7f fix(reports): WO Margin PDF — t-field requires dot-notation on Odoo 19
The template used 't-field="step['rate']"' for monetary values pulled
from dict rows. Odoo 19's QWeb asserts t-field has at least one dot
(it's strictly for record.field_name lookups). Replaced six bare-dict
t-field usages with t-esc; the existing t-options widget=monetary +
display_currency still applies for currency formatting.

Verified by rendering report for WO-30019 — 1229-byte valid PDF, no
QWeb error.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 18:51:17 -04:00
gsinghpal
457d9b7dbf fix(numbering): post-review fixes — credit notes, SO unlink, multi-part grouping, SQL whitelist
- B1: Add Credit Note wizard path was blocked because invoice_origin
  has copy=False and the wizard doesn't set fp_from_so_invoice. Now
  the validator allows reversals when reversed_entry_id points at a
  customer-facing move that itself went through the validator at
  original creation time. account.move._fp_parent_sale_order also
  walks self.reversed_entry_id._fp_parent_sale_order so the credit
  note inherits the parent number (CN-<parent>).

- Bug 1: sale.order.unlink() now blocks deletion when x_fc_parent_number
  is set (matches spec §6.2). Draft quotes remain freely deletable
  per Odoo standard. Applies to all users including admins.

- Bug 2: out_receipt added to CUSTOMER_TYPES so POS-style receipts
  hit the same SO-flow gate as out_invoice / out_refund.

- C1: WO grouping key changed from recipe.id to (recipe.id, part.id,
  coating.id). Bundling lines with different parts under one WO put
  first_line's part_number on the CoC header — silent compliance
  mis-attestation. Now distinct parts always get distinct WOs even
  when they share a recipe.

- C3: SQL whitelist (_FP_COUNTER_FIELD_RE) on _fp_assign_parent_name's
  interpolated counter field name. No user input today; defence in
  depth for future subclasses that might read the name from context.

Verified on entech: parent=30017, credit note = CN-30017,
multi-part SO produces 2 WOs (one per part), confirmed-SO unlink
blocked, out_receipt blocked, whitelist regex enforced.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 18:19:08 -04:00
gsinghpal
5b399fbdda fix(configurator): copy operator-input prompts when cloning recipe to part
_clone_subtree() in fp_part_composer_controller built node vals
manually and never copied source.input_ids — so 'Load Template'
copied the recipe tree structure but dropped every custom prompt,
leaving operators with empty data-capture screens. The fix iterates
input_ids and calls .copy({'node_id': new_node.id}) so kind,
target_min/max/unit, compliance_tag, hint, selection_options,
sequence — every field on the input model — flows through.

Verified on entech: ENP-ALUM-BASIC clone now shows all 105 prompts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 17:58:11 -04:00
gsinghpal
b5416d242c test(numbering): E2E walkthrough — quote -> SO -> WO -> IN -> CoC -> DLV -> RCV -> Hold -> RMA
Verified pass on entech (parent=30015): all linked docs share the
parent number, immutability + unlink-block + direct-invoice-block
all enforced. NCR/CAPA fall back to legacy sequences as designed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 14:35:29 -04:00
gsinghpal
fdbbd2852a fix(numbering): WO Detail report strips WO- prefix for compact display
short_wo now handles both naming schemes: new WO-NNNNN[-NN] (strips
WO-) and legacy WH/JOB/NNNNN (last slash segment). Customer-facing
Work Order column shows '30000-02' instead of 'WO-30000-02'.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 14:34:09 -04:00
gsinghpal
be109c9c79 feat(numbering): surface quote ref under SO name on the form
A small grey 'Originally quoted as Q202605-200' line appears below
the SO heading once the order is confirmed. Uses invisible= on the
wrapper div (Odoo 19 forbids t-if in standard form views).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 14:32:22 -04:00
gsinghpal
78d633f63f feat(numbering): immutable name/doc_index + unlink block on issued docs
write() override raises UserError if name or x_fc_doc_index is in
vals and differs from the stored value (bypass: context flag
fp_allow_name_rename=True for the SO-confirm rename + bulk WO
creation paths). unlink() override raises UserError for records
that have been issued a name; applies to all users including
admins — cancellation must go through the state machine.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 13:39:03 -04:00
gsinghpal
95cb73d91a feat(numbering): wire NCR, CAPA, Hold, RMA into parent-numbered mixin
Hold derives parent via job_id.sale_order_id; RMA via sale_order_id
directly — both get HOLD-<parent> / RMA-<parent> names. NCR and CAPA
have no SO link in core, so they fall back to their legacy sequences
(NCR/YYYY/NNN, CAPA/YYYY/NNN); future modules can override the
_fp_parent_sale_order hook to enable parent naming.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 13:36:29 -04:00
gsinghpal
0d85063b5e feat(numbering): wire CoC/RCV/DLV/PU into parent-numbered mixin + rename counters
Per-model counter fields on sale.order renamed to x_fc_pn_*_count
to avoid collision with pre-existing compute fields of the same
short name in bridge_mrp / receiving / configurator (silent
compute-override was suppressing the storage). 4 child models
(fp.certificate, fp.receiving, fusion.plating.delivery,
fusion.plating.pickup.request) now derive names as PFX-<parent>
with -NN suffix from the 2nd onward.

fusion.plating.pickup.request gains a sale_order_id field
(optional) so pickups created against an SO get parent-derived
names, while standalone pickups (pre-SO) fall back to PU/YYYY/NNNN.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 13:30:37 -04:00
gsinghpal
765a0a4c82 feat(numbering): block direct invoice creation + wire account.move into mixin
Customer invoices (out_invoice / out_refund) can only be created via
sale.order._create_invoices() or with an invoice_origin matching an
existing SO. Applies to ALL users including admins. Once created,
the move's name is derived from the SO's parent number: IN-30000,
IN-30000-02, CN-30000, ... Pre-existing portal-job link on
action_post() preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 13:21:09 -04:00
gsinghpal
6c6fb8d2a4 feat(numbering): WO grouping by recipe + parent-derived bulk naming
Replaces x_fc_wo_group_tag grouping with resolved-recipe grouping.
Bare WO-<parent> when 1 recipe, WO-<parent>-NN zero-padded for N>1
ordered by min line sequence. fp.job inherits parent-numbered mixin
for the manual-add path; bulk SO-confirm sets names explicitly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 13:18:10 -04:00
gsinghpal
1b1bebdcd8 feat(numbering): assign parent_number + rename to SO-<n> on confirm
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 13:14:47 -04:00
gsinghpal
e0d1998811 feat(numbering): draw quote name from fp.quote.number on SO create
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 13:12:45 -04:00
gsinghpal
bc3f584851 feat(numbering): add parent_number + counters to sale.order
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 13:10:55 -04:00
gsinghpal
105909470f feat(numbering): add fp.parent.numbered.mixin abstract model
Atomic counter via SELECT FOR UPDATE on the parent SO row. Composes
child names as PREFIX-PARENT (bare for first) or PREFIX-PARENT-NN
(zero-padded 2-digit, then unpadded past 99). Subclasses implement
three hooks: _fp_parent_sale_order, _fp_name_prefix, _fp_parent_counter_field.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 13:09:17 -04:00
gsinghpal
fd9d4e775b feat(numbering): add fp.parent.number + fp.quote.number sequences
Parent sequence starts at 30000. Quote sequence is Q + YYYYMM + non-resetting
counter starting at 200. Phase 1 Task 1 of the parent-number hierarchy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 13:07:16 -04:00
gsinghpal
2de5491693 plan(numbering): step-by-step implementation plan
15 tasks across 8 phases — foundation (sequences + mixin + SO fields),
quote/SO rename, WO grouping rewrite, invoice block + naming, child
model wiring (CoC/RCV/DLV/PU/NCR/CAPA/Hold/RMA), immutability + unlink
block, view + report fixes, end-to-end walkthrough.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 12:38:08 -04:00
gsinghpal
671820427a spec(numbering): parent-number hierarchy design
Quote→SO→WO→IN→CoC→DLV→RCV→… all share a single parent number drawn
from the sale order. New abstract mixin centralises naming with atomic
counter increment, compliance-grade immutability, and a hard block on
direct invoice creation outside the SO workflow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 12:28:52 -04:00
gsinghpal
01a46e33e2 fix(process-tree): breadcrumb pile-up + dead "No MO selected" banner
Two issues on the Process Tree client action:

1. Back to Work Order kept growing breadcrumbs (WO -> Tree -> WO ->
   Tree -> ...) because onBack used action.doAction() which PUSHES
   a new act_window onto the stack instead of popping. Fixed by
   trying action.restore() first (pops the Tree off the stack and
   returns to the parent WO/Step controller). Falls through to
   explicit doAction only when there's no parent in the stack
   (direct URL access).

2. The empty-state banner referenced productionId, a dead variable
   from the bridge_mrp era when the tree was tied to mrp.production.
   Since the component now uses jobId (fp.job context key), the
   "No manufacturing order selected" message ALWAYS fired regardless
   of whether a job was loaded. Fixed by using jobId and updating
   the message to "No work order selected".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 09:05:27 -04:00
gsinghpal
2d9779047b fix(jobs): registry load failure after Tier 2/3 persistence patches
Two compounding issues introduced by the persistence audit work:

1. fields.Text mismatch: sale.order.x_fc_internal_note and
   x_fc_external_note are actually fields.Html. I declared the
   sale.order.line related mirrors and the fp.job stored copies as
   fields.Text, so setup_related raised:
     TypeError: Type of related field
     sale.order.line.x_fc_internal_note is inconsistent with
     sale.order.x_fc_internal_note
   Fixed by switching both Note fields on fp.job and sale.order.line
   to fields.Html.

2. Module-load-order: Tier 3 fields (x_fc_delivery_method,
   x_fc_ship_via, x_fc_invoice_strategy) are defined in
   fusion_plating_jobs (related to sale.order via _inherit), but I
   referenced them in fusion_plating core's fp_job_views.xml — which
   loads BEFORE fusion_plating_jobs registers the fields. View
   validator raised "Field x_fc_delivery_method does not exist".
   Fixed by removing those 3 fields from the core view group and
   adding them via xpath in fusion_plating_jobs's fp_job_form_inherit
   (which loads after the fields are registered).

Both fixes deployed and verified — registry loads in 2s, all field
types match, related path resolves correctly. No data loss; the
fp.job rows that already had stored Text content for internal_note /
external_note will carry over into the Html field intact.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 08:45:59 -04:00
gsinghpal
cba9a6da6b feat(jobs): mirror delivery_method/ship_via/invoice_strategy on fp.job
Tier 3 of the SO->fp.job persistence audit. Three logistics/billing
fields surface on fp.job as related read-only (not stored) mirrors:

- x_fc_delivery_method - Local Delivery / Shipping Partner / Customer
  Pickup. Cargo classification used by logistics planning.
- x_fc_ship_via - Carrier name (UPS, FedEx, customer pickup, etc.).
- x_fc_invoice_strategy - Deposit / Progress / Net Terms / COD-Prepay.
  Read by the invoicing module's hooks; mirroring on the WO is for
  manager visibility only.

These were intentionally chosen as related (not stored persisted)
because the SO is the authoritative source - the existing downstream
code (delivery + invoicing modules) already reads them off SO directly.
A stored copy would risk drift. Related auto-follows SO updates.

Same three fields also mirrored on sale.order.line as stored related
for per-line list visibility.

Closes the SO->fp.job persistence audit. All 10 operational fields
identified now flow through to the WO (7 stored + populated at confirm,
3 related read-only).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 08:39:12 -04:00
gsinghpal
15eac309ee feat(jobs): persist deadlines + planned start + notes on fp.job
Tier 2 of the SO->fp.job persistence audit. Four operational metadata
fields mirrored from sale.order:

- x_fc_internal_deadline (Date) - shop's internal target finish date,
  ahead of the customer-facing deadline. Kept separate from
  date_deadline (which scheduling code may adjust).
- x_fc_planned_start_date (Date) - customer-quoted planned start date.
  Kept separate from date_planned_start (Datetime, capacity-adjusted).
- x_fc_internal_note (Text) - shop-internal notes from the order.
- x_fc_external_note (Text) - customer-facing notes, printed on
  traveller / BoL / cert.

All four populate at SO confirm via _fp_auto_create_job, and surface
on sale.order.line as stored related fields for per-line visibility.
fp.job form view gets a Notes group alongside the Customer References
group from Tier 1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 08:37:19 -04:00
gsinghpal
7d37f5713c feat(jobs): persist Customer Job # + PO # + Rush Order on fp.job
Tier 1 of the SO->fp.job persistence audit. Three customer-reference
fields entered on sale.order's Plating tab were not flowing through
to fp.job (or SO lines), so the shop floor and printed paperwork
(traveller, BoL, cert) had to round-trip via sale_order_id every time.

Changes:
- fp.job: new x_fc_customer_job_number (Char, tracking), x_fc_po_number
  (Char, tracking), x_fc_rush_order (Boolean, tracking). All three
  populated by _fp_auto_create_job at SO confirm time.
- sale.order.line: x_fc_customer_job_number / x_fc_po_number added as
  stored related fields off order_id so per-line list views show the
  customer's references without navigating to the order header
  (x_fc_rush_order was already on lines).
- fp.job form view: small Customer References group under the title
  surfaces the three fields where the user expects them.

Verified end-to-end: SO -> SO line related fields -> fp.job direct
fields all carry the same value.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 08:34:56 -04:00
gsinghpal
cd2584d6ee ui(rename): "Plating Job" -> "Work Order" / display "WO # 01368"
Standardise user-facing terminology across 5 modules (27 files):
  - display_name compute: 'Work Order # 01368' -> 'WO # 01368'
  - _description on 5 models: Plating Job{," Step"," Step Time Log"," Margin Report"," Recipe Node Override"} -> Work Order equivalents
  - field labels (string=...) on 13 Many2one / One2many fields
    across fp.batch, fp.thickness_reading, fp.quality.hold,
    fp.job_consumption, fp.portal.job, fp.certificate, fp.delivery,
    fp.quality.check, fp.racking.inspection, res.partner, sale.order
  - XML view labels: action names, list/form/search strings,
    portal template names, dashboard tile titles

What's deliberately preserved:
  - DB model name 'fp.job' (technical identifier — used by
    sale_order.x_fc_plating_job_ids and all comodel refs)
  - Module name 'fusion_plating_jobs' (directory / import path)
  - Settings -> Apps display label 'Fusion Plating Jobs' (module
    identity for Odoo's app picker)
  - 'Use Native Plating Jobs' migration toggle (internal mechanism
    flag, not user-facing terminology)

Verified on entech: WH/JOB/01368 now displays as 'WO # 01368'
everywhere humans look (form header, breadcrumbs, M2O dropdowns,
error messages, smart-button titles).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 08:22:09 -04:00
gsinghpal
dcbe8305d0 ui(process-tree): back to Work Order + pulsing green for done steps
Two improvements to the Process Tree visualization opened from the
Work Order's Process Tree header button:

  1. Back button returns to the Work Order (job form) instead of
     Plant Overview. fp.job.action_open_process_tree now passes
     back_job_id in the client-action context; process_tree.js
     reads it via a new backJobId getter, updates the button label
     to "Back to Work Order", and routes onBack to fp.job form.
     The Plant Overview fallback stays for callers that don't pass
     either back_step_id or back_job_id.

  2. Completed operation/step cards now have a green fill (#1e8449)
     and a subtle pulsing glow (box-shadow animation, 2.6s alternate)
     so finished work pops against still-pending dark cards. Hover
     pauses the animation so the click target is steady. Reuses the
     same green the workflow-state slice already used.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 08:13:46 -04:00
gsinghpal
798458c834 ui(jobs): Finish & Next becomes pulsing vivid-green icon
User wanted Finish & Next to drop its text label like the other row
buttons, but stand out visually as the primary action. Solution:
icon-only with a vivid green color and a subtle pulse animation.

- New SCSS: fp_finish_btn.scss with branch-on-$o-webclient-color-
  scheme so the dark bundle uses green-400 (pops on dark bg) and
  light bundle uses green-600. Pulse animation 1.8s ease-in-out
  infinite, scale 1.0 ↔ 1.18. Pauses on hover/focus so the click
  target is steady.
- Registered in both web.assets_backend and web.assets_web_dark
  per the project's dark-mode rule (CLAUDE.md).
- View: string="Finish & Next" → title="Finish & Next",
  class drops "text-primary", gains "o_fp_finish_btn".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 01:05:28 -04:00