fusion_clock doesn't depend on planning, so planning's models load AFTER it
during -u and the port saw no data. Now detect planning tables via SQL,
defer (no marker) when the ORM isn't loaded, and finish the port from the
deploy odoo-shell step (full registry). Marker now owned by the method.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Settings saved via set_param() have no ir_model_data; the noupdate config XML
then collides on UNIQUE(key) during -u. Pre-migrate links existing params to
their XML external id (value-preserving) so upgrades are robust. Found on the
Entech clone-verify; affects prod (35 params vs 32 xmlids).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Model: fclk_create_open_shifts/claim_open_shift/release_shift (days-before
cutoff + role eligibility)/bulk_apply. Planner: Open Shift… panel, open-shifts
strip with delete, Apply-to-dept; load includes open shifts. Portal: claim
open shifts + release own upcoming shifts with feedback banners. Tests for
claim/role-gate/release/bulk.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
is_open + crosses_midnight fields; employee_id optional (open shifts);
company_id computed w/ env.company fallback; drop hard one-per-day UNIQUE
(allow split + open). Overnight math in planned_hours/_check_schedule_times/
scheduled_times. _get_fclk_day_plan resolves multiple posted rows into ONE
work-window so penalties/overtime/absence stay correct. Migration drops the
old constraint defensively. Tests for overnight, window, open shifts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
post-migrate(19.0.5.0.0) -> fusion.clock.schedule._fclk_port_planning_data:
planning.role -> fusion.clock.role, employee default/allowed roles, and
planning.slot -> fusion.clock.schedule (local date+float, role map, posted
if published, open if unassigned). Guarded (no-op on Community), idempotent
(marker), per-row savepoints. Integration test runs on Enterprise clones.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Generalise post_week into fclk_publish_range/fclk_email_posted_range +
planner Publish… panel + publish_range endpoint. Fold the /my/clock/schedule
controller+template+css from fusion_planning into fusion_clock (native
schedule only, role colour); inline Schedule nav across all portal pages.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fusion.clock.schedule.recurrence (repeat every N day/week/month/year;
forever/until/N-times) re-fit from planning.recurrency onto per-day rows;
daily generation cron; _fclk_on_leave skip; planner Repeat…/Stop-repeat
UI + endpoints; recurrence + role indicators on cells.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces Odoo Planning's planning.role: name+colour model with the same
1-11 palette, employee default/allowed role fields, Employee Roles editor,
role_id on shift template + schedule with default resolution, ACLs, menus.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On the sale.order and fp.job forms, the Holds/Checks/NCRs/CAPAs/RMAs quality
smart buttons (and the SO WO button) now hide when their count is 0, so the row
shows only quality work that exists. NCR and CAPA counts are re-scoped from
customer-wide to order/job via a shared _fp_quality_ncr_ids() helper (NCRs
reached through the order's RMAs + the order/job's holds), so each badge and the
list its button opens always agree. Also aligned the job RMA button's list
domain to its (already SO-scoped) count.
Reverts the Sub-12 Phase D "always-visible (zero is OK)" choice back to the
module's documented hide-at-zero convention.
- fusion_plating_quality 19.0.8.1.0 -> 19.0.8.2.0
- fusion_plating_jobs 19.0.12.4.0 -> 19.0.12.5.0
Deployed + verified on entech (badge == helper across sampled SOs/jobs).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Re-fit Planning's role + recurrence + send onto the native per-day
fusion.clock.schedule model; retire fusion_planning into fusion_clock;
make the family Community-installable. Full feature-parity matrix,
data migration, and gated Entech rollout included.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Carriers post the shipping label and (for international) the commercial invoice
to the delivery order's chatter, where they were hard to find. Add two smart
buttons on the stock.picking form that open the latest of each via the PDF
preview dialog (fusion_pdf_preview when installed, else open in a new tab).
Document discovery is carrier-agnostic (computed from the picking's attachments):
labels match 'Label*'; invoices match '*CommercialInvoice*' (UPS/Canada Post) or
'ShippingDoc-*' (FedEx/DHL, _get_delivery_doc_prefix). Buttons hide when absent.
Verified on entech: real FedEx picking resolved its label (invoice correctly
none for a domestic ship); synthetic UPS names resolved label+invoice and the
invoice button fired fusion_pdf_preview.open_attachment.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Relabel UPS 'Bill My Account' -> "Bill recipient's UPS account" with clear
help (it bills the customer's own UPS account; $0 shipping line; falls back to
Bill Shipper when the customer has no account on file).
- Improve the customer 'UPS Account Number' field help (stored per-customer,
auto-recalled at ship time for Bill Receiver).
- Add ups_rest_documentation_type setting (No / UPS commercial invoice) on the
UPS REST carrier, mirroring FedEx. Default 'invoice' preserves the existing
auto-generate-on-international behaviour; gate require_invoice on it so it can
be turned off. Surfaced on the UPS REST config page.
Validated live on entech (UPS production): CA->US shipment generated the label
+ a 60KB commercial invoice PDF (country of origin auto = CA, HS code applied),
then voided. Bill Receiver request confirmed accepted by UPS.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The UPS REST ship request wrapped the reference as
{'Value': [{'Code':'BM','Value': picking.name}]}, but _ups_rest_prepare_shipping_data
already builds reference_number as a list of {Code, Value} dicts. UPS expects
ReferenceNumber to be such an object (or array) with a STRING Value and rejects
the double-wrapped form on the ship call. This branch fires for every non-US/US
(e.g. CA->CA, CA->US) shipment, so rating worked but label creation failed.
Pass the list directly. Validated end-to-end against UPS production from a
Canadian origin: rate + real label (tracking 1Z6W...6355, then voided).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fp.certificate.contact_partner_id (single 'Customer Contact') becomes
contact_partner_ids (Many2many) — same shape as the partner's Default CoC
Contacts, as requested.
- Auto-populate: at job creation (fp.job cert resolution) + lazy-fill at issue,
contact_partner_ids = the customer's x_fc_default_coc_contact_ids (ALL).
- Send: action_send_to_customer pre-fills the composer with exactly the cert's
contact_partner_ids, so the CoC goes to all the defined clients (fallback:
company).
- Primary: the FIRST contact prints on the CoC + is gated for email; report
uses contact_partner_ids[:1].
- Gate: requires >=1 Customer Contact + the primary has an email.
- View: many2many_tags.
- Migration 19.0.10.3.0: copies each cert's old single contact into the new M2m,
drops the orphaned column.
Deployed + verified on entech: migration copied 16 certs, old column dropped,
field is M2m, send pre-fills the cert contacts, CoC report renders. entech-only
part_line_ids preserved.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
res.partner.x_fc_default_coc_contact_id (single Many2one) becomes
x_fc_default_coc_contact_ids (self-referential Many2many 'Default CoC
Contacts') so a customer can list several contacts who need the CoC.
- res.partner: M2m field (rel fp_default_coc_contact_rel) + many2many_tags.
- Cert: contact_partner_id (primary addressee printed on the cert) is set to
the FIRST CoC contact at job creation + lazy-filled at issue.
- Send: action_send_to_customer pre-fills the email composer with ALL the
customer's CoC contacts (primary + the rest), falling back to the company.
- fp.job cert-default resolution + the action_issue gate wording updated.
- Migration 19.0.10.2.0: copies each partner's old single value into the new
M2m, then drops the orphaned column.
Deployed + verified on entech: migration copied 2 existing values, old column
dropped, field is M2m, send pre-fills all contacts. entech-only part_line_ids
/ multi-part resolver preserved.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Service-booking wizard CSS: scroll on small screens (height:100% so overflow
engages), padded fields (!important vs Odoo input normalisation), narrow-screen
sub-grid collapse. Also hardens scripts/verify_service_booking.sh with an
asset-bundle compile gate. Clone-verified GREEN (assets compile) + deployed to
westin-v19 (fusion_claims 19.0.9.5.0).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The compile gate's 'odoo shell' tried to bind 8069 (the running app holds it) and
died with 'Address already in use' before compiling, false-failing the gate. Add
--no-http --http-port=0 --gevent-port=0 (same as the test run) so the shell loads
the registry and force-compiles the bundles without binding a port.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Confirm->Receive (A): after a single interactive SO confirm, receiving's
action_confirm returns action_view_receiving() so the user lands straight
on the Receive Parts screen (opt-out via fp_no_receiving_redirect context).
- Lock recipe (1): recipe_id readonly on the WO form — stick to the
order-entry recipe.
- Hide spec (2): customer_spec_id invisible on the WO form.
- Reset step (3): new fp.job.step.button_reset (operator-usable, audited) +
an undo button next to Start. Resets to Ready, clears finish + sign-off,
closes open timelogs, keeps start audit + move/CoC history.
- Lock steps (4): steps list create=false delete=false (no Add a line / no
trash) — steps come from the recipe, only skippable, never deleted.
- Bake gate fix (5): _fp_missing_required_step_inputs now honours the node's
collect_measurements master switch, matching the Record-Inputs wizard.
collect_measurements=False + required prompts no longer blocks finish
(wizard shows 0 rows, so the gate must too). Unblocks WO-30098 + 63 other
affected nodes (bake steps).
Deployed + verified on entech (-u jobs; bake finishes, reset done->ready,
recipe readonly, spec hidden, steps locked, receiving redirect target OK).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reported on the live wizard: no scroll on small screens, not responsive, fields
look unpadded.
- .o_service_booking: min-height:100% -> height:100% so the root is capped to the
action area and overflow:auto scrolls INTERNALLY (min-height let it grow to
content height, so the clipping action container never scrolled).
- input/select/textarea.f: padding 10px 12px !important + line-height 1.4 so
Odoo's backend input normalisation can't strip the field padding.
- add a <=560px media query collapsing the .two/.three sub-grids, wrapping the
time picker, and tightening margins (the main .grid already collapses at 780px).
- bump version 19.0.9.4.0 -> 19.0.9.5.0 (asset cache-bust).
Also harden scripts/verify_service_booking.sh: force-compile web.assets_backend +
web.assets_web_dark on the clone after tests, so a broken SCSS fails the deploy
gate BEFORE prod (a bad stylesheet would break the whole backend bundle; -u does
not compile assets — Odoo compiles them lazily).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Generalize the delivery packing slip template to be model-agnostic
(branches on doc._name to resolve the sale order + ship-to for
sale.order / fp.job / fp.receiving / fusion.plating.delivery) and add
three report actions bound to sale.order, fp.job and fp.receiving so the
packing slip appears in each one's Print menu (delivery already had it).
Uses _scheduled / _notes so it never AttributeErrors on models without
scheduled_date / notes. Declare the fusion_plating_receiving dep on
reports (already transitive via logistics) for the fp.receiving binding.
Verified on entech: real content for SO-30102, WO-30102, RCV-30103; all
four Print-menu bindings live.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The packing slip report only existed for stock.picking (Delivery Orders),
but this shop ships via fusion.plating.delivery and has no pickings — so
packing slips never rendered for their flow, and the prior auto-generate +
email-notification paths pointed the stock.picking report at a delivery
(wrong model -> blank PDF).
Add a delivery-native variant: report_fp_packing_slip_delivery_portrait +
action_report_fp_packing_slip_delivery_portrait (bound to
fusion.plating.delivery -> shows in the delivery Print menu), resolving the
SO + lines from the delivery job_ref (same pattern as the BoL report) and
reusing the shared styles / address / signoff bits + a sale.order.line
items table. Repoint _fp_generate_packing_slip (dispatch auto-gen) and the
notification attachment to the new report.
Verified on entech: real content (customer, PO, items, PS#) for DLV-30102 —
142KB PDF vs prior blank 12.8KB.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fusion.plating.delivery already had packing_list_attachment_id + a viewer
action, but nothing populated it — shipping a local delivery produced no
packing slip. Add _fp_generate_packing_slip(): renders
fusion_plating_reports.action_report_fp_packing_slip_portrait and stores
it on packing_list_attachment_id. Hooked into action_start_route (the
dispatch / loaded-on-vehicle moment, so it travels with the goods) and as
a generate-if-missing catch-all on action_mark_delivered. Idempotent
(skips deliveries that already have one unless force=True) and best-effort
(a report glitch logs + continues, never blocks shipping). Report action
resolved at runtime so logistics keeps no hard dep on
fusion_plating_reports. Deployed + verified on entech (12.8KB PDF for
DLV-30097, rolled back).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
res.partner has no `mobile` field in this Odoo 19 build, so
_fp_resolve_delivery_defaults crashed with AttributeError when a job's
last shop step finished and auto-created a delivery
(button_finish -> _fp_check_advance_post_shop -> _fp_create_delivery).
This blocked operators from finishing the step at all.
Guard the read with the codebase's 'x' in obj._fields pattern so it
falls back to phone, and still picks up mobile on instances that define
it. Deployed + verified on entech (restart, no -u; pure Python change).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Internal Job Sticker is no longer a separate Layout A: it's now a COPY of the
External sticker (Layout B, one per box, logo + WO + BOX + QR + rail fields +
prominent PLATING THICKNESS banner) but feeds the INTERNAL description and
labels its notes "INTERNAL NOTES" so the shop copy can't be confused with the
customer copy. The old Layout-A body template is deleted.
- Removed the Internal sticker from the fp.job Print menu (binding_model_id ->
False); it now prints from the Receiving screen instead.
- Added "External Sticker" + "Internal Sticker" print buttons to the fp.receiving
form header (shown once a WO exists). Each renders one label per tracked box
for the receiving's work order (passes a single WO so the SO-scoped box loop
doesn't reprint each label per job).
Verified on entech (WO-30094 / RCV-30096): internal renders Layout B with the
internal description + INTERNAL NOTES; external unchanged; both receiving buttons
return the right report actions.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Caveat on the existing "custom-header reports need .article for UTF-8" rule:
the dpi=96 mm-based job stickers are the exception — adding .article re-routes
through Odoo's standard report CSS and blows up the mm/dpi layout. For those,
strip the non-ASCII glyph to ASCII in _clean() instead. (Learned fixing the
'375ºF' bake-text mojibake on the external sticker.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Relocate plating thickness out of the cramped rail (where it shared a row
with Due and wrapped) to a big 21pt "PLATING THICKNESS" banner at the top of
the main panel — the team's most-watched spec, now hard to miss. Gated on a
new has_thk flag (real value with a digit; skips empty/'N/A'). Due takes the
full rail row.
- Fix the bake-text degree mojibake: operators type 'º' (U+00BA) for "375ºF";
through this sticker's lightweight html_container path (no .article UTF-8
wrapper) it renders "375°F". Adding a .article wrapper fixes encoding but
blows up the dpi=96 mm layout (tested), so _clean() now strips º/°/˚ to clean
ASCII -> "375F".
Verified on entech (WO-30094).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The left rail (overflow:hidden, fixed height) was ~8mm over its budget, so the
last grid row (Due | Thk) fell off the bottom and rendered as an empty band.
Reclaimed the height: r-logo 11→9mm, r-wo 14→13mm, r-qrflags 36→32mm (+ qfwrap-full
33→31mm / qffull line-height 36→32mm to match), r-fld padding 1→0.7mm. Due/Thk
now render fully. Verified on entech (WO-30094, PO 980933709).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Drop the <br/> between label and value (.lbl is display:block, so the
value already sits beneath it; the <br/> added a blank-line gap).
- Rebalance widths: PO# 34→25%, Qty 16→13%, Due 30→26%, Thk 20→36% + nowrap,
so a thickness RANGE (e.g. 0.0025" - 0.0030") stays on ONE line instead of
wrapping. Verified on entech (WO-30096).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Internal Job Sticker (Layout A) redesign per operator feedback:
- Factory (ENTECH) logo added to the black header band on a white chip
(mirrors the external sticker; visible on the dark band + thermal print).
- Customer moved out of row 3 up next to Part# in row 2. Part# keeps the
dominant width (stripped customer name is short + consistent), MASK/BAKE
flags still float at the Part# edge.
- Row 3 now PO# / Qty / Due / Thk (4 fields, customer removed) with bigger
values (13/14/12/11pt) spread across the full width.
- Internal header QR trimmed 30→27mm so the QR-driven band is shorter; the
freed height flows to the NOTES block.
Rendered + verified on entech (WO-30072 / AMP-CANA).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The recipe-cert-toggles feature (fb6cccc8) taught
fp.job._resolve_required_cert_types to suppress thickness for recipes
with requires_thickness_report=False (passivation, chemical conversion,
anodize seal-only). But the actual thickness-data ENFORCEMENT never got
the memo: both fp.certificate.action_issue's hard gate AND the
Issue-Certs wizard's readiness hint re-derived 'needs thickness' from
partner flags only and ignored the recipe. Result: a passivation CoC for
a thickness/strict customer could never be issued — the gate demanded
Fischerscope data the process physically cannot produce.
Consolidate the partner-flag + recipe-suppression logic into one
fp.certificate._fp_needs_thickness_data() helper and route both the gate
and the wizard through it, so the cert-type resolver and the issue-time
gate can never drift again. Add regression tests: passivation recipe
suppresses the issue gate even for strict-thickness customers; a normal
recipe still enforces (control, guards aerospace).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Technician Service Booking & Auto-Quote: OWL 'Book a Service' wizard,
editable fusion.service.rate rate-card table, auto draft repair Sale Order
(call-out + per-km), and the fusion_tasks datetime-inverse tz fix. Clone-verified
GREEN and deployed to westin-v19 (fusion_claims 19.0.9.4.0) on 2026-06-04.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Hardened after the first real clone-verify on odoo-westin:
- Cleanup now generates an orphan-delete for EVERY single-column FK from PROD's
pg_constraint and applies it to the clone (was tax-tables-only). westin-v19 also
has deleted-company (payslip_tags_table, account_account_res_company_rel) and
deleted-journal (account_payment_method_line) orphans that broke the clone -u.
- run_odoo passes --http-port=0 --gevent-port=0 so --test-enable (which forces
http_spawn even with --no-http in Odoo 19) doesn't die on 'Address already in use'.
- TEST_TAGS scoped to this feature's classes (the broad tag also runs pre-existing
dashboard/wizard tests that fail in this prod-config runner, unrelated to this work).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Real install verified on the Westin clone; these were test-only bugs:
- Task-create tests hardcoded scheduled_date 2026-06-03, now in the past, which
the base _check_no_overlap rejects ('Cannot schedule tasks in the past'). Use
future dates (tz test pins a future July date so Toronto stays EDT for the
9:00->13:00 UTC assertion).
- Service-rate resolver tests created rows with seeded codes (callout_standard_normal,
per_km) -> UNIQUE(code) violation post-install. Assert against the seed instead.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The <template>_product_variant auto external-ID is not reliably created in this
Odoo 19 (only 5 exist on westin-v19; none for these products or product_labor_hourly),
so the rate rows' product_id refs failed at install: 'External ID not found:
..._product_variant'. Seed each product as model=product.product (the xmlid IS the
variant; name/price/uom/etc. delegate via _inherits) and reference it directly.
In-shop labour now uses a dedicated product_labour_inshop ($75) rather than reusing
product_labor_hourly, whose variant xmlid likewise does not exist. Caught on the
Westin clone install.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
product.template lost the separate purchase-UoM field uom_po_id in Odoo 19
(only uom_id remains). The plan's seed carried uom_po_id, which ParseErrors at
install: 'Invalid field uom_po_id in product.template'. Caught on first real
clone-install on the Westin Enterprise clone. The existing product_labor_hourly
uses uom_id only — match that.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Move the call-type select handler into onCallType() — OWL cannot compile a
multi-statement inline t-on body (was a render-breaking crash on mount).
- Replace color-mix() inside border shorthands with var(--sb-border) (Odoo-19
SCSS drops color-mix in a border shorthand).
- Technician placeholder option value '' (not 'false') so the required-tech
guard isn't bypassed.
- Remove dead setTiming(); null-coalesce the refdata onWillStart load.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review follow-up: the base fusion.technician.task.description is required=True and
non-in-store tasks require an address (_check_address_required). So:
- action_book_from_wizard now defaults description to 'Service booking' when the
payload carries neither description nor issue (avoids a required-field failure).
- test_task_without_order_is_allowed now sets description + is_in_store=True so it
exercises only the relaxed _check_order_link, not those unrelated base constraints.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Relaxes _check_order_link to a no-op (service bookings auto-create their SO;
in-shop/walk-in tasks may have none) and adds x_fc_is_service_repair on
sale.order. The 'Service Repair' crm.tag from the plan is intentionally
omitted: fusion_claims does not depend on crm and sale.order has no tag_ids;
the boolean flag is the repair-SO identity.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>