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
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_repairsrun on one database → new module may depend on both. - Enterprise
appointmentis 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_claimsruns 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 whenproduct.template.x_fc_maintenance_interval_months > 0.- Daily reminder cron
cron_maintenance_due_reminders→ 30/7/1-day bands → branded emailemail_template_maintenance_due_reminderwith tokenized link/repairs/maintenance/book/<token>. - Booking controller:
controllers/portal_maintenance_booking.py— single 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 onx_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:
fusion.maintenance.policy(ops-configurable, no code per category):device_type→interval_months, reminder bands,service_product_id(priced visit),appointment_type_id, required technician skill. Turns "stair lift = 6 mo, $X" into data.- Claims bridge (daily cron): scan
fusion_claimssale.order.linefor delivered+approved devices whosedevice_typematches an active policy → ensure a maintenance contract exists, anchored atdelivery_date + interval. Idempotent (key on serial / sale-line). Extend the reused contract withx_fc_source_claim_line_id,x_fc_device_type,x_fc_policy_idso the repairs path and claims path both feed one contract model. - Real-time booking on
appointment: token link → slot picker backed byappointment.type(partner pre-resolved from token, no login). Slot pick → realcalendar.event→ hook spawnsrepair.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/DBwith the real Westin container + database. CLAUDE.md rule #1: never code from memory — read the real Enterpriseappointmentsource 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(notgroups_id);ir.cronhas nonumbercall; declarativemodels.Constraint/models.Index; HTTP routestype="jsonrpc"; OWL uses standalonerpc(). - No
sale.subscriptionmodel exists — a subscription is asale.orderwithis_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 onodoo-modsdev-app).