# 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 = [];
```
