Notify chauffeurs when an accepted job's details change #3

Merged
sb merged 5 commits from chauffeur-updates into main 2026-08-08 20:52:41 +10:00
Owner

If office staff edits pickup time, address, vehicle, or similar details on a job a chauffeur has already accepted, the chauffeur now gets a simple email linking to their updated chauffeur PDF, instead of finding out on the day.

Scope, deliberately kept tight:

  • Only fires for BookingCar rows that are chauffeurStatus === "ACCEPTED" both before and after the edit, with the same chauffeur throughout.
  • Reassignments (changing which chauffeur is on the job) are skipped entirely — separate concern, unaffected.
  • Postponements/date changes are out of scope for this pass (only the main booking edit form is covered, not the quick "Postpone" action).
  • Watched fields: vehicle, pickup/dropoff time & address, pickup contact, duration, route, night-transfer flag. Internal-only fields (cost, pay rates, overtime data) don't trigger it.

Implementation:

  • New "job-updated-chauffeur" email template (src/lib/email-templates.ts) — simple body, links to Booking.chauffeurSheetUrl. Editable under Settings → Email Templates like the others.
  • New src/lib/job-change-notification.ts — pure trigger-detection (findAcceptedCarsWithChanges, unit tested) plus the fire-and-forget sender, mirroring the existing accept/decline notification pattern in offer-response.ts.
  • Hooked into PUT /api/bookings/[id], right after the transaction resolves, never awaited into the response.

Verified: build/lint clean, 186/186 tests passing (12 new). Also confirmed live in a running dev server — an ACCEPTED job's pickup time change correctly triggered a send attempt (only failed on the sandbox's missing SMTP server, logged cleanly, save unaffected), and an unrelated internal-field-only edit (carCost) correctly triggered nothing.

