Merge fusion_maintenance brainstorm, design spec & Plan 1 into main
Docs only: the fusion_maintenance brief (+ Westin Step 0 / install-base sizing), the approved design spec (build into fusion_repairs; flat-fee per type; new-sale trigger + two-regime backfill; technician-aware booking on fusion_tasks), and Plan 1 (Foundation) + Plans 2-5 roadmap. Implementation pending. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
298
docs/superpowers/specs/2026-06-02-fusion-maintenance-design.md
Normal file
298
docs/superpowers/specs/2026-06-02-fusion-maintenance-design.md
Normal file
@@ -0,0 +1,298 @@
|
||||
# fusion_maintenance — Design Spec
|
||||
|
||||
> Automated preventive‑maintenance follow‑ups + self‑serve real‑time booking for Westin
|
||||
> medical mobility equipment (stair lifts, porch lifts, lift chairs, wheelchairs, power
|
||||
> wheelchairs/scooters), to keep clients on schedule and turn service into recurring revenue.
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Status** | Design **approved** (brainstorm dialogue 2026‑06‑02). Ready for implementation plan. |
|
||||
| **Implemented by** | **Extending `fusion_repairs`** (no new module). Version bump. |
|
||||
| **Target instance** | Westin production — host `odoo-westin` (192.168.1.40), container `odoo-dev-app`, DB `westin-v19`. One company / one DB running `fusion_claims` (live) + `fusion_repairs` (to be deployed). |
|
||||
| **Relates to** | [`docs/plans/fusion_maintenance_brainstorm.md`](../../plans/fusion_maintenance_brainstorm.md) (brief + Step 0 + sizing), [`2026-05-20-fusion-repairs-design.md`](2026-05-20-fusion-repairs-design.md) (base module). |
|
||||
| **Next step** | `writing-plans` → implementation plan. **No code until the plan is written and this spec is reviewed.** |
|
||||
|
||||
---
|
||||
|
||||
## 1. Goal
|
||||
|
||||
Westin sells/services mobility equipment that needs preventive maintenance every **1–6 months
|
||||
depending on the product**. Today there is no system keeping clients on schedule. We want:
|
||||
|
||||
1. The system **automatically emails the client** when a unit is due for maintenance.
|
||||
2. The client can **book the visit themselves** (real‑time, self‑serve, no login) **or** call the
|
||||
office and staff book it for them.
|
||||
3. The booking **lands in our scheduling/calendar** as a real technician job.
|
||||
4. The **technician accesses and updates the maintenance log** on the visit; the system keeps the
|
||||
full history per unit.
|
||||
5. The **next maintenance is auto‑rescheduled** → recurring loop.
|
||||
6. The client is **told the cost** up front.
|
||||
7. Outcome: clients stay on track **and** Westin gains **recurring revenue**.
|
||||
8. Design/UX stays **consistent with `fusion_claims`** (branded emails, `x_fc_` naming, Canadian
|
||||
English, `$`+`currency_id`).
|
||||
|
||||
## 2. Locked decisions (from the brainstorm)
|
||||
|
||||
| # | Decision | Choice | Why |
|
||||
|---|----------|--------|-----|
|
||||
| D1 | Separate module vs. part of `fusion_repairs` | **Build into `fusion_repairs`** | The maintenance engine already lives there (~90% built); a separate module would duplicate it. fusion_repairs already owns the equipment categories, `repair.order`, technician tasks, service plans, and the Westin rate card. |
|
||||
| D2 | Pricing / revenue model | **Flat fee per equipment type** | Transparent cost to show the client; recurring per‑visit revenue. Configured per equipment **category** with per‑product override. |
|
||||
| D3 | Enrollment scope | **New sales + backfill existing install base** | The recurring revenue and "keep clients on track" value is in the *existing* base, not just future sales. |
|
||||
| D4 | Booking engine | **Technician‑aware picker on `fusion_tasks`** (NOT Enterprise `appointment`) | Clients see only slots a qualified tech is genuinely free for (route/skill‑aware); booking creates the technician task directly — one scheduling world, no appointment↔task bridge. Bonus: **no Enterprise dependency → Community‑testable locally.** |
|
||||
|
||||
## 3. Grounding (verified, not assumed)
|
||||
|
||||
### 3.1 What `fusion_repairs` ALREADY has (reuse — do not rebuild)
|
||||
Source: [`fusion_repairs/models/maintenance_contract.py`](../../../fusion_repairs/models/maintenance_contract.py), [`technician_task.py`](../../../fusion_repairs/models/technician_task.py), [`repair_service_plan.py`](../../../fusion_repairs/models/repair_service_plan.py), `cloud.md`.
|
||||
|
||||
- `fusion.repair.maintenance.contract` — partner/product/lot/original_SO, `interval_months`,
|
||||
`last_service_date`, `next_due_date`, state machine (`draft/active/paused/cancelled`),
|
||||
`booking_token` (unique), `last_reminder_band`, `booking_repair_id`. `roll_next_due_date()`
|
||||
advances the cycle correctly via `relativedelta`.
|
||||
- Reminder cron `cron_send_due_reminders` — daily, **30/7/1‑day** bands, per‑band dedup, queued
|
||||
branded email `email_template_maintenance_due_reminder` with the tokenized link.
|
||||
- Public booking controller `/repairs/maintenance/book/<token>` — `auth='public'`, token‑validated,
|
||||
already‑booked guard, thanks page.
|
||||
- `create_repair_from_booking()` — spawns a `repair.order` (`x_fc_intake_source='client_portal'`),
|
||||
links `x_fc_maintenance_contract_id`, dedups.
|
||||
- **Roll‑forward** on technician task completion ([`technician_task.py:88`](../../../fusion_repairs/models/technician_task.py:88)): when a `task_type='maintenance'` task → `status='completed'`, sets `last_service_date`, calls `roll_next_due_date()`, posts chatter. **This is the recurring loop.**
|
||||
- Pre‑paid **service‑plan subscriptions** (`fusion.repair.service.plan.subscription`) wired to
|
||||
`sale.order.action_confirm()` + visit burn engine (revenue primitive; optional here).
|
||||
- **Rate card** (`fusion.repair.callout.rate`, standard vs `lift_elevating`), `repair.order.x_fc_quote_total`.
|
||||
- **Equipment category taxonomy** (`fusion.repair.product.category`): stairlift / porch_lift /
|
||||
lift_chair flagged `equipment_class=lift_elevating`, `safety_critical=True`.
|
||||
- **Inspection certificate** (`fusion.repair.inspection.certificate`, M1 — Done): PDF + expiry cron.
|
||||
- Visit‑report wizard (signature, parts, labour timer).
|
||||
- `product.template.x_fc_maintenance_interval_months` (exists, [product_template.py:23](../../../fusion_repairs/models/product_template.py:23)).
|
||||
- `fusion_tasks` availability engine: [`_find_next_available_slot(tech_id, date, ...)`](../../../fusion_tasks/models/technician_task.py:544) and [`_get_available_gaps(tech_id, date, ...)`](../../../fusion_tasks/models/technician_task.py:664) — **route‑aware** (tech start address + geocoding + travel). Tech skills on `res.users.x_fc_repair_skills`.
|
||||
|
||||
### 3.2 The 4 gaps this spec closes
|
||||
1. **Contract auto‑creation trigger is dead code** — `_spawn_maintenance_contracts()` is defined on
|
||||
`sale.order` ([maintenance_contract.py:198](../../../fusion_repairs/models/maintenance_contract.py:198)) but **never called**. No `action_confirm` override invokes it → no contracts exist today.
|
||||
2. **No real booking** — the booking page is a bare `<input type="date">` ("a team member will call
|
||||
to confirm"); no availability, no slots, no calendar/task. **This is the main new build.**
|
||||
3. **No cost shown to the client** anywhere (email or booking page).
|
||||
4. **No auto tech‑task creation, no structured maintenance log, no office‑follow‑up crons**
|
||||
(`ir.config_parameter` toggles exist; no cron/Python).
|
||||
|
||||
### 3.3 Install‑base sizing (Westin live, 2026‑06‑02)
|
||||
- Serial numbers are captured **~only on real equipment** (parts have 0 serials) → `x_fc_serial_number`
|
||||
is a de‑facto "trackable unit" marker and the natural **idempotency key**.
|
||||
- ADP‑side base ≈ **138 serial‑tracked units / ~136 customers** (walkers 68, wheelchairs 45, power
|
||||
bases 7, scooters 4, +14 no‑device‑type). Funders: adp 109, direct_private 13, adp_odsp 10,
|
||||
march_of_dimes 7. Deliveries 2022‑10 → 2026‑05.
|
||||
- **Lifts (sized 2026‑06‑02; name‑based, approximate)** — a LARGE base in Westin's Odoo: stair lifts
|
||||
~254 customers (416 lines incl. accessories), porch/VPL ~30 customers (75 lines), lift chairs ~41
|
||||
customers (47 lines) — real products (Access BDD, Handicare, Serenity VPL, Pride VivaLift). **But lift
|
||||
serial coverage is ~0** (12/416 stairlift lines, 0 VPL, 2 lift‑chair). So the serial‑as‑unit‑key
|
||||
approach that works for ADP wheelchairs **does NOT work for lifts** — lifts must be keyed by
|
||||
(partner + base‑unit product + sale line), excluding accessory lines (curves, rails, remotes, charging
|
||||
stations, rentals). This splits the backfill into two regimes (§6.2).
|
||||
- Two backfill data gaps: 14 units have no device_type (need product/manual category); non‑ADP units
|
||||
lack `x_fc_adp_delivery_date` (need an invoice/order‑date fallback anchor).
|
||||
|
||||
## 4. Architecture
|
||||
|
||||
Extend `fusion_repairs`. No new module, no new top‑level dependency for the core flow (booking uses
|
||||
`fusion_tasks`, already a hard dep; pricing/Poynt already deps). The optional `fusion_claims` read
|
||||
for the wheelchair backfill is a **soft** dependency (guarded `if 'fusion.claims' model present`),
|
||||
so `fusion_repairs` still installs/test‑runs without `fusion_claims` on local dev.
|
||||
|
||||
Reuse map: contract engine (extend), `fusion.technician.task` (booking target + availability +
|
||||
roll‑forward), `repair.order` (visit container/pricing/Poynt), inspection certificate (lift
|
||||
compliance), visit‑report wizard (extend with checklist), branded email pattern, rate card.
|
||||
|
||||
## 5. Data model
|
||||
|
||||
All new fields `x_fc_`, Canadian English labels, Monetary = `$` + `currency_id`.
|
||||
|
||||
### 5.1 Maintenance policy — on `fusion.repair.product.category` ("per equipment type")
|
||||
- `x_fc_maintenance_enabled` (Boolean) — is this category maintainable?
|
||||
- `x_fc_maintenance_interval_months` (Integer) — default cadence (1–6+).
|
||||
- `x_fc_maintenance_fee` (Monetary, `currency_id`) — the **flat fee** shown to the client.
|
||||
- `x_fc_maintenance_skill_id` — the technician skill the booking matches on (maps to
|
||||
`res.users.x_fc_repair_skills`). **If skills are already category‑based** (a tech's
|
||||
`x_fc_repair_skills` are equipment categories), drop this field and simply match technicians whose
|
||||
skills include *this* category — confirm the skills representation before modelling (§15).
|
||||
- `x_fc_maintenance_service_product_id` (M2O `product.product`, optional) — the service product used
|
||||
when drafting the priced invoice/SO line; falls back to a generic "Maintenance visit" product.
|
||||
|
||||
**Per‑product override:** `product.template.x_fc_maintenance_interval_months` (exists) +
|
||||
new `product.template.x_fc_maintenance_fee` (Monetary, optional). Resolution order at contract
|
||||
creation: product override → category policy.
|
||||
|
||||
### 5.2 Extend `fusion.repair.maintenance.contract`
|
||||
- `x_fc_maintenance_fee` (Monetary) — resolved price snapshot, shown to client.
|
||||
- `x_fc_source` (Selection: `sale` / `backfill` / `claims` / `manual`).
|
||||
- `x_fc_source_sale_line_id` (M2O `sale.order.line`) — provenance + idempotency.
|
||||
- `x_fc_device_serial` (Char, indexed) — idempotency key (esp. for claims/backfill where no lot).
|
||||
- `x_fc_policy_category_id` (M2O `fusion.repair.product.category`).
|
||||
- Constraint: at most one **active** contract per `(x_fc_device_serial)` (or per source sale line
|
||||
when serial absent) — declarative `models.Constraint` / partial `models.Index`.
|
||||
|
||||
### 5.3 New `fusion.repair.maintenance.visit` (the log)
|
||||
A structured, queryable per‑visit record — *not* buried in chatter.
|
||||
- `contract_id` (M2O, required), `technician_task_id` (M2O `fusion.technician.task`),
|
||||
`repair_order_id` (M2O `repair.order`, the container), `partner_id`, `product_id`, `lot_id`.
|
||||
- `visit_date`, `technician_id` (res.users), `state` (`scheduled/in_progress/done/no_show/cancelled`).
|
||||
- `checklist_line_ids` (O2M to `fusion.repair.maintenance.checklist.line`: label, result
|
||||
`pass/fail/na`, note) — items seeded **per equipment category** (lift checklist ≠ wheelchair
|
||||
checklist).
|
||||
- `findings` (Html, `Markup()`), `parts_note`, `x_fc_fee` (Monetary), `signature` (Binary),
|
||||
`inspection_certificate_id` (M2O — set for `safety_critical` categories).
|
||||
- "log/history" view = the list of visits per contract/unit (smart button on contract + partner).
|
||||
|
||||
## 6. Enrollment — two paths
|
||||
|
||||
### 6.1 Path A — new sales (fix the dead trigger)
|
||||
Override `sale.order.action_confirm()` to call `_spawn_maintenance_contracts()` (reuse the existing
|
||||
method; fix + wire it). For each confirmed line whose product/category has
|
||||
`x_fc_maintenance_enabled` and a serial/lot:
|
||||
- Create one `active` contract per unit (respect quantity), `x_fc_source='sale'`,
|
||||
`x_fc_source_sale_line_id` set, serial captured.
|
||||
- `next_due_date = (delivery/commitment date or date_order) + interval` (fallback chain handles
|
||||
non‑ADP units lacking a delivery date).
|
||||
- Resolve + snapshot `x_fc_maintenance_fee`.
|
||||
- **Idempotent**: skip if an active contract already exists for the serial / sale line.
|
||||
|
||||
### 6.2 Path B — backfill existing install base (one‑time wizard, idempotent)
|
||||
`fusion.repair.maintenance.backfill.wizard`:
|
||||
- **Scan** historical `sale.order.line` for products whose category/product is maintenance‑enabled and
|
||||
were delivered. **Two unit‑identity regimes**, because lifts carry no serials (§3.3):
|
||||
- **Serial‑tracked** (ADP wheelchairs/power chairs, via the `fusion_claims` serial/`device_type` data
|
||||
— soft dep, guarded; map ADP `device_type` → maintenance category): require a serial, **dedup by serial**.
|
||||
- **Non‑serial** (lifts — stair/porch/VPL/lift‑chair): do **NOT** require a serial. One contract per
|
||||
**base‑unit line**, **dedup by (partner + maintainable product + source sale line)**. The per‑product
|
||||
`x_fc_maintenance_enabled` flag is what includes base units and **excludes accessory lines** (curves,
|
||||
rails, remotes, charging stations, rentals) — only the lift itself gets a contract, not its add‑ons.
|
||||
- **Stagger** the first `next_due_date` across a configurable window (e.g. spread overdue units over
|
||||
N weeks) so years of equipment don't all email on day one.
|
||||
- **Dry‑run first**: produce a report (counts by category, # new vs already‑enrolled, # skipped for
|
||||
missing serial/date, the stagger schedule). Nothing is created or emailed until the operator
|
||||
approves and runs "Execute".
|
||||
- Anchor fallback for units with no delivery date: invoice date → order date → today.
|
||||
|
||||
## 7. Booking flow (the main build)
|
||||
|
||||
### 7.1 Client self‑serve (no login)
|
||||
1. Reminder email (existing branded template, **+ fee line added**) → tokenized link.
|
||||
2. Public slot‑picker page (extend the existing `/repairs/maintenance/book/<token>` route; replace
|
||||
the date input). The page:
|
||||
- Resolves the contract from the token; shows unit + **flat fee** ("$X + applicable tax").
|
||||
- Computes candidate technicians = users whose `x_fc_repair_skills` include the policy's
|
||||
`x_fc_maintenance_skill_id`.
|
||||
- Calls `fusion_tasks` `_get_available_gaps` / `_find_next_available_slot` per candidate tech over
|
||||
the next ~2–3 weeks, ranked by **proximity** to the client address → presents a short list of
|
||||
real open slots (date + window + implied tech).
|
||||
3. Client picks a slot → POST confirm:
|
||||
- **Re‑validate** the slot is still free (gap check) — if taken/expired, re‑render slots with a
|
||||
gentle notice (prevents double‑booking).
|
||||
- Create a `fusion.technician.task` (`task_type='maintenance'`) on that slot, **assigned to the
|
||||
qualified tech** (auto‑assignment by availability+skill), linked to the contract.
|
||||
- Spawn/link the maintenance‑type `repair.order` (container) + the `fusion.repair.maintenance.visit`
|
||||
(state `scheduled`, checklist seeded from the category).
|
||||
- Send the branded confirmation email (date/window/tech, fee, what to expect).
|
||||
- Set `booking_repair_id` (dedup).
|
||||
4. **No‑slot fallback:** if no qualified tech/slot in range → show "request a callback" → create an
|
||||
office activity. Never a dead end.
|
||||
|
||||
### 7.2 Office books on the client's behalf
|
||||
- A **"Book maintenance"** action on the `fusion.repair.maintenance.contract` form opens the same
|
||||
slot‑picker logic in the backend (office books while on the phone).
|
||||
- The existing dispatch board remains available for manual scheduling/override.
|
||||
|
||||
### 7.3 Token security fix
|
||||
On `roll_next_due_date()`, **regenerate `booking_token`** (currently it is not regenerated, so an
|
||||
old link stays valid across cycles). Old token → friendly "link expired" page.
|
||||
|
||||
## 8. Cost & revenue
|
||||
|
||||
- The **flat fee** (`x_fc_maintenance_fee`) is shown in **both** the reminder email and the
|
||||
slot‑picker page, Canadian English, `$` + tax note.
|
||||
- On booking, draft a priced line (SO/invoice) using `x_fc_maintenance_service_product_id` (or the
|
||||
generic visit product) at the contract's fee. Payment options: **pay‑at‑door via `fusion_poynt`**
|
||||
(existing `action_collect_payment` on the repair) or invoice after the visit.
|
||||
- Recurring revenue = one priced visit per cycle; the roll‑forward arms the next cycle automatically.
|
||||
(Pre‑paid annual plan upsell via the existing subscription engine is out of v1 — §11.)
|
||||
|
||||
## 9. Maintenance log & the recurring loop
|
||||
|
||||
- The technician fills the visit via the **extended visit‑report wizard** (existing tool) — checklist
|
||||
results, findings, parts, signature — which writes the `fusion.repair.maintenance.visit` record.
|
||||
- For `safety_critical` categories (lifts), completing the visit **issues an inspection certificate**
|
||||
(reuse M1) and links it on the visit — the log doubles as compliance proof.
|
||||
- On task `status='completed'` → existing **roll‑forward**: `last_service_date=today`,
|
||||
`next_due_date += interval`, reset `last_reminder_band`, **regenerate token**, visit → `done`.
|
||||
- Next cycle's reminder fires automatically when `next_due_date` re‑enters the 30‑day band.
|
||||
|
||||
## 10. Office follow‑up crons (toggle‑gated, exist as config only today)
|
||||
- **Unbooked**: reminder sent, no booking after N days → office call activity on the contract.
|
||||
- **Overdue**: `next_due_date` passed with no completed visit in the cycle → escalation activity.
|
||||
- Driven by the existing `ir.config_parameter` toggles in `data/ir_config_parameter_data.xml`.
|
||||
- Per‑row **savepoint** isolation inside the cron loop (no `cr.commit()` in tests — CLAUDE.md #14).
|
||||
|
||||
## 11. Out of scope (v1 — YAGNI)
|
||||
- SMS reminders / two‑way SMS booking (needs `fusion_ringcentral`).
|
||||
- Logged‑in `/my/equipment` client portal (X5).
|
||||
- Pre‑paid annual maintenance‑plan auto‑upsell at booking.
|
||||
- Full multi‑stop route optimization / batching (we use per‑tech availability + proximity ranking,
|
||||
not a global optimizer).
|
||||
- ADP funder re‑billing of maintenance (maintenance is private‑pay flat fee in v1).
|
||||
|
||||
## 12. Error handling & edge cases
|
||||
- **Double‑booking:** re‑validate the gap at confirm; lose the race → re‑show slots.
|
||||
- **Token:** per‑cycle regeneration; invalid/expired/already‑booked → friendly pages (exist, extend).
|
||||
- **No qualified tech / no slots:** callback fallback, not an error page.
|
||||
- **Backfill:** dry‑run + report; strict serial dedup; stagger; fallback anchor chain; never email on
|
||||
dry‑run.
|
||||
- **Missing data:** units with no device_type/category → excluded from auto‑backfill, listed in the
|
||||
report for manual enrollment.
|
||||
- **Audit on failure paths** (if any "booking failed" row is written in an `except`): use a separate
|
||||
`self.env.registry.cursor()` so it survives rollback (CLAUDE.md audit rule).
|
||||
- **`message_post` HTML** bodies wrapped in `Markup()` (CLAUDE.md).
|
||||
|
||||
## 13. Testing
|
||||
`fusion_repairs/tests/` (none exist today). Local dev is **Community** and — because we chose
|
||||
`fusion_tasks` over Enterprise `appointment` — the **entire feature is Community‑testable** on
|
||||
`odoo-modsdev`. `TransactionCase` coverage:
|
||||
- Contract spawn on `sale.order` confirm (enabled vs disabled category; quantity; idempotency).
|
||||
- Backfill wizard: **two‑regime dedup** (serial for wheelchairs; partner+product+line for lifts), accessory‑line exclusion, stagger, dry‑run produces no records, anchor fallback.
|
||||
- Booking: slot list comes from real gaps; confirm creates task+repair+visit; **double‑book guard**;
|
||||
no‑slot fallback.
|
||||
- Roll‑forward on completion: dates advance, band reset, **token regenerated**, visit → done.
|
||||
- Crons: reminder bands; unbooked/overdue follow‑ups (savepoint isolation).
|
||||
- Run: `docker exec odoo-modsdev-app odoo -d fusion-dev --test-enable --test-tags /fusion_repairs -u fusion_repairs --stop-after-init --http-port=0 --gevent-port=0`.
|
||||
|
||||
## 14. Deployment & configuration
|
||||
1. Land on local dev, full E2E + tests green.
|
||||
2. **Deploy `fusion_repairs` to Westin** (`odoo-westin` / `westin-v19`) — the accepted bigger lift
|
||||
(first production deploy of fusion_repairs; verify rate‑card numbers, ACLs, asset bundles).
|
||||
3. **Configure** maintainable categories: `x_fc_maintenance_enabled`, interval, fee, skill, service
|
||||
product — for lifts (stairlift/porch/lift chair) + power & manual wheelchairs.
|
||||
4. Ensure technicians have `x_fc_repair_skills` + start addresses (for availability/routing).
|
||||
5. Run the **backfill wizard dry‑run → review report → execute** (staggered).
|
||||
6. Watch the first reminder/booking cycle; confirm emails, slots, task creation, completion → roll.
|
||||
|
||||
## 15. Open items to verify at implementation (rule #1 — read live source)
|
||||
- Exact representation of tech skills (`res.users.x_fc_repair_skills`) and how a category's required
|
||||
skill maps to it (Selection vs M2O vs tag) — read fusion_repairs/fusion_tasks before modelling
|
||||
`x_fc_maintenance_skill_id`.
|
||||
- Signatures of `_find_next_available_slot` / `_get_available_gaps` (params, return shape, working
|
||||
hours source) and whether they already account for travel windows.
|
||||
- The visit‑report wizard's current fields/flow before extending it with the checklist.
|
||||
- The inspection‑certificate issue API (how M1 creates a certificate) for the lift link.
|
||||
- **Lift base sized** (§3.3): ~254 stairlift + ~30 porch/VPL + ~41 lift‑chair customers, but ~0 serials.
|
||||
Still to verify: which exact products are **base units vs accessories** (so `x_fc_maintenance_enabled`
|
||||
lands on base units only), plus the lift interval/fee per category. Lift products aren't yet tagged
|
||||
with `fusion_repairs` categories on Westin (module not deployed there) — categorization is a deploy step.
|
||||
- `fusion_claims` device_type → maintenance‑category mapping table for the wheelchair backfill.
|
||||
|
||||
## 16. Build sequence (for the implementation plan)
|
||||
1. **Policy + fee data model** (category fields, product override, contract extensions, constraints).
|
||||
2. **Path A trigger** (wire `_spawn_maintenance_contracts` into `action_confirm`, fee resolution, anchor fallback) + tests.
|
||||
3. **Cost in email** (add fee to the reminder template).
|
||||
4. **Technician‑aware booking** (slot‑picker page + controller on `fusion_tasks` availability; task/repair/visit creation; double‑book guard; office action; token regen) + tests — the largest unit.
|
||||
5. **Maintenance visit log + checklist** (model, per‑category seed, visit‑report‑wizard extension, inspection‑cert link) + tests.
|
||||
6. **Backfill wizard** (scan/dedup/stagger/dry‑run; fusion_claims soft bridge) + tests.
|
||||
7. **Office follow‑up crons** (unbooked/overdue) + tests.
|
||||
8. **Deploy + configure + backfill** on Westin.
|
||||
Reference in New Issue
Block a user