Просмотр исходного кода

Show an online booking's time on the workshop clock in the confirmation and the desk notice (#589)

* Show an online booking's time on the workshop clock in the confirmation and the desk notice

The confirmation sent to the customer and the New online booking notice formatted the slot in the server's time zone, so a workshop outside UTC read the wrong hour. They now use the workshop's time zone, as the cancellation notice already did.

* Make the booked-time test independent of the machine's time zone
Bernt Christian Egeland 13 часов назад
Родитель
Сommit
d479bdc779

+ 32 - 0
src/__tests__/features/inspection-reminders/booking-actions.test.ts

@@ -103,6 +103,7 @@ function send(over: Record<string, unknown> = {}) {
 beforeEach(() => {
   vi.clearAllMocks()
   settings.bookingMode = 'direct'
+  settings.timeZone = 'Europe/Oslo'
   appSetting.findUnique.mockResolvedValue(null)
   serviceRecord.create.mockResolvedValue({ id: 'rec-1', vehicleId: 'v-1' })
   serviceRequest.create.mockResolvedValue({ id: 'req-1' })
@@ -152,6 +153,37 @@ describe('booking link', () => {
     expect(confirmation.frequency).toBe('once')
   })
 
+  it('tells the desk and the customer the booked time on the workshop clock, not the server clock', async () => {
+    const slot = new Date('2026-12-02T07:00:00Z')
+    const format = (timeZone?: string) =>
+      new Intl.DateTimeFormat(undefined, {
+        dateStyle: 'medium',
+        timeStyle: 'short',
+        timeZone,
+      }).format(slot)
+    // A workshop whose clock reads differently from the machine running the
+    // tests, wherever that is: no two of these agree at this hour, so at
+    // least one is not the host's, and the test never passes by coincidence.
+    const workshopZone = ['Pacific/Auckland', 'America/Los_Angeles', 'Asia/Kolkata'].find(
+      (zone) => format(zone) !== format()
+    ) as string
+    settings.timeZone = workshopZone
+    inspectionReminderSend.findUnique.mockResolvedValue(
+      send({ dueAt: new Date('2026-12-20T00:00:00Z'), expiresAt: new Date('2026-12-30T00:00:00Z') })
+    )
+    vi.setSystemTime(new Date('2026-11-20T00:00:00Z'))
+    try {
+      await confirmBooking({ token: TOKEN, start: slot.toISOString() })
+    } finally {
+      vi.useRealTimers()
+    }
+    const onWorkshopClock = format(workshopZone)
+    expect(format()).not.toBe(onWorkshopClock)
+    const [notice] = notify.mock.calls as unknown as [{ message: string }][]
+    expect(notice[0].message).toContain(onWorkshopClock)
+    expect(scheduledMessage.create.mock.calls[0][0].data.body).toContain(onWorkshopClock)
+  })
+
   it('refuses a slot that was taken in the meantime', async () => {
     inspectionReminderSend.findUnique.mockResolvedValue(send())
     claimableResource.mockResolvedValue(null)

+ 3 - 0
src/features/inspection-reminders/Actions/bookingActions.ts

@@ -249,9 +249,12 @@ export async function confirmBooking(raw: unknown) {
     },
   })
 
+  // The server runs in UTC: the customer's confirmation and the desk's notice
+  // both read the slot on the workshop's clock, as the cancellation already does.
   const when = new Intl.DateTimeFormat(undefined, {
     dateStyle: 'medium',
     timeStyle: 'short',
+    timeZone: settings.timeZone,
   }).format(start)
   await notify({
     organizationId: send.organizationId,