Files
Odoo-Modules/docs/plans/fusion_maintenance_brainstorm.md
Claude de3e0df5fc docs(fusion_maintenance): brainstorm + handoff brief for connected-env session
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
2026-06-02 04:01:54 +00:00

7.7 KiB

fusion_maintenance — Brainstorm & Handoff Brief

Status: research/brainstorm only — no code, no final decisions. Written from a Claude Code web session that could not reach the private network (no Tailscale, no docker daemon, Supabase KB unreachable). Resume from a Tailscale-connected env (dev box or a host that can reach Westin production) and do the live inspection in Step 0 before committing to the design.

Goal (user's words, paraphrased)

Automated maintenance follow-ups for mobility/accessibility equipment we've sold, to turn service into recurring revenue. Reminder emails → client books maintenance → booking happens in real time and lands in our calendar. Leverage Odoo Enterprise's appointment system. Decide whether this lives in fusion_repairs or a new module — the result must be seamless and production-ready.

Decisions locked with the user (this session)

  • Same DB: fusion_claims + fusion_repairs run on one database → new module may depend on both.
  • Enterprise appointment is available → build real-time booking ON it (appointment.type / appointment.slot / calendar.event), do not hand-roll a calendar.
  • Public self-serve booking → reminder email carries a token link to a no-login slot picker (extend the existing /repairs/maintenance/book/<token> pattern). Elderly clients shouldn't log in.
  • Target box for grounding = Westin production (where fusion_claims runs day-to-day).

Key findings from repo exploration

fusion_repairs (v19.0.2.2.6) ALREADY has a maintenance engine — reuse it, don't fork

  • fusion.repair.maintenance.contract: interval, due/last-service dates, state machine. Auto-spawned on SO confirm when product.template.x_fc_maintenance_interval_months > 0.
  • Daily reminder cron cron_maintenance_due_reminders → 30/7/1-day bands → branded email email_template_maintenance_due_reminder with tokenized link /repairs/maintenance/book/<token>.
  • Booking controller: controllers/portal_maintenance_booking.pysingle date-confirm form, NO slot availability, NO conflict check, NO calendar event. ← this is the real gap.
  • Contract roll-forward on technician-task completion (next_due_date += interval).
  • fusion.repair.service.plan.subscription: pre-paid visit plans (recurring-revenue primitive).
  • Deps: repair, maintenance, sale_management, stock, purchase, website, portal, fusion_tasks, fusion_poynt, fusion_authorizer_portal. ~8.3k LOC, 25+ models.

fusion_claims (v19.0.9.2.0) is the ideal trigger source

  • Claim container = sale.order (x_fc_sale_type: adp, odsp, wsib, insurance, march_of_dimes, …).
  • Equipment unit = sale.order.line.x_fc_serial_number + product_id.
  • Equipment category = fusion.adp.device.code.device_type (wheelchair, walker, hospital bed, stair lift, porch lift, custom ramp, …) — matches the user's "sale groups".
  • Schedule anchors: x_fc_adp_delivery_date, x_fc_service_start_date; gate on x_fc_adp_approved.
  • Customer = sale.order.partner_id; prescriber = x_fc_authorizer_id.
  • Already depends on calendar, fusion_tasks, ai, fusion_ringcentral.

Proposed architecture (PENDING live verification)

New module fusion_maintenance depending on fusion_repairs, fusion_claims, appointment. Reuses the existing contract/reminder/roll-forward engine; adds the 3 genuinely-missing pieces:

  1. fusion.maintenance.policy (ops-configurable, no code per category): device_typeinterval_months, reminder bands, service_product_id (priced visit), appointment_type_id, required technician skill. Turns "stair lift = 6 mo, $X" into data.
  2. Claims bridge (daily cron): scan fusion_claims sale.order.line for delivered+approved devices whose device_type matches an active policy → ensure a maintenance contract exists, anchored at delivery_date + interval. Idempotent (key on serial / sale-line). Extend the reused contract with x_fc_source_claim_line_id, x_fc_device_type, x_fc_policy_id so the repairs path and claims path both feed one contract model.
  3. Real-time booking on appointment: token link → slot picker backed by appointment.type (partner pre-resolved from token, no login). Slot pick → real calendar.event → hook spawns repair.order + technician task, assigns by skill/zone, advances reminder band, rolls contract forward.

Recurring revenue: each policy carries service_product_id → booked visit drafts a priced SO/invoice; optional pre-paid annual plan via existing service.plan.subscription; optional door payment via existing fusion_poynt.

STEP 0 — run on Westin production FIRST (grounding before any decision)

Replace APP/DB with the real Westin container + database. CLAUDE.md rule #1: never code from memory — read the real Enterprise appointment source before building the booking layer.

APP=<westin_app_container> ; DB=<westin_db>

# 1) Install matrix — confirm same-DB + Enterprise appointment present + versions
docker exec "$APP" psql -U odoo -d "$DB" -c \
"SELECT name,state,latest_version FROM ir_module_module \
 WHERE name IN ('fusion_claims','fusion_repairs','fusion_maintenance','calendar','maintenance','repair') \
    OR name LIKE 'appointment%' ORDER BY name;"

# 2) Real device_type distribution (drives per-category policies)
docker exec "$APP" psql -U odoo -d "$DB" -c \
"SELECT device_type, count(*) FROM fusion_adp_device_code GROUP BY device_type ORDER BY 2 DESC;"

# 3) Locate the Enterprise appointment source (read, don't guess the API)
docker exec "$APP" bash -lc 'ls -d /mnt/enterprise-addons/appointment 2>/dev/null || \
  find / -maxdepth 6 -type d -name appointment 2>/dev/null | grep -i addons | head'

# 4) Appointment model surface to build booking on (adjust path from #3)
docker exec "$APP" cat <appointment_path>/models/appointment_type.py | head -160
docker exec "$APP" ls <appointment_path>/controllers/    # find the public booking controller

# 5) How fusion_repairs maintenance contracts already look in live data
docker exec "$APP" psql -U odoo -d "$DB" -c \
"SELECT state, count(*) FROM fusion_repair_maintenance_contract GROUP BY state;"

Open questions to resolve with the user (in the connected session)

  • MVP cut: reminders+booking for which device categories first (e.g. stair/porch lifts — the compliance-heavy, highest recurring-revenue ones)?
  • Revenue mechanic: auto-draft a priced SO/invoice per booking, vs. pre-paid annual plan, vs. pay-at-door via Poynt — which is the default?
  • Technician assignment: auto-assign by skill+zone at booking time, or leave dispatch manual (fusion_tasks) and only reserve the calendar slot?
  • Booking-portal strategy: enhance the existing /repairs/maintenance/book/<token> in place (fusion_repairs isn't production-deployed yet) vs. add a new /maintenance/book/<token> route.

Applicable CLAUDE.md rules (don't relearn the hard way)

  • Rule #1: read reference files from the running instance before coding (esp. the appointment source).
  • Odoo 19: res.users.group_ids (not groups_id); ir.cron has no numbercall; declarative models.Constraint/models.Index; HTTP routes type="jsonrpc"; OWL uses standalone rpc().
  • No sale.subscription model exists — a subscription is a sale.order with is_subscription=True.
  • New fields use x_fc_ prefix; Canadian English; $ Monetary + currency_id.
  • Route attachment opens through fusion_pdf_preview (att.action_fusion_preview(...)).
  • Tests need --http-port=0 --gevent-port=0. Westin prod is Enterprise; local dev is Community (so the appointment-dependent module can't be installed/tested on odoo-modsdev-app).