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

112 lines
4.9 KiB
Markdown

# Customer DB Integration in SPQ-v2 (All Forms → Customers Collection)
## Overview
When a shop creates a quote, appointment, or repair order, the customer info should be saved to the `customers` PocketBase collection and linked via `customerId` across all record types. This reference documents the full pattern as applied to Quotes, Appointments, and Repair Orders.
## Architecture
```
User types first/middle/last name + phone in form
debounced client-side customer matching (400ms)
Match found? ─yes─→ Show "Use Existing Customer" banner
| ↓
no User clicks "Use"
| ↓
↓ Form populated, customerId set
Auto-create on save
(ensureCustomerRecord)
customers collection (PB)
customerId on primary record (appointment / RO / quote)
```
## Shared Helper Library
**`src/lib/customerName.ts`** provides three reusable functions:
- **`composeName(first, middle, last)`** — joins parts into a single `name` string
- **`parseName(fullName)`** — splits into `{ firstName, middleName, lastName }` (first-word/last-word heuristic)
- **`findMatchingCustomer<T>(customers, query)`** — generic matcher: phone (exact 3+ digits) → exact full name → partial name. Generic so it preserves the input type.
### Type Changes
**`CustomerFormData`** (src/components/customers/types.ts):
- Added `firstName`, `middleName`, `lastName` alongside existing `name` field
- The `name` field is auto-composed from the three parts via `composeName()`
**`customerWriteSchema`** (src/schemas/customer.ts):
- Added optional `firstName`, `middleName`, `lastName` (defaults to '')
## Form-Specific Implementations
### AppointmentModal (src/components/appointments/AppointmentModal.tsx)
- **Inputs:** 3-column grid for First / Middle / Last Name (middle optional)
- **Customer matching:** Debounced 400ms, searches loaded customer list client-side
- **Suggestion banner:** Shows "Existing customer found — Name [Use] [Continue as New]"
- **Auto-create:** `ensureCustomerRecord()` on submit — creates customer in PB, sets `customerId`
- **Linked indicator:** Green badge "Linked to customer record: Name"
- **Props:** `customers: CustomerWithVehicles[]` (loaded by parent AppointmentsPage)
### ROForm (src/components/ROForm.tsx)
- Same 3-input layout as AppointmentModal
- Same matching + suggestion banner + use/dismiss controls
- Same `ensureCustomerRecord()` on submit
- Props: `customers?: CustomerWithVehicles[]` (optional, defaults to [])
- Threaded through `ROModal``RepairOrders.tsx` (parent fetches customer list)
### Customer Form (CustomerFormModal.tsx)
- Updated to use first/middle/last name inputs instead of single "Name" field
- Composes `name` field internally on save
### Quote Form (CustomerInfoPanel in QuoteGenerator)
- Same 3-name-input layout + customer search at top
- Duplicate detection via debounce + PB filter
- Auto-create via `ensureCustomerRecord()` on save/share
## Customer Matching Logic
Implemented in `findMatchingCustomer()` (customerName.ts):
1. **Phone match (exact):** If phone has 3+ digits, find customer whose phone (digits-only) equals query
2. **Exact name match:** If first + last name entered, find customer whose `name` equals composed name
3. **Partial name:** Try `startsWith` on customer name, then `includes`
**Important:** Matching is client-side on the loaded customers array — it does NOT use PB `~` filter which can 400 on some collections.
## Auto-Create on Save
`ensureCustomerRecord()` is called before saving any record when no `customerId` exists:
1. If customerId already set → return it
2. Get userId from pb.authStore
3. Compose name from first/middle/last
4. Double-check for existing customer (in case one was added since page load)
5. If found → auto-link (set customerId, return id)
6. If not found → `pb.collection('customers').create({ name, phone, email, ... })`
7. Return new id
## CustomerId Propagation
- **Appointment → Check-In:** The check-in flow reads `appointment.customerId` and passes to RO create
- **Quote → RO Conversion:** `customerId` is passed through in QuoteGenerator's RO conversion
- **Customers page detail view:** Shows linked ROs by querying `customerId` or (legacy) `customerName`
## Key Files
| File | Role |
|------|------|
| `src/lib/customerName.ts` | Shared helpers (composeName, parseName, findMatchingCustomer) |
| `src/components/appointments/AppointmentModal.tsx` | First/middle/last inputs + matching + auto-save |
| `src/components/ROForm.tsx` | Same pattern on RO creation/edit |
| `src/components/customers/CustomerFormModal.tsx` | Customer CRUD modal (also first/middle/last) |
| `src/components/customers/types.ts` | CustomerRecord, CustomerWithVehicles, CustomerFormData |
| `src/schemas/customer.ts` | Zod schema (firstName/middleName/lastName fields) |