Core logic: types.ts (defaults), notification.service.ts (skipEmail + dedup), booking-notifications.ts, loyalty-notifications.ts, reminder-scheduling.ts, booking.service.ts + scheduling.service.ts (staff-selection capture), features/owner/actions/appointments.ts. Schema: prisma/schema.prisma + new migration 20260912165903_add_staff_selected_by_customer. UI: both notification-preferences pages (customer, staff) now use the same default logic as the actual send decision. Tests: notification.service.integration.test.ts, booking-notifications.integration.test.ts, reminder-scheduling.integration.test.ts, loyalty-notifications.integration.test.ts, plus a new createTestAppointment fixture. Docs: docs/notifications.md rewritten to match. Default changes Exactly as specified — full table is in docs/notifications.md. In short: STAFF_NEW_APPOINTMENT, STAFF_APPOINTMENT_RESCHEDULED, STAFF_APPOINTMENT_REMINDER, OWNER_NEW_BOOKING, OWNER_NO_SHOW now default off; everything else that was already toggleable is untouched. No longer email (fixed, not a toggle) BOOKING_COMPLETED, LOYALTY_STAMP_ADDED, LOYALTY_REWARD_REDEEMED — always. BOOKING_BARBER_CHANGED — only when the customer originally booked "any available barber." The customer's 2-hour reminder is removed entirely (24h stays); staff keeps both, just off by default. All via a new skipEmail flag on notify(), checked in one place — no new preference rows for these. Deduplication One new EQUIVALENCE_GROUPS map in notification.service.ts (STAFF_NEW_APPOINTMENT>OWNER_NEW_BOOKING, STAFF_APPOINTMENT_CANCELLED>OWNER_BOOKING_CANCELLED). Inside notify(), before creating deliveries, it checks for an already-created equivalent notification for the same (businessId, appointmentId, recipientUserId); if found and at-least-as-relevant, the new one gets zero deliveries on every channel (row still created for audit trail). Only listed types participate, so OWNER_NO_SHOW and any future actionable owner type are never touched.
This commit is contained in:
18 files changed
+999
-172
No files matched your search
@@ -1124,6 +1124,18 @@ model Appointment {
|
||||
staffId String
|
||||
staff StaffProfile @relation(fields: [staffId], references: [id], onDelete: Restrict)
|
||||
|
||||
/// Whether the customer picked THIS specific staff member when they
|
||||
/// originally booked, as opposed to "any available barber" (the system
|
||||
/// then auto-assigns one — see BookingService.createBooking). Set once
|
||||
/// at creation and never rewritten by a later reassignment — it
|
||||
/// describes the ORIGINAL booking, used to decide whether a later
|
||||
/// staff reassignment is worth emailing the customer about (see
|
||||
/// notifyBookingBarberChanged): someone who never expressed a
|
||||
/// preference is less likely to care who they end up with. Defaults to
|
||||
/// true so every appointment created before this field existed keeps
|
||||
/// notifying on reassignment exactly as it did before.
|
||||
staffSelectedByCustomer Boolean @default(true)
|
||||
|
||||
status AppointmentStatus @default(CONFIRMED)
|
||||
|
||||
/// Stored as UTC instants; the business's `timezone` (see Business model)
|
||||
|
||||
Reference in new issue
Block a user