If office staff edits pickup time, address, vehicle, or similar details on a job a chauffeur has already accepted, the chauffeur now gets a simple email linking to their updated chauffeur PDF, instead of finding out on the day. Scope, deliberately kept tight: - Only fires for `BookingCar` rows that are `chauffeurStatus === "ACCEPTED"` both before and after the edit, with the same chauffeur throughout. - Reassignments (changing which chauffeur is on the job) are skipped entirely — separate concern, unaffected. - Postponements/date changes are out of scope for this pass (only the main booking edit form is covered, not the quick "Postpone" action). - Watched fields: vehicle, pickup/dropoff time & address, pickup contact, duration, route, night-transfer flag. Internal-only fields (cost, pay rates, overtime data) don't trigger it. Implementation: - New `"job-updated-chauffeur"` email template (`src/lib/email-templates.ts`) — simple body, links to `Booking.chauffeurSheetUrl`. Editable under Settings → Email Templates like the others. - New `src/lib/job-change-notification.ts` — pure trigger-detection (`findAcceptedCarsWithChanges`, unit tested) plus the fire-and-forget sender, mirroring the existing accept/decline notification pattern in `offer-response.ts`. - Hooked into `PUT /api/bookings/[id]`, right after the transaction resolves, never awaited into the response. Verified: build/lint clean, 186/186 tests passing (12 new). Also confirmed live in a running dev server — an ACCEPTED job's pickup time change correctly triggered a send attempt (only failed on the sandbox's missing SMTP server, logged cleanly, save unaffected), and an unrelated internal-field-only edit (`carCost`) correctly triggered nothing.
If office staff edits pickup time, address, vehicle, or similar details
on a job a chauffeur has already accepted, the chauffeur now gets a
simple email linking to their updated chauffeur PDF instead of finding
out on the day.

Skips reassignments, postponements, and internal-only fields (cost/pay
rates) — only in-place edits to an already-accepted job's own details
trigger it. New "Job Details Updated" template, editable under
Settings -> Email Templates like the other chauffeur emails.
Match the trigger to exactly what generateChauffeurPdf renders instead
of a hand-picked field list: dropped dropoffTime/route (never shown;
only a computed finish time appears), and added a booking-level check
covering the ~27 fields the sheet does render (ceremony/reception,
contacts, flight info, special instructions, etc.) so those now notify
every currently-accepted chauffeur on the booking too.
Author
Owner

Follow-up commit (v1.87.1): the trigger now watches every field the chauffeur PDF actually renders, not a hand-picked subset.

  • Dropped dropoffTime/route from the per-car watch list — generateChauffeurPdf never shows them, only a computed pickup+duration finish time.
  • Added a booking-level check covering the ~27 fields the sheet does render (ceremony/reception details, contacts, flight info, special instructions, etc.). Changing any of those now notifies every currently-accepted chauffeur on the booking, even if their own car row is untouched.
  • Internal-only fields (office notes, costs, pay rates, overtime data) still don't trigger it — none of them appear on the chauffeur PDF.
  • Re-verified live: a specialInstructions-only edit correctly notified the accepted chauffeur; a notes-only edit (not on the PDF) correctly triggered nothing.
Follow-up commit (v1.87.1): the trigger now watches every field the chauffeur PDF actually renders, not a hand-picked subset. - Dropped `dropoffTime`/`route` from the per-car watch list — `generateChauffeurPdf` never shows them, only a computed pickup+duration finish time. - Added a booking-level check covering the ~27 fields the sheet does render (ceremony/reception details, contacts, flight info, special instructions, etc.). Changing any of those now notifies every currently-accepted chauffeur on the booking, even if their own car row is untouched. - Internal-only fields (office notes, costs, pay rates, overtime data) still don't trigger it — none of them appear on the chauffeur PDF. - Re-verified live: a `specialInstructions`-only edit correctly notified the accepted chauffeur; a `notes`-only edit (not on the PDF) correctly triggered nothing.
Author
Owner

Bug found. If a booking has 2 accepted cars going to the same chauffeur (or even the same booking with 2 different accepted chauffeurs, which is correct — but if it was the same person on both cars, they'd get 2 near-identical emails for one edit)

Bug found. If a booking has 2 accepted cars going to the same chauffeur (or even the same booking with 2 different accepted chauffeurs, which is correct — but if it was the same person on both cars, they'd get 2 near-identical emails for one edit)
A chauffeur driving two accepted cars on the same booking got two
"job details updated" emails for one edit. notifyChauffeursOfJobChanges
now dedupes recipients by email before sending, so an edit only ever
produces one email per chauffeur regardless of how many of their
accepted cars were affected.
Author
Owner

Bug fix (v1.87.2), found via real testing: editing specialInstructions on a booking with 2 accepted cars sent 2 emails instead of 1, because the booking-level trigger flagged each accepted BookingCar row independently with no dedup — if both rows belong to the same chauffeur, they got the email twice.

notifyChauffeursOfJobChanges now dedupes recipients by chauffeur email before sending (new dedupeRecipientsByEmail, unit tested), so one edit now produces exactly one email per chauffeur regardless of how many of their accepted cars were affected. Two different chauffeurs on the same booking still each get their own email, correctly.

Unrelated: the audit-log entry for that same save also showed payments: — → [object Object]. That's not from anything in this PR — it's pre-existing computePaymentsDiff audit code in src/lib/audit.ts. Flagging separately, not fixed here.

Bug fix (v1.87.2), found via real testing: editing specialInstructions on a booking with 2 accepted cars sent 2 emails instead of 1, because the booking-level trigger flagged each accepted `BookingCar` row independently with no dedup — if both rows belong to the same chauffeur, they got the email twice. `notifyChauffeursOfJobChanges` now dedupes recipients by chauffeur email before sending (new `dedupeRecipientsByEmail`, unit tested), so one edit now produces exactly one email per chauffeur regardless of how many of their accepted cars were affected. Two different chauffeurs on the same booking still each get their own email, correctly. Unrelated: the audit-log entry for that same save also showed `payments: — → [object Object]`. That's not from anything in this PR — it's pre-existing `computePaymentsDiff` audit code in `src/lib/audit.ts`. Flagging separately, not fixed here.
Author
Owner

new instead of auto email. have a option to enable it or disable or when click update booking ask if to send email to chauffeur

new instead of auto email. have a option to enable it or disable or when click update booking ask if to send email to chauffeur
sb changed title from Notify chauffeurs when an accepted job's details change to WIP: Notify chauffeurs when an accepted job's details change 2026-08-08 20:35:04 +10:00
New "Notify chauffeurs when an accepted job changes" control under
Settings -> Email Templates:
- Auto (default): sends immediately, same as before.
- Ask: the save defers sending and returns the pending recipients;
  BookingForm shows a confirm dialog, and confirming calls the new
  POST /api/bookings/[id]/notify-chauffeurs endpoint to send.
- Off: never sends.

Also fixes a client-build break found while wiring this up: schemas.ts
is imported by client components, and the new mode constant was
originally defined in job-change-notification.ts, which imports
nodemailer (server-only) -- pulling the whole email stack into the
client bundle. Moved the constant to its own dependency-free module,
src/lib/notify-mode.ts.
Author
Owner

New commit (v1.88.0), addressing the request for an enable/disable/ask option: added a "Notify chauffeurs when an accepted job changes" setting under Settings → Email Templates, with three modes:

  • Auto (default) — sends immediately, same as before this commit.
  • Ask — the booking save no longer sends automatically; it returns the pending recipient(s) in the response, and the booking edit page shows a confirm dialog ("Notify chauffeur(s) of this change?"). Confirming calls a new POST /api/bookings/[id]/notify-chauffeurs endpoint to actually send.
  • Off — never sends.

New SystemSettings.chauffeurJobChangeNotifyMode column (migration included), select control in EmailSettingsForm.tsx, and mode branching in the PUT /api/bookings/[id] handler.

Also fixed a real build break found while implementing this: schemas.ts is imported by client components (form validation), and pulling the new mode constant from job-change-notification.ts dragged the whole server-only nodemailer stack into the client bundle, breaking npm run build. Moved the constant to a dependency-free src/lib/notify-mode.ts leaf module.

Verified: 199 tests passing, build/lint clean, and all three modes tested live against the running API — Auto sends immediately, Ask correctly defers and the confirm endpoint sends on request, Off suppresses entirely with no pending notification and no send attempt.

New commit (v1.88.0), addressing the request for an enable/disable/ask option: added a "Notify chauffeurs when an accepted job changes" setting under Settings → Email Templates, with three modes: - **Auto** (default) — sends immediately, same as before this commit. - **Ask** — the booking save no longer sends automatically; it returns the pending recipient(s) in the response, and the booking edit page shows a confirm dialog ("Notify chauffeur(s) of this change?"). Confirming calls a new `POST /api/bookings/[id]/notify-chauffeurs` endpoint to actually send. - **Off** — never sends. New `SystemSettings.chauffeurJobChangeNotifyMode` column (migration included), select control in `EmailSettingsForm.tsx`, and mode branching in the `PUT /api/bookings/[id]` handler. Also fixed a real build break found while implementing this: `schemas.ts` is imported by client components (form validation), and pulling the new mode constant from `job-change-notification.ts` dragged the whole server-only `nodemailer` stack into the client bundle, breaking `npm run build`. Moved the constant to a dependency-free `src/lib/notify-mode.ts` leaf module. Verified: 199 tests passing, build/lint clean, and all three modes tested live against the running API — Auto sends immediately, Ask correctly defers and the confirm endpoint sends on request, Off suppresses entirely with no pending notification and no send attempt.
Author
Owner

This job's details changed for a chauffeur who already accepted it. Send an update email to Stewart Test?

cancel or send email

when click cancel the job does not save till you click update booking again.

change cancel to don't send and update booking

This job's details changed for a chauffeur who already accepted it. Send an update email to Stewart Test? cancel or send email when click cancel the job does not save till you click update booking again. change cancel to don't send and update booking
The booking is already saved by the time the notify dialog appears --
it only decides whether to also email the chauffeur -- but the generic
"Cancel" label read as "cancel the save," causing a redundant re-click
of Update Booking. Dialog now opens with "Booking saved..." and its
cancel button reads "Don't send". ConfirmDialog gained an optional
cancelLabel prop for this.
Author
Owner

UX fix (v1.88.1) for the "the job does not save till you click update booking again" report: that wasn't actually a save bug — the booking is fully saved before the notify dialog ever appears (the dialog only decides whether to also email the chauffeur). The generic "Cancel" label just read as "cancel the save," so people re-clicked Update Booking thinking nothing had happened.

Dialog now opens with "Booking saved. ..." and its cancel button reads "Don't send" instead of "Cancel" (new optional cancelLabel prop on ConfirmDialog). No change to the actual save/send logic — confirmed via the earlier API-level testing that the PUT already commits before the dialog is shown, independent of what the office clicks next.

UX fix (v1.88.1) for the "the job does not save till you click update booking again" report: that wasn't actually a save bug — the booking is fully saved before the notify dialog ever appears (the dialog only decides whether to also email the chauffeur). The generic "Cancel" label just read as "cancel the save," so people re-clicked Update Booking thinking nothing had happened. Dialog now opens with "Booking saved. ..." and its cancel button reads "Don't send" instead of "Cancel" (new optional `cancelLabel` prop on `ConfirmDialog`). No change to the actual save/send logic — confirmed via the earlier API-level testing that the PUT already commits before the dialog is shown, independent of what the office clicks next.
Author
Owner

All Test ok

All Test ok
sb changed title from WIP: Notify chauffeurs when an accepted job's details change to Notify chauffeurs when an accepted job's details change 2026-08-08 20:52:34 +10:00
sb merged commit 718420625a into main 2026-08-08 20:52:41 +10:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
sb/ACC-System!3
No description provided.