Files
hermes-config/skills/self-hosting/shop-pro-quote/references/quote-generator-fixes-june-2026.md
T
2026-07-12 10:17:17 -04:00

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:

  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.

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-lgmax-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):

let svcs = q.services || [];
if (typeof svcs === 'string') {
    try { svcs = JSON.parse(svcs); } catch(e) { svcs = []; }
}
if (!Array.isArray(svcs)) svcs = [];