Plan 1 of fusion_maintenance, verified on the Westin Enterprise sandbox (westin-fr-test) via odoo shell. Maintenance policy (enabled/interval/flat fee/service product) on the equipment category + per-product fee override; contract gains fee/source/serial/policy/currency; fixed the dead _spawn_maintenance_contracts and wired it into the existing action_confirm (delivery-date anchor w/ fallback, two-regime serial dedup, fee resolution product->category); reminder email shows the flat fee; category form exposes the policy. Verified: trigger creates 1 priced contract (fee 149, next_due commitment+6mo, source=sale); idempotent on re-confirm; product override beats category; no contract when category not maintainable; fee renders as $149.00. v19.0.2.3.0.
NOTE: mail_template_data.xml is noupdate=1 -> the fee line loads on fresh install (the prod deploy) but NOT on -u of an already-installed system. The Westin prod-config test container (workers + log_level=warn) does not run --test-enable post_install tests (a pre-existing module load issue under the test phase), so behaviour was verified by odoo shell instead.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Surfaced by installing fusion_repairs into a westin-v19 clone (its first-ever clean install; cloud.md's 'installed locally' was stale). (1) Post-visit NPS mail template used url_encode(), which is NOT in Odoo 19's mail.template QWeb render context -> save-validation failed at install (ParseError 'issue with this value'); replaced with a string-method (.replace) fallback. (2) views/menus.xml defined menu_fusion_repairs_configuration AFTER the children referencing it as parent -> 'External ID not found in the system'; moved the parent definition above its children. fusion_repairs now installs cleanly (32 models, 11 templates) on the Enterprise stack.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(fusion_portal): funding-source selector on accessibility forms
Reps can now mark an accessibility assessment's funding source on the web form
(Private / March of Dimes / ODSP / WSIB / Hardship / Insurance / Other) so the
generated draft sale order routes to the correct funding pipeline instead of
always defaulting to private pay. Adds Hardship to the x_fc_funding_source
selection + sale_type_map; the new form <select> is auto-serialised by the
existing FormData submit, and accessibility_assessment_save now maps
funding_source -> x_fc_funding_source. The model + SO routing were already in
place (2026-04 audit fix) — this closes the form + controller gap.
Plan: docs/superpowers/plans/2026-06-02-accessibility-funding-selector.md
Spec: docs/superpowers/specs/2026-06-02-assessment-visit-funding-design.md
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(fusion_portal): validate funding_source in accessibility save (parity with booking)
Coerce an unexpected/tampered funding_source to direct_private instead of passing
it raw into create() (which would raise on the Selection field). Mirrors the
/book-assessment controller; the whitelist is derived from the model selection so
it auto-covers hardship and any future values.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Operators saw dark-on-dark (invisible) text in the workspace + "Cannot
Finish Step" dialog in Odoo dark mode.
Root cause: var(--text-secondary, #xxx) — a made-up variable that doesn't
exist in Odoo, so it always fell back to the hardcoded dark hex (invisible
on dark). Used 33× across job_workspace.scss + 5 component stylesheets.
Replaced with the real dark-aware var(--bs-secondary-color).
Also fixed paired backgrounds that would hide the now-theme-flipped text:
- finish-block action note → var(--bs-tertiary-bg) (was #f3f4f6).
- Tinted status banners (finish-block step, overtime timer, receiving
status) → color-mix over var(--bs-body-bg) + var(--bs-body-color).
Odoo's bootstrap lacks the BS5.3 -bg-subtle/-text-emphasis vars
(verified against _root.scss), so color-mix is the dark-aware path.
Solid accent pills/dots (white text) and the color-coded plant-card chips
(light-bg + dark-text, readable in both) intentionally left as-is.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Matched, intentional look for the two scan controls:
- Scan QR (camera, primary sticker-scan) — accent-filled blue, fa-qrcode.
- Enter Code (manual / scanner-gun) — accent-tinted secondary, fa-keyboard-o.
Both now use Font Awesome icons (no emoji), inline-flex aligned icon+label.
Enter Code's class restructured so scan-alt persists alongside the active
state when the drawer is open.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The QrScanner component renders its own fa-qrcode icon; the board passed
it label '📷 Camera', so the camera button showed two icons (QR + camera
emoji). Drop the emoji → one icon.
Also clarify the two scan paths (they do different things):
- "Scan QR" = camera scan of the printed job sticker (primary path)
- "Enter Code" = manual / hardware scanner-gun text drawer (no camera)
Reordered so the camera (sticker) scan reads first. Other QrScanner call
sites already pass plain/no labels — this was the only double-icon.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Regression from the partial-order board: _job_presences emitted a card
for any area containing a `ready` step. These recipes seed ALL downstream
steps to `ready` at job creation, so a job showed in every future stage
at once (e.g. WO-30061 across racking/receiving/plating/inspection) even
though no parts had advanced there.
Fix: a stage shows ONLY where parts physically are (qty_at_step > 0,
which includes the first-active seed) OR where a step is in_progress/
paused. A merely ready/pending future step with no parts no longer shows.
Strict sequential progress falls out for free — the qty_at_step seed sits
on the lowest-sequence non-terminal step and advances as each completes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Operators can now see and advance a job's parts across multiple stages
at once (e.g. 10 Masking / 20 Plating / 20 Baking on one 50-part job).
Tracking model C (fluid per-stage quantities + existing hold/scrap/
rework records for exceptions); board option 2 (a card per occupied
stage); wait-to-reconverge close. Additive only — no new model, no
migration, no change to the close/cert/ship lifecycle.
Board (fusion_plating_shopfloor/controllers/plant_kanban.py):
- One card PER (job, stage), composite key "{job_id}:{area}". Unsplit
jobs render exactly as before. _job_presences/_render_presence;
primary presence keeps full job card_state, secondary presences
derive state from their focus step.
Card (plant_card.js/.xml/.scss):
- "20 of 50 here" badge; tap opens the workspace focused on that
stage's step (focus_step_id, already accepted by the workspace).
Move + light-up (move_controller.py, fusion_plating_jobs/fp_job_step.py):
- Availability/pre-fill now from qty_at_step (step had no qty_done/
qty_scrapped fields — the old read was always 0, dead path).
- Forward move auto-flips destination pending->ready (no auto-start;
labour timer stays explicit) and auto-finishes a drained source
(best-effort). Predecessor gate is qty-aware: a step with real
arrived parts is startable regardless of upstream completion
(_fp_has_real_incoming, single source of truth for can_start /
blocker / button_start / move blockers).
Operator advance (job_workspace.js):
- "Send -> <next>" action on in_progress/paused steps opens the slimmed
Move dialog (qty steppers, no keyboard; advanced fields collapsed).
Was only wired into the deprecated shopfloor_tablet before.
Close (fp_job.py):
- button_mark_done counts move-based scrap (_fp_scrapped_via_moves) into
qty_scrapped and derives qty_done = qty - scrapped (was blindly
= job.qty, over-counting). Reconciliation gate unchanged.
Static-validated: pyflakes (py), lxml parse (xml), node --check (js).
Dynamic tests + browser check need an installed env (entech/trial) —
plating modules can't install on the local Community DB.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Design for parts fanning across shop-floor stages (e.g. 10 at Masking,
20 at Plating, 20 at Baking on one 50-part job):
- Tracking model C — fluid per-stage quantities via existing qty_at_step;
failed/held/rework subsets ride existing hold/scrap/rework records.
- Board Option 2 — a card per stage-presence (composite job:area keys);
unsplit jobs render identically to today.
- Easy-advance operator flow — one "Send to next" action, steppers /
rack-tap (no keyboard), intent-named Hold/Scrap/Rework buttons.
- Light-up plumbing — auto-ready on arrival, qty-aware predecessor gate,
auto-finish source on drain; no auto-start (labour accuracy).
- Close — wait to reconverge; close/cert/ship/invoice lifecycle unchanged.
Additive only: no new core model, no data migration, no change to the
quantity model, OWL component tree, or close lifecycle.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
TDD plan for the enrollment+pricing foundation: maintenance policy fields on the equipment category (+ product fee override), maintenance-contract extensions, fix+wire the dead _spawn_maintenance_contracts into the existing action_confirm (delivery-date anchor, two-regime serial dedup, fee snapshot), fee line in the reminder email, category UI, version 19.0.2.3.0. Grounded in real source. Plans 2-5 (booking on fusion_tasks, visit log + checklist, two-regime backfill, office crons) roadmapped.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Live sizing on Westin: stair lifts ~254 customers / porch-VPL ~30 / lift chairs ~41, but lift serial coverage ~0 (12/416 stairlift lines). The serial-as-unit-key approach (valid for ADP wheelchairs) fails for lifts. Backfill now splits into two regimes: serial dedup for wheelchairs; partner+base-product+sale-line dedup for lifts with accessory-line exclusion via the per-product maintainable flag.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Brainstormed design: bundle a home visit's assessments (ADP + accessibility),
measurement-first with client/funding deferred, add-as-you-go workspace,
per-item funding selector (fixes the March-of-Dimes routing gap), and on
completion group items into ONE draft sale order per funding workflow
(ADP / MOD / ODSP / Hardship / private) reusing the existing pipelines.
Adds ADP multi-device + combination rules, a new mobility-scooter type, and a
power-mobility home-accessibility rule that feeds the accessibility upsell.
v1 keeps manual quotation (no auto-pricing); MOD $15k cap is a reminder only.
Phased 1-3; risks + file map included.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Build maintenance INTO fusion_repairs (engine ~90% already there): per-category policy (interval + flat fee, product override); fix the dead contract-spawn trigger for new sales + a one-time idempotent backfill of the existing install base (lifts + fusion_claims wheelchairs); technician-aware self-serve booking on fusion_tasks availability (NO Enterprise appointment) creating a technician task; structured maintenance visit log + inspection cert for lifts; office follow-up crons; cost shown to client. Out of v1: SMS, /my/equipment, route optimization.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sized the real serial-tracked install base on sale.order.line: ~138 units / ~136 customers across all funders (walkers 68, wheelchairs 45, power bases 7, scooters 4, +14 with no ADP device_type). Serial# is captured ~only on equipment, so it doubles as a trackable-unit marker. ADP-only gating misses ~28 units (direct_private/adp_odsp/march_of_dimes) -> bridge should key on serial, funder-agnostic. Flags two data gaps (no-device_type units; non-ADP units lacking delivery_date) and reframes the MVP open question as volume (walkers/chairs) vs margin (powered units).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ran Step 0 against Westin prod (westin-v19 on odoo-westin). Resolved the APP/DB placeholders (DO boxes dead; migrated on-prem to odoo-dev-app), added a dated STEP 0 RESULTS section, and corrected the open questions the live inspection disproved: no stair/porch lifts in Westin ADP data; Enterprise appointment already ships native token booking; fusion_repairs contract engine not deployed; device_type is the ADP billing-code catalog taxonomy, not the install base.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Captures the maintenance-followup design exploration so it can resume from a
Tailscale-connected environment with access to Westin production:
- fusion_repairs already has a maintenance contract/reminder/booking engine to reuse
- fusion_claims (sale.order.line + adp.device.code.device_type) is the trigger source
- locked decisions: same DB, Enterprise appointment, public self-serve token booking
- Step 0 live-inspection command pack to run on Westin prod before any code
- open questions (MVP cut, revenue mechanic, tech assignment, booking route)
https://claude.ai/code/session_011wfSKQfSWhKZcm1yzSGznW
Rename module fusion_authorizer_portal -> fusion_portal everywhere:
manifest/assets, controllers, models, views, JS (odoo.define + asset URLs),
migration MODULE constants; plus cross-module refs in fusion_schedule,
fusion_repairs, fusion_quotations (depends + inherit_id) and the pdf_filler
import in fusion_claims. Add rename_module.sql for the one-time in-place DB
rename (ir_module_module, ir_model_data, ir_ui_view.key,
ir_module_module_dependency) required on installed envs before -u fusion_portal.
Document the rename gotcha as rule 16 in CLAUDE.md.
Redesign the Accessibility Assessment selector: replace Font Awesome icon tiles
with photo-banner cards using 7 optimized images (1000x750 PNG -> 800x600 JPEG,
~8MB -> 488KB), per-type colour accent bar + centered pill button, hover
lift/zoom. Images ship as module static files so they deploy/sync with the module.
Drop the regenerable graphify-out cache from the module.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Recomputing only x_fclk_break_minutes left historical x_fclk_net_hours / x_fclk_overtime_hours stale (add_to_compute+flush of one field does not cascade to dependents). Recompute the full chain in dependency order. Caught verifying the entech deploy.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Statutory unpaid break now deducts automatically from worked hours on every path - portal, kiosk, NFC, auto-clock-out cron, AND manual backend entry.
- new fusion.clock.break.rule per-province table (seed Ontario 5h->30, 10h->+30), resolved from the employee's company province with a global default fallback
- x_fclk_break_minutes is now a single idempotent stored compute (statutory(worked_hours) + penalties), replacing the 4 duplicated write sites (_apply_break_deduction x3 callsites + auto-clock-out cron + penalty write)
- retire break_threshold_hours (superseded by per-rule break1_after_hours); post-migrate drops the param and recomputes historical breaks
- 11 tests all green; module install + 19.0.4.1.0 migration verified on modsdev
Bump 19.0.4.0.3 -> 19.0.4.1.0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
People aren't good with 24h. Default Clock-In/Out are now AM/PM dropdowns (15-min
grid) instead of 24h float_time inputs. Stored value stays the float-string
(e.g. '9.0'), so all downstream float(get_param(...)) reads are unchanged;
persisted manually with get-snap for any off-grid value. Bump 19.0.4.0.3.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Settings tidy-up: one 'Kiosk' block holding both PIN Kiosk and NFC Kiosk
(clearly described so users know which is which), each with an Open-kiosk
button when enabled; Corrections + Sounds split into a 'Portal' block. Move
Auto-Wipe Photos under Photo Verification (was hidden for PIN-only clients).
Bump 19.0.4.0.0 -> 19.0.4.0.1.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A proper shared-device PIN kiosk for clients who don't want NFC: photo-tile grid
(+search) -> tap -> PIN (or first-use create) -> optional master-gated selfie ->
clock, in the NFC kiosk's dark glass + brand-gradient style. Built as an Odoo 19
Interaction; new pin_kiosk.scss (scoped); reworked clock_kiosk.py
(search +avatar/has_pin, verify_pin needs_setup, set_pin, clock via kiosk location).
Drops the redundant kiosk_pin_required (PIN always required); relabels the company
kiosk location; adds a PIN-kiosk app icon. Opt-in via enable_kiosk (off by default).
HttpCase tests added. Bump 19.0.4.0.0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The global enable_photo_verification toggle only fed the portal get_settings flag;
the actual writes ignored it — the NFC kiosk gated on nfc_photo_required and the
portal on location.require_photo, so photos were captured even with the toggle OFF.
Now it's the master: OFF => no photo captured/stored anywhere (NFC kiosk config +
tap, and portal check-in); ON => per-location / NFC settings apply. Test + help
text updated. Bump 3.16.0 -> 3.16.1.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Audit of all 41 settings found 3 that were shown but read nowhere, and 17 Boolean
toggles that couldn't be turned OFF.
- Remove grace_period_minutes (orphaned by the schedule-driven cron rewrite) and
weekly_overtime_threshold (never implemented): field + view + seed.
- enable_ip_fallback now actually gates _verify_location's IP-whitelist check
(default ON to preserve current behaviour).
- All 17 fusion_clock Boolean settings now persist explicitly as 'True'/'False'
via a _FCLK_BOOL_PARAMS loop in get_values/set_values (config_parameter Booleans
can't store False, so OFF never stuck). Add round-trip tests. Bump 3.15.2 -> 3.16.0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Pay Period Anchor Date was a free-text Char. Make it a fields.Date (date
picker) persisted manually in get_values/set_values as 'YYYY-MM-DD' under
fusion_clock.pay_period_start (res.config.settings Date fields don't round-trip
via config_parameter in Odoo 19). Reader code unchanged. Bump 3.15.1 -> 3.15.2.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
_resolve_tz fell back to env.company.tz, which raises AttributeError for any
user without a personal tz (surfaced by the new list-wide pay-period filters,
which resolve a company-level tz). Use env.company.partner_id.tz. Regression
test added. Bump 3.15.0 -> 3.15.1.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reuse the existing Pay Period setting (Frequency + Anchor) as the single
source of truth via a shared pure helper (models/pay_period.py); fusion.clock.report
delegates to it. Add Current/Previous/Next Pay Period filters to the attendance
search view (search-method computed booleans on hr.attendance), a Bi-Weekly Period
picker wizard (pick start -> auto +2 weeks, editable; Apply opens the filtered list)
reachable from an Attendance menu item and a dashboard tile. Window follows the
configured frequency; TZ-correct via local-day boundaries. Bump 3.14.4 -> 3.15.0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
hr_attendance's action is gantt-first and the native gantt timeline renders collapsed until a manual resize; open viewType:list so the button lands on a working list. Bump 3.14.3 -> 3.14.4.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Quick Actions were at the very bottom, so managers had to scroll past the whole team band to reach the nav shortcuts. Relocate the block to just above the Team/Org section (still below the personal band everyone has). Bump 3.14.2 -> 3.14.3.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Plain height:100%+overflow-y:auto did not scroll under the flex action container. Use the proven pattern: root flex column height:100%; inner .fclk-dash-wrap flex:1; min-height:0; overflow-y:auto. Bump 3.14.1 -> 3.14.2.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the 1200px centred cap (wasted side space) and make .fclk-dash height:100%; overflow-y:auto so tall content scrolls. Bump 3.14.0 -> 3.14.1.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Rework /fusion_clock/dashboard_data into a personal block (everyone)
plus a team block (team lead = direct reports, manager = org-wide).
A regular employee's payload never contains another employee's data.
- New OWL stacked layout: gradient KPI cards (Today/Week/OT/Streak),
Today's Shift, Recent Activity, Upcoming Leave, Recent Penalties; team
band adds Present/Absent/Late/Pending, roster, and Needs Attention.
- Dark/light via compile-time $o-webclient-color-scheme branching;
drop the old runtime html.o_dark dashboard block.
- Open the Dashboard menu to group_fusion_clock_user (lead/manager imply).
- Add HttpCase permission/no-leak tests. Bump 3.13.2 -> 3.14.0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Attendance now groups the operational records: All Attendances, Leave
Requests, Correction Requests, Penalties (Leaves + Penalties moved in from
top level).
- Scheduling groups all schedule-building: Shift Planner, Scheduled Shifts,
Shifts (templates, moved from Configuration), Schedule Audit.
- Configuration: Settings, Locations, Enroll NFC Card (the NFC wizard moved in
from top level).
- Removed the duplicate top-level Locations menu (kept the one under Config).
Only parent/sequence changed; no actions/views touched. Live on entech 3.13.2.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The "My Schedule" portal page read only published planning.slot (Odoo Planning),
but team leads post in the fusion_clock Shift Planner, which writes
fusion.clock.schedule -> so posted schedules never appeared. Merge both sources:
the page now lists published planning.slot AND posted fusion.clock.schedule
(employee, state=posted, not OFF, within the 60-day horizon), sorted together.
Verified on entech: Garry's 7 posted shifts (Jun 1-7) now render. 19.0.1.5.0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
iOS Safari date inputs have a large intrinsic min-width that can break a flex
row; switch .fclk-leave-daterange to grid 1fr 1fr + min-width:0 on the inputs
so the two fields always share the row and shrink. Also changes the bundle hash
to force iOS to drop the cached CSS. Live on entech 19.0.3.13.1.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>