Files
2026-07-12 10:17:17 -04:00

82 lines
3.8 KiB
Markdown

# RO-Quote Sync Mechanism
## Overview
When a quote is generated from a Repair Order (RO), approved services on the quote sync back to the RO's services array on Save. This is the bidirectional RO ↔ Quote link.
## Key Files
| File | Role |
|------|------|
| `src/components/quoteGenerator/QuoteSummary.tsx` | Sync logic in `handleSave` (lines 146-194) and `ensureShareToken` (lines ~270-303) |
| `src/pages/QuoteGenerator.tsx` | Sets `repairOriginId` from `data.repairOrderId` on edit load (line ~524) and from `fromRO` URL param (line ~103) |
## Data Flow
### 1. New Quote from RO
- User clicks "Generate Quote" from RO → navigates to `/quote?fromRO=RO_ID&name=...`
- `fromRO` effect in QuoteGenerator.tsx loads the RO, sets `repairOriginId = ro.id`
- User adds/approves services, clicks Save
- `handleSave` in QuoteSummary.tsx creates the quote record with `repairOrderId: repairOriginId` (line 125)
- Sync block runs: filters `services.filter(s => s.approved)`, loads current RO services, merges (by service name — avoids duplicates), updates RO with `JSON.stringify(roServices)`
- Toast: `"Synced N approved service(s) to RO"`
### 2. Editing Existing Quote from RO
- Quote has `repairOrderId` set in PocketBase
- Edit-load effect restores `setRepairOriginId(data.repairOrderId)` (QuoteGenerator.tsx line ~524)
- User approves more services, clicks Save
- Sync block runs same as above
### 3. Share with Customer (ensureShareToken)
- **New quote**: Creates the quote record, syncs approved services to RO, then creates share token. Same merge logic as handleSave.
- **Editing existing quote** (editId set): The `if (!quoteId)` block is SKIPPED — no sync on share of existing quotes. The quote is read from PB, share token created/returned. **This is a known gap.**
## Critical Conditions
The sync block only runs when BOTH are true:
1. `repairOriginId` is set (non-null)
2. `services.filter(s => s.approved).length > 0`
## Why Sync Might Not Run
| Cause | Symptom |
|-------|---------|
| Quote's `repairOrderId` is null in PB | `repairOriginId` stays null; entire sync block skipped silently |
| No services approved | Sync block skipped; no toast shown (by design) |
| RO load fails (permissions/network) | Falls to catch block → error toast: `"Sync to RO failed: ..."` |
| Schema validation fails before sync | Error toast from Zod; sync never reached |
**Most common cause**: The quote was created before the `repairOrderId` field existed. Check the quote in PocketBase admin — if `repairOrderId` is empty, the quote has no RO link. Generate a new quote from the RO to get the field populated.
## PocketBase Fields
- `quotes.repairOrderId` — relation field linking quote to its originating RO. Set at quote creation (line 125 in handleSave).
- `repairOrders.services` — stored as `JSON.stringify(array)` (plain JSON text field, NOT a PocketBase relation).
## Sync Merge Logic
Services are merged by **name** (not ID):
- If RO already has a service with the same name → update it (preserving status if already set)
- If name not found → append new service
Each synced service gets: `id: 'qs-{quoteService.id}'`, `name`, `description`, `laborHours` (from price/rate), `laborRate`, `partsCost: 0`, `total: price`, `status: 'pending'`
## Testing the Sync
1. Create an RO owned by the current user
2. Generate a quote from it (`/quote?fromRO=RO_ID`)
3. Add and approve services
4. Click Save
5. Check the RO in PocketBase — `services` should contain the approved services
6. Verify via API: `GET /api/collections/repairOrders/records/RO_ID`
## Previous Investigation
- The sync code was verified working end-to-end (2026-07-08)
- All debug `[SPQ-DEBUG]` console.logs have been removed
- The sync runs in both `handleSave` and `ensureShareToken` (new-quote path only)
- RO `services` field is written as `JSON.stringify(roServices)` — always produces valid JSON arrays