3.8 KiB
3.8 KiB
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=... fromROeffect in QuoteGenerator.tsx loads the RO, setsrepairOriginId = ro.id- User adds/approves services, clicks Save
handleSavein QuoteSummary.tsx creates the quote record withrepairOrderId: repairOriginId(line 125)- Sync block runs: filters
services.filter(s => s.approved), loads current RO services, merges (by service name — avoids duplicates), updates RO withJSON.stringify(roServices) - Toast:
"Synced N approved service(s) to RO"
2. Editing Existing Quote from RO
- Quote has
repairOrderIdset 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:
repairOriginIdis set (non-null)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 asJSON.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
- Create an RO owned by the current user
- Generate a quote from it (
/quote?fromRO=RO_ID) - Add and approve services
- Click Save
- Check the RO in PocketBase —
servicesshould contain the approved services - 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
handleSaveandensureShareToken(new-quote path only) - RO
servicesfield is written asJSON.stringify(roServices)— always produces valid JSON arrays