4.4 KiB
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:
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:
-
convertToPBpoison:Timestampobjects are NOTDateinstances, soconvertToPBfalls through toJSON.stringify()producing garbage{"_date":"..."}in PocketBase. -
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.
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-modalbetween model selector and upload area - Defaults to today when modal opens
selectedDateinjected 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
headerDatefallback replaced withselectedDatein appointment mapping
Pattern: Services JSON Parsing
Always guard against services being returned as strings (PocketBase stores nested objects as JSON):
let svcs = q.services || [];
if (typeof svcs === 'string') {
try { svcs = JSON.parse(svcs); } catch(e) { svcs = []; }
}
if (!Array.isArray(svcs)) svcs = [];