diff --git a/docs/plans/fusion_maintenance_brainstorm.md b/docs/plans/fusion_maintenance_brainstorm.md index f3ec900d..266c3d4f 100644 --- a/docs/plans/fusion_maintenance_brainstorm.md +++ b/docs/plans/fusion_maintenance_brainstorm.md @@ -70,7 +70,15 @@ door payment via existing `fusion_poynt`. > from memory — read the real Enterprise `appointment` source before building the booking layer. ```bash -APP= ; DB= +# RESOLVED 2026-06-02 — Westin Odoo prod migrated OFF Digital Ocean onto the on-prem Proxmox +# cluster. Old DO IPs (152.42.146.204 / 178.128.229.92) are DEAD (:22 timeout). Live box: +# host `odoo-westin` = 192.168.1.40 via the `supabase-prod` Tailscale jump (Windows OpenSSH +# ProxyCommand → run `ssh odoo-westin ...` from PowerShell). App container `odoo-dev-app` +# (odoo:19, Enterprise); DB container `odoo-dev-db`; DB `westin-v19`; user `odoo` (local-socket +# trust inside odoo-dev-db). Enterprise addons → /mnt/enterprise-addons, custom → /mnt/extra-addons. +# SQL: ssh odoo-westin 'docker exec odoo-dev-db psql -U odoo -d westin-v19 -c "..."' +# FS read: ssh odoo-westin 'docker exec odoo-dev-app sed -n 1,160p /mnt/enterprise-addons/...' +APP=odoo-dev-app ; DB=westin-v19 ; DBC=odoo-dev-db # 1) Install matrix — confirm same-DB + Enterprise appointment present + versions docker exec "$APP" psql -U odoo -d "$DB" -c \ @@ -95,15 +103,61 @@ docker exec "$APP" psql -U odoo -d "$DB" -c \ "SELECT state, count(*) FROM fusion_repair_maintenance_contract GROUP BY state;" ``` +## STEP 0 — RESULTS (ran 2026-06-02 against Westin prod `westin-v19`) +> Grounding facts only — **no design decisions made**. These correct several assumptions above. + +**Connection (resolved):** host `odoo-westin` (192.168.1.40) via the `supabase-prod` Tailscale jump. +App container `odoo-dev-app` (odoo:19, Enterprise), DB container `odoo-dev-db`, DB `westin-v19`, +user `odoo`. Old Digital Ocean boxes are DEAD — Westin migrated on-prem. + +**1) Install matrix** — `appointment` **19.0.1.3 installed** (+ `appointment_account_payment`, +`_crm`, `_hr`, `_microsoft_calendar`, `_sms`). All deps present: `calendar`, `maintenance`, `repair`, +`sale_management`, `portal`, `website`, `resource`, `phone_validation`, `web_gantt`. `fusion_claims` +**19.0.9.2.0 installed**. `fusion_repairs` and `fusion_maintenance` are **absent entirely** (no +records). → a module depending on `appointment` installs cleanly; "reuse the fusion_repairs engine" +means *deploy fusion_repairs to Westin first* (heavy) **or** own a lean contract model here. Note +Odoo's native `maintenance` (CMMS) is installed — an under-considered third reuse option. + +**2) device_type** — 119 distinct values, but `fusion.adp.device.code` is the ADP billing-code +**CATALOG** (`_order='device_type, device_code'`), so counts are catalog codes per type, **NOT units +installed**. Top entries are seating COMPONENTS (Seat Cushion 564, Back Support 375, Headrest 193). +The maintainable **equipment classes** ≈ wheelchairs (manual + power tilt), power bases, power +scooters, wheeled walkers / walking frames, paediatric standing frames, specialty strollers (~6-8 +clean categories). → `device_type` can't be a 1:1 policy key (119 values, mostly parts); needs a +grouping/whitelist. **Real install base must be sized on `sale.order.line`** (`x_fc_adp_device_type` +[computed from product's `x_fc_adp_device_code_id.device_type`], `x_fc_serial_number`, +`x_fc_adp_approved`; delivery dates `x_fc_adp_delivery_date` / `x_fc_service_start_date`) — **NOT yet +run; this is the next grounding step.** + +**3) + 4) Enterprise appointment source** — `/mnt/enterprise-addons/appointment`. The no-login token +slot-picker is **mostly NATIVE — don't hand-roll it**: public booking (`auth="public"`), invite +tokens (`appointment.invite`, `/appointment/?…invite_token`), live availability +(`/appointment//update_available_slots`, jsonrpc/public), slot submit → real `calendar.event` +(`/appointment//submit`), auto/manual staff+resource assignment, capacity, booked/cancelled mail +templates. Model `appointment.type`; controller `controllers/appointment.py`. → the module mainly +needs to: seed an `appointment.type` per category, drop a partner-bound invite link into the reminder +email, and hook `calendar.event` create → spawn the service task + advance the contract. +`appointment_account_payment` is installed → native pay-to-book is on the table for the revenue mechanic. + +**5) Maintenance-contract state** — `relation "fusion_repair_maintenance_contract" does not exist` +→ confirms the fusion_repairs maintenance engine is **not** on Westin. + +**Headline correction:** Westin's ADP data has **zero** stair lifts / porch lifts / ramps / hospital +beds — those belong to the fusion_repairs / EN-Tech (mobility) domain. Westin's recurring-revenue +play is **wheelchairs / power bases / scooters / walkers / seating**. Open questions updated below. + ## 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)? +- **MVP cut**: reminders+booking for which device categories first? (Step 0 update: stair/porch lifts + do NOT exist in Westin's ADP data. Candidates are the powered units most likely to need recurring + service — power wheelchairs, power bases, power scooters — then manual wheelchairs / walkers.) - **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/` in place - (fusion_repairs isn't production-deployed yet) vs. add a new `/maintenance/book/` route. +- **Booking-portal strategy**: Step 0 shows Enterprise `appointment` already ships public, + token-based real-time booking (`appointment.invite` + `/appointment//...`, `auth="public"`). + Ride on that (generate an invite per reminder, partner pre-bound, no login) vs. a custom + `/maintenance/book/` route? (The `/repairs/...` route is moot — fusion_repairs isn't on Westin.) ## 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).