# Quote Generator & Appointment Fixes (June 2026) ## Timestamp Static Methods — PARTIAL FIX (deeper issues found) `pocketbase.js` `Timestamp` class was missing Firebase-compatible static methods. Added: ```js static fromDate(date) { return new Timestamp(date); } static now() { return new Timestamp(new Date()); } ``` `appointments.js:handleEditAppointment()` uses `Timestamp.fromDate(appointmentDateTime)` and `Timestamp.now()` — without these, the save silently fails (TypeError). HOWEVER, adding the static methods was NOT sufficient. Two deeper issues discovered: 1. **`convertToPB` poison**: `Timestamp` objects are NOT `Date` instances, so `convertToPB` falls through to `JSON.stringify()` producing garbage `{"_date":"..."}` in PocketBase. 2. **`new Date(timestampObj)` crashes**: `Date()` constructor doesn't understand Timestamp wrapper → `Invalid Date` → empty form fields → user re-enters data → save appears to work but PocketBase gets garbage. **Final fix**: Removed `Timestamp` wrapper entirely from `handleEditAppointment()`. Use plain `new Date()` for saves — `convertToPB` handles `Date` natively. Also added `Timestamp` recognition to `convertToPB` as belt-and-suspenders. ## Quote Auto-Conversion on Full Decision In `quote-tab-manager.js:saveQuote()`: when EVERY service has `customerDecision` of `approved` or `declined` (none pending), the quote auto-sets `status: 'converted'`. This drops it from the Daily Briefing on the dashboard. ```js const allDecided = selectedServices.length > 0 && selectedServices.every(s => s.customerDecision === 'approved' || s.customerDecision === 'declined'); if (allDecided) { quoteData.status = 'converted'; } ``` ## Daily Briefing Approved/Declined Dollar Breakdown In `dashboard.js:generateDailyBriefing()`: now calculates `approvedDollars` and `declinedDollars` across all active quotes. The data string sent to the LLM includes: ``` Total quote value: $2,847.50 — Approved: $1,200.00, Declined: $1,647.50 ``` System prompt updated to tell the LLM: "Mention the Approved vs Declined dollar breakdown when relevant." ## Create RO from Appointment — Full Fields `appointments.js:promptRepairOrderDetails()` now accepts the full appointment object (not just `appointment.vin`). Modal expanded from 5 fields to 9, all pre-filled: | Field | Source | |---|---| | Customer Name | appointment.customerName | | Customer Phone | appointment.customerPhone | | Vehicle Info | appointment.vehicleInfo | | Service Advisor | appSettings.serviceAdvisor | | RO Number | auto-generated (timestamp) | | Mileage | appointment.mileage | | VIN | appointment.vin | | Estimated Hours | default 2.0 | | Customer Service Type | Waiter/Drop Off | `createRepairOrderFromAppointment()` now uses modal values for customerName, customerPhone, vehicleInfo, serviceAdvisor (with appointment fallback). ## Add Custom Service Modal Unification `repair-orders.html`: Modal now matches Edit Service modal: - Width: `max-w-lg` → `max-w-6xl` - Label: "Recommendation Reason" → "Recommendation" - Label: "Explanation" → "Customer Explanation" - Added: Shop charge checkbox (checked by default) - AI Write button: "AI Write" → "✨ AI Write" `quote-tab-manager.js:saveCustomService()` now captures: - `technicianNotes` (was in modal but not saved) - `applyShopCharge` (new checkbox) - `recommendationReason` (maps to same value as recommendation, for edit-service consistency) `openAddCustomServiceModal()` resets technician notes and shop charge on open. ## Appointment Scan — User-Selected Date (no OCR date pass) The screenshot scanner now asks the user to pick the appointment date BEFORE scanning. See `references/ocr-date-extraction.md` for full details. Key changes: - Date picker added to `#scan-screenshot-modal` between model selector and upload area - Defaults to today when modal opens - `selectedDate` injected into LLM system prompt: `### ALL APPOINTMENTS ARE FOR: 2026-06-14` - Rule 1 changed to: "Use the provided date for ALL appointments. Do not read dates from the text." - Second Tesseract OCR pass (PSM 7 on cropped header) REMOVED entirely — saves ~30s per scan - `headerDate` fallback replaced with `selectedDate` in appointment mapping ## Pattern: Services JSON Parsing Always guard against services being returned as strings (PocketBase stores nested objects as JSON): ```js let svcs = q.services || []; if (typeof svcs === 'string') { try { svcs = JSON.parse(svcs); } catch(e) { svcs = []; } } if (!Array.isArray(svcs)) svcs = []; ```