01
The problem
The "we found one more thing" phone call goes badly: the customer can't see it, gets suspicious and says no. With a photo and a price per line, they decide on their own, calmly.
02
The model
Each checklist item has a state (OK, Watch, Urgent), a note, a photo and a price. Only items that aren't OK go into the quote.
| Table | What it holds |
|---|---|
| inspections | One per vehicle and visit: mechanic, sent time and approval time. |
| inspection_items | Checklist item: state, note, photo, price and whether the customer accepted. |
| photos | Files in cloud storage, with a single-use link for the customer. |
03
Approval
The customer accepts or declines each line. Urgent lines start accepted but can be declined. The approved total is what the garage can do without calling again.
ts
// Só o que não está OK vai para o orçamento.
// O que é urgente nasce aceite: o cliente pode recusar,
// mas tem de o fazer de propósito.
function setLevel(id, level) {
set(id, { level, accepted: level === "urgente" });
}
const quote = ITEMS.filter(id => checks[id].level && checks[id].level !== "ok");04
What I'd do differently in production
Photos would go to cloud storage and the customer would get a single-use link by text. In this demo the photos stay on your phone and vanish on reload.
ts
// O total aprovado é o que a oficina pode fazer sem voltar a ligar.
const total = quote
.filter(id => checks[id].accepted)
.reduce((sum, id) => sum + checks[id].cents, 0);