# 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(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) |