payload-reserve 2.3.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +218 -4
- package/dist/collections/Holds.d.ts +18 -0
- package/dist/collections/Holds.js +113 -0
- package/dist/collections/Holds.js.map +1 -0
- package/dist/collections/Reservations.js +8 -0
- package/dist/collections/Reservations.js.map +1 -1
- package/dist/collections/Resources.js +35 -1
- package/dist/collections/Resources.js.map +1 -1
- package/dist/collections/Services.js +41 -0
- package/dist/collections/Services.js.map +1 -1
- package/dist/components/CalendarView/LaneTimelineView.js +4 -5
- package/dist/components/CalendarView/LaneTimelineView.js.map +1 -1
- package/dist/components/CalendarView/index.js +55 -71
- package/dist/components/CalendarView/index.js.map +1 -1
- package/dist/components/DashboardWidget/DashboardWidgetServer.js +11 -74
- package/dist/components/DashboardWidget/DashboardWidgetServer.js.map +1 -1
- package/dist/components/DashboardWidget/fetchDashboardStats.d.ts +42 -0
- package/dist/components/DashboardWidget/fetchDashboardStats.js +113 -0
- package/dist/components/DashboardWidget/fetchDashboardStats.js.map +1 -0
- package/dist/defaults.d.ts +2 -0
- package/dist/defaults.js +12 -0
- package/dist/defaults.js.map +1 -1
- package/dist/endpoints/cancelBooking.js +43 -14
- package/dist/endpoints/cancelBooking.js.map +1 -1
- package/dist/endpoints/checkAvailability.js +27 -1
- package/dist/endpoints/checkAvailability.js.map +1 -1
- package/dist/endpoints/createBooking.js +108 -7
- package/dist/endpoints/createBooking.js.map +1 -1
- package/dist/endpoints/customerSearch.js +6 -0
- package/dist/endpoints/customerSearch.js.map +1 -1
- package/dist/endpoints/effectiveTimezone.js +1 -0
- package/dist/endpoints/effectiveTimezone.js.map +1 -1
- package/dist/endpoints/getSlots.js +27 -1
- package/dist/endpoints/getSlots.js.map +1 -1
- package/dist/endpoints/holdSlot.d.ts +12 -0
- package/dist/endpoints/holdSlot.js +126 -0
- package/dist/endpoints/holdSlot.js.map +1 -0
- package/dist/endpoints/releaseSlot.d.ts +8 -0
- package/dist/endpoints/releaseSlot.js +34 -0
- package/dist/endpoints/releaseSlot.js.map +1 -0
- package/dist/endpoints/resourceAvailability.d.ts +4 -0
- package/dist/endpoints/resourceAvailability.js +105 -4
- package/dist/endpoints/resourceAvailability.js.map +1 -1
- package/dist/hooks/holds/validateHoldSlot.d.ts +14 -0
- package/dist/hooks/holds/validateHoldSlot.js +90 -0
- package/dist/hooks/holds/validateHoldSlot.js.map +1 -0
- package/dist/hooks/reservations/acquireBookingLock.d.ts +30 -0
- package/dist/hooks/reservations/acquireBookingLock.js +71 -0
- package/dist/hooks/reservations/acquireBookingLock.js.map +1 -0
- package/dist/hooks/reservations/calculateEndTime.js +13 -1
- package/dist/hooks/reservations/calculateEndTime.js.map +1 -1
- package/dist/hooks/reservations/expandRequiredResources.js +3 -0
- package/dist/hooks/reservations/expandRequiredResources.js.map +1 -1
- package/dist/hooks/reservations/validateActive.d.ts +11 -0
- package/dist/hooks/reservations/validateActive.js +103 -0
- package/dist/hooks/reservations/validateActive.js.map +1 -0
- package/dist/hooks/reservations/validateConflicts.js +22 -1
- package/dist/hooks/reservations/validateConflicts.js.map +1 -1
- package/dist/hooks/reservations/validateGuestBooking.js +3 -0
- package/dist/hooks/reservations/validateGuestBooking.js.map +1 -1
- package/dist/hooks/shared/preventDeleteWhenReferenced.d.ts +42 -0
- package/dist/hooks/shared/preventDeleteWhenReferenced.js +87 -0
- package/dist/hooks/shared/preventDeleteWhenReferenced.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js.map +1 -1
- package/dist/plugin.js +179 -3
- package/dist/plugin.js.map +1 -1
- package/dist/services/AvailabilityService.d.ts +51 -5
- package/dist/services/AvailabilityService.js +315 -38
- package/dist/services/AvailabilityService.js.map +1 -1
- package/dist/services/HoldService.d.ts +53 -0
- package/dist/services/HoldService.js +169 -0
- package/dist/services/HoldService.js.map +1 -0
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js.map +1 -1
- package/dist/translations/ar.json +5 -1
- package/dist/translations/de.json +5 -1
- package/dist/translations/en.json +5 -1
- package/dist/translations/es.json +5 -1
- package/dist/translations/fa.json +5 -1
- package/dist/translations/fr.json +5 -1
- package/dist/translations/hi.json +5 -1
- package/dist/translations/id.json +5 -1
- package/dist/translations/pl.json +5 -1
- package/dist/translations/ru.json +5 -1
- package/dist/translations/tr.json +5 -1
- package/dist/translations/zh.json +5 -1
- package/dist/types.d.ts +32 -0
- package/dist/types.js.map +1 -1
- package/dist/utilities/calendarGrid.d.ts +44 -0
- package/dist/utilities/calendarGrid.js +67 -0
- package/dist/utilities/calendarGrid.js.map +1 -0
- package/dist/utilities/reservationChanges.d.ts +6 -1
- package/dist/utilities/reservationChanges.js +1 -1
- package/dist/utilities/reservationChanges.js.map +1 -1
- package/dist/utilities/reserveDebug.d.ts +26 -0
- package/dist/utilities/reserveDebug.js +35 -0
- package/dist/utilities/reserveDebug.js.map +1 -0
- package/dist/utilities/resolveReservationItems.d.ts +13 -2
- package/dist/utilities/resolveReservationItems.js +87 -2
- package/dist/utilities/resolveReservationItems.js.map +1 -1
- package/dist/utilities/retryOnWriteConflict.d.ts +66 -0
- package/dist/utilities/retryOnWriteConflict.js +135 -0
- package/dist/utilities/retryOnWriteConflict.js.map +1 -0
- package/dist/utilities/tenantTimezone.d.ts +77 -1
- package/dist/utilities/tenantTimezone.js +163 -2
- package/dist/utilities/tenantTimezone.js.map +1 -1
- package/dist/utilities/transactionSupport.d.ts +13 -0
- package/dist/utilities/transactionSupport.js +30 -0
- package/dist/utilities/transactionSupport.js.map +1 -0
- package/package.json +13 -13
|
@@ -61,6 +61,8 @@
|
|
|
61
61
|
"errorInvalidTransition": "Tidak dapat berpindah dari \"{{from}}\" ke \"{{to}}\".",
|
|
62
62
|
"errorCancellationNotice": "Pembatalan memerlukan pemberitahuan minimal {{period}} jam. Hanya tersisa {{hours}} jam sebelum janji temu.",
|
|
63
63
|
"errorConflict": "Slot waktu ini berbenturan dengan reservasi yang sudah ada untuk sumber daya ini.",
|
|
64
|
+
"errorServiceInactive": "Layanan \"{{name}}\" tidak aktif dan tidak dapat dipesan.",
|
|
65
|
+
"errorResourceInactive": "Sumber daya \"{{name}}\" tidak aktif dan tidak dapat dipesan.",
|
|
64
66
|
"calendarLoading": "Memuat reservasi...",
|
|
65
67
|
"calendarToday": "Hari Ini",
|
|
66
68
|
"calendarCreateNew": "Buat Baru",
|
|
@@ -128,8 +130,10 @@
|
|
|
128
130
|
"filterByResource": "Filter berdasarkan sumber daya",
|
|
129
131
|
"fieldOwner": "Pemilik",
|
|
130
132
|
"fieldResourceType": "Tipe sumber daya",
|
|
133
|
+
"fieldResources": "Sumber Daya",
|
|
134
|
+
"fieldResourcesDesc": "Sumber daya yang dapat melakukan layanan ini. Untuk mengubahnya, buka sumber daya dan edit bidang Layanan-nya.",
|
|
131
135
|
"fieldRequiredResources": "Sumber daya yang dibutuhkan",
|
|
132
|
-
"fieldRequiredResourcesDesc": "Kumpulan sumber daya tambahan yang ditempati oleh setiap pemesanan layanan ini (mis. sebuah kursi). Pemesanan otomatis diperluas untuk menyertakan ini dan diblokir jika ada kumpulan yang penuh.",
|
|
136
|
+
"fieldRequiredResourcesDesc": "Kumpulan sumber daya tambahan yang ditempati oleh setiap pemesanan layanan ini (mis. sebuah kursi). Pemesanan otomatis diperluas untuk menyertakan ini dan diblokir jika ada kumpulan yang penuh. Ini berbeda dari sumber daya yang dapat melakukan layanan ini — lihat Sumber Daya di atas.",
|
|
133
137
|
"fieldAllowGuestBooking": "Pemesanan tamu",
|
|
134
138
|
"fieldAllowGuestBookingDesc": "Izinkan pemesanan tanpa akun pelanggan. \"Warisi\" menggunakan default tingkat plugin.",
|
|
135
139
|
"guestBookingInherit": "Warisi default plugin",
|
|
@@ -61,6 +61,8 @@
|
|
|
61
61
|
"errorInvalidTransition": "Nie można przejść ze stanu \"{{from}}\" do \"{{to}}\".",
|
|
62
62
|
"errorCancellationNotice": "Anulowanie wymaga wyprzedzenia co najmniej {{period}} godzin. Do wizyty pozostało tylko {{hours}} godzin.",
|
|
63
63
|
"errorConflict": "Ten przedział czasowy koliduje z istniejącą rezerwacją dla tego zasobu.",
|
|
64
|
+
"errorServiceInactive": "Usługa „{{name}}\" nie jest aktywna i nie można jej zarezerwować.",
|
|
65
|
+
"errorResourceInactive": "Zasób „{{name}}\" nie jest aktywny i nie można go zarezerwować.",
|
|
64
66
|
"calendarLoading": "Ładowanie rezerwacji...",
|
|
65
67
|
"calendarToday": "Dziś",
|
|
66
68
|
"calendarCreateNew": "Utwórz nową",
|
|
@@ -128,8 +130,10 @@
|
|
|
128
130
|
"filterByResource": "Filtruj według zasobu",
|
|
129
131
|
"fieldOwner": "Właściciel",
|
|
130
132
|
"fieldResourceType": "Typ zasobu",
|
|
133
|
+
"fieldResources": "Zasoby",
|
|
134
|
+
"fieldResourcesDesc": "Zasoby, które mogą wykonać tę usługę. Aby to zmienić, otwórz zasób i edytuj jego pole Usługi.",
|
|
131
135
|
"fieldRequiredResources": "Wymagane zasoby",
|
|
132
|
-
"fieldRequiredResourcesDesc": "Dodatkowe pule zasobów zajmowane przez każdą rezerwację tej usługi (np. fotel). Rezerwacje są automatycznie rozszerzane o nie i są blokowane, jeśli którakolwiek pula jest pełna.",
|
|
136
|
+
"fieldRequiredResourcesDesc": "Dodatkowe pule zasobów zajmowane przez każdą rezerwację tej usługi (np. fotel). Rezerwacje są automatycznie rozszerzane o nie i są blokowane, jeśli którakolwiek pula jest pełna. To nie to samo co zasoby, które mogą wykonać tę usługę — zobacz Zasoby powyżej.",
|
|
133
137
|
"fieldAllowGuestBooking": "Rezerwacja gościnna",
|
|
134
138
|
"fieldAllowGuestBookingDesc": "Zezwalaj na rezerwacje bez konta klienta. \"Dziedzicz\" używa domyślnego ustawienia wtyczki.",
|
|
135
139
|
"guestBookingInherit": "Dziedzicz domyślne ustawienie wtyczki",
|
|
@@ -61,6 +61,8 @@
|
|
|
61
61
|
"errorInvalidTransition": "Невозможно перейти из «{{from}}» в «{{to}}».",
|
|
62
62
|
"errorCancellationNotice": "Для отмены требуется уведомление не менее чем за {{period}} часов. До приёма осталось всего {{hours}} часов.",
|
|
63
63
|
"errorConflict": "Этот временной слот конфликтует с существующим бронированием для данного ресурса.",
|
|
64
|
+
"errorServiceInactive": "Услуга «{{name}}» неактивна и недоступна для бронирования.",
|
|
65
|
+
"errorResourceInactive": "Ресурс «{{name}}» неактивен и недоступен для бронирования.",
|
|
64
66
|
"calendarLoading": "Загрузка бронирований...",
|
|
65
67
|
"calendarToday": "Сегодня",
|
|
66
68
|
"calendarCreateNew": "Создать",
|
|
@@ -128,8 +130,10 @@
|
|
|
128
130
|
"filterByResource": "Фильтровать по ресурсу",
|
|
129
131
|
"fieldOwner": "Владелец",
|
|
130
132
|
"fieldResourceType": "Тип ресурса",
|
|
133
|
+
"fieldResources": "Ресурсы",
|
|
134
|
+
"fieldResourcesDesc": "Ресурсы, которые могут оказывать эту услугу. Чтобы изменить, откройте ресурс и отредактируйте его поле «Услуги».",
|
|
131
135
|
"fieldRequiredResources": "Необходимые ресурсы",
|
|
132
|
-
"fieldRequiredResourcesDesc": "Дополнительные пулы ресурсов, которые занимает каждое бронирование этой услуги (например, кресло). Бронирования автоматически расширяются для их включения и блокируются, если какой-либо пул заполнен.",
|
|
136
|
+
"fieldRequiredResourcesDesc": "Дополнительные пулы ресурсов, которые занимает каждое бронирование этой услуги (например, кресло). Бронирования автоматически расширяются для их включения и блокируются, если какой-либо пул заполнен. Это не то же самое, что ресурсы, которые могут оказывать эту услугу — см. «Ресурсы» выше.",
|
|
133
137
|
"fieldAllowGuestBooking": "Гостевое бронирование",
|
|
134
138
|
"fieldAllowGuestBookingDesc": "Разрешить бронирования без учётной записи клиента. «Наследовать» использует значение по умолчанию на уровне плагина.",
|
|
135
139
|
"guestBookingInherit": "Наследовать значение плагина по умолчанию",
|
|
@@ -61,6 +61,8 @@
|
|
|
61
61
|
"errorInvalidTransition": "\"{{from}}\" durumundan \"{{to}}\" durumuna geçiş yapılamaz.",
|
|
62
62
|
"errorCancellationNotice": "İptaller en az {{period}} saat önceden bildirim gerektirir. Randevuya yalnızca {{hours}} saat kaldı.",
|
|
63
63
|
"errorConflict": "Bu zaman aralığı, bu kaynak için mevcut bir rezervasyonla çakışıyor.",
|
|
64
|
+
"errorServiceInactive": "\"{{name}}\" hizmeti aktif değil ve rezerve edilemez.",
|
|
65
|
+
"errorResourceInactive": "\"{{name}}\" kaynağı aktif değil ve rezerve edilemez.",
|
|
64
66
|
"calendarLoading": "Rezervasyonlar yükleniyor...",
|
|
65
67
|
"calendarToday": "Bugün",
|
|
66
68
|
"calendarCreateNew": "Yeni Oluştur",
|
|
@@ -128,8 +130,10 @@
|
|
|
128
130
|
"filterByResource": "Kaynağa göre filtrele",
|
|
129
131
|
"fieldOwner": "Sahip",
|
|
130
132
|
"fieldResourceType": "Kaynak türü",
|
|
133
|
+
"fieldResources": "Kaynaklar",
|
|
134
|
+
"fieldResourcesDesc": "Bu hizmeti verebilecek kaynaklar. Değiştirmek için kaynağı açın ve Hizmetler alanını düzenleyin.",
|
|
131
135
|
"fieldRequiredResources": "Gerekli kaynaklar",
|
|
132
|
-
"fieldRequiredResourcesDesc": "Bu hizmetin her rezervasyonunun kullandığı ek kaynak havuzları (ör. bir koltuk). Rezervasyonlar bunları içerecek şekilde otomatik genişletilir ve herhangi bir havuz dolarsa engellenir.",
|
|
136
|
+
"fieldRequiredResourcesDesc": "Bu hizmetin her rezervasyonunun kullandığı ek kaynak havuzları (ör. bir koltuk). Rezervasyonlar bunları içerecek şekilde otomatik genişletilir ve herhangi bir havuz dolarsa engellenir. Bu, hizmeti hangi kaynakların verebileceğiyle aynı şey değildir — yukarıdaki Kaynaklar'a bakın.",
|
|
133
137
|
"fieldAllowGuestBooking": "Misafir rezervasyonu",
|
|
134
138
|
"fieldAllowGuestBookingDesc": "Müşteri hesabı olmadan rezervasyona izin ver. \"Devral\" eklenti düzeyindeki varsayılanı kullanır.",
|
|
135
139
|
"guestBookingInherit": "Eklenti varsayılanını devral",
|
|
@@ -61,6 +61,8 @@
|
|
|
61
61
|
"errorInvalidTransition": "无法从“{{from}}”转换为“{{to}}”。",
|
|
62
62
|
"errorCancellationNotice": "取消预约至少需要提前 {{period}} 小时通知。距离预约仅剩 {{hours}} 小时。",
|
|
63
63
|
"errorConflict": "此时间段与该资源的现有预约冲突。",
|
|
64
|
+
"errorServiceInactive": "服务\"{{name}}\"未启用,无法预订。",
|
|
65
|
+
"errorResourceInactive": "资源\"{{name}}\"未启用,无法预订。",
|
|
64
66
|
"calendarLoading": "正在加载预约……",
|
|
65
67
|
"calendarToday": "今天",
|
|
66
68
|
"calendarCreateNew": "新建",
|
|
@@ -128,8 +130,10 @@
|
|
|
128
130
|
"filterByResource": "按资源筛选",
|
|
129
131
|
"fieldOwner": "负责人",
|
|
130
132
|
"fieldResourceType": "资源类型",
|
|
133
|
+
"fieldResources": "资源",
|
|
134
|
+
"fieldResourcesDesc": "可以提供此服务的资源。如需更改,请打开该资源并编辑其\"服务\"字段。",
|
|
131
135
|
"fieldRequiredResources": "必需资源",
|
|
132
|
-
"fieldRequiredResourcesDesc": "
|
|
136
|
+
"fieldRequiredResourcesDesc": "此服务的每次预约都会占用的额外资源池(例如一把座椅)。预约会自动扩展以包含这些资源,若任一资源池已满则会被阻止。这与可以提供此服务的资源不是同一回事 — 请参见上方的\"资源\"。",
|
|
133
137
|
"fieldAllowGuestBooking": "访客预约",
|
|
134
138
|
"fieldAllowGuestBookingDesc": "允许无客户账户的预约。“继承”将使用插件级别的默认设置。",
|
|
135
139
|
"guestBookingInherit": "继承插件默认设置",
|
package/dist/types.d.ts
CHANGED
|
@@ -162,10 +162,18 @@ export type ReservationPluginConfig = {
|
|
|
162
162
|
schedules?: CollectionOverride;
|
|
163
163
|
services?: CollectionOverride;
|
|
164
164
|
};
|
|
165
|
+
/** Emit info-level `reserve_debug` traces for slot generation and conflict detection (default false) */
|
|
166
|
+
debug?: boolean;
|
|
165
167
|
/** Default buffer time in minutes between reservations */
|
|
166
168
|
defaultBufferTime?: number;
|
|
167
169
|
/** Disable the plugin entirely */
|
|
168
170
|
disabled?: boolean;
|
|
171
|
+
/**
|
|
172
|
+
* Reject bookings against an inactive service or resource, and omit them from
|
|
173
|
+
* availability. Default `true`. Set `false` to restore the previous behaviour
|
|
174
|
+
* where `active` was advisory and only filtered in the admin UI.
|
|
175
|
+
*/
|
|
176
|
+
enforceActive?: boolean;
|
|
169
177
|
/** @deprecated Use `collectionOverrides.reservations.fields` instead. Extra fields appended to Reservations. */
|
|
170
178
|
extraReservationFields?: Field[];
|
|
171
179
|
/** Resolve external busy intervals (calendar sync etc.) folded into availability + calendar display. */
|
|
@@ -191,9 +199,23 @@ export type ReservationPluginConfig = {
|
|
|
191
199
|
resourceOwnerMode?: ResourceOwnerModeConfig;
|
|
192
200
|
/** Configurable resourceType vocabulary (default: staff/equipment/room) */
|
|
193
201
|
resourceTypes?: string[];
|
|
202
|
+
/**
|
|
203
|
+
* Short-lived slot claims taken while a customer completes checkout.
|
|
204
|
+
*
|
|
205
|
+
* Opt-in: when absent, no holds collection is created and availability
|
|
206
|
+
* behaviour is byte-identical to before. Enabling it makes unexpired holds
|
|
207
|
+
* occupy their resource in every availability check.
|
|
208
|
+
*/
|
|
209
|
+
slotHolds?: {
|
|
210
|
+
enabled?: boolean;
|
|
211
|
+
/** Minutes a hold survives before it stops occupying its slot. Default 10. */
|
|
212
|
+
ttlMinutes?: number;
|
|
213
|
+
};
|
|
194
214
|
/** Override collection slugs */
|
|
195
215
|
slugs?: {
|
|
196
216
|
customers?: string;
|
|
217
|
+
/** Slug for the slot-holds collection (only created when `slotHolds.enabled`). */
|
|
218
|
+
holds?: string;
|
|
197
219
|
media?: string;
|
|
198
220
|
reservations?: string;
|
|
199
221
|
resources?: string;
|
|
@@ -227,12 +249,17 @@ export type ResolvedReservationPluginConfig = {
|
|
|
227
249
|
schedules?: CollectionOverride;
|
|
228
250
|
services?: CollectionOverride;
|
|
229
251
|
};
|
|
252
|
+
debug: boolean;
|
|
230
253
|
defaultBufferTime: number;
|
|
231
254
|
disabled: boolean;
|
|
255
|
+
enforceActive: boolean;
|
|
232
256
|
extraReservationFields: Field[];
|
|
233
257
|
getExternalBusy: GetExternalBusy | undefined;
|
|
234
258
|
/** Whether the media collection (`slugs.media`) exists — set by the plugin; gates the image upload fields. */
|
|
235
259
|
hasMediaCollection: boolean;
|
|
260
|
+
/** Set by the plugin once Resources is built: does it still expose a
|
|
261
|
+
* top-level `services` relationship for the Services join to target? */
|
|
262
|
+
hasResourceServicesField: boolean;
|
|
236
263
|
hooks: ReservationPluginHooks;
|
|
237
264
|
leaveTypes: string[];
|
|
238
265
|
localized: boolean;
|
|
@@ -243,8 +270,13 @@ export type ResolvedReservationPluginConfig = {
|
|
|
243
270
|
};
|
|
244
271
|
resourceOwnerMode: ResolvedResourceOwnerModeConfig | undefined;
|
|
245
272
|
resourceTypes: string[];
|
|
273
|
+
slotHolds: {
|
|
274
|
+
enabled: boolean;
|
|
275
|
+
ttlMinutes: number;
|
|
276
|
+
};
|
|
246
277
|
slugs: {
|
|
247
278
|
customers: string;
|
|
279
|
+
holds: string;
|
|
248
280
|
media: string;
|
|
249
281
|
reservations: string;
|
|
250
282
|
resources: string;
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { CollectionConfig, Field, PayloadRequest } from 'payload'\n\n// --- Duration & Capacity models ---\n\nexport type DurationType = 'fixed' | 'flexible' | 'full-day'\n\nexport type CapacityMode = 'per-guest' | 'per-reservation'\n\n// --- Configurable status machine ---\n\nexport type StatusMachineConfig = {\n blockingStatuses: string[]\n /** Status treated as \"cancelled\" — fires beforeBookingCancel/afterBookingCancel, the cancellation notice period, and the cancellationReason field. */\n cancelStatus: string\n /** Status treated as \"confirmed\" — fires beforeBookingConfirm/afterBookingConfirm. */\n confirmStatus: string\n defaultStatus: string\n statuses: string[]\n terminalStatuses: string[]\n transitions: Record<string, string[]>\n}\n\nexport const DEFAULT_STATUS_MACHINE: StatusMachineConfig = {\n blockingStatuses: ['pending', 'confirmed'],\n cancelStatus: 'cancelled',\n confirmStatus: 'confirmed',\n defaultStatus: 'pending',\n statuses: ['pending', 'confirmed', 'completed', 'cancelled', 'no-show'],\n terminalStatuses: ['completed', 'cancelled', 'no-show'],\n transitions: {\n cancelled: [],\n completed: [],\n confirmed: ['completed', 'cancelled', 'no-show'],\n 'no-show': [],\n pending: ['confirmed', 'cancelled'],\n },\n}\n\n// --- Reservation item (for multi-resource bookings, Phase 3) ---\n\nexport type ReservationItemConfig = {\n endTime?: string\n guestCount?: number\n resource: string\n service?: string\n startTime?: string\n}\n\n// --- Plugin hooks for external integrations ---\n\nexport type ReservationPluginHooks = {\n afterBookingCancel?: Array<\n (args: { doc: Record<string, unknown>; req: PayloadRequest }) => Promise<void> | void\n >\n afterBookingConfirm?: Array<\n (args: { doc: Record<string, unknown>; req: PayloadRequest }) => Promise<void> | void\n >\n afterBookingCreate?: Array<\n (args: { doc: Record<string, unknown>; req: PayloadRequest }) => Promise<void> | void\n >\n afterStatusChange?: Array<\n (args: {\n doc: Record<string, unknown>\n newStatus: string\n previousStatus: string\n req: PayloadRequest\n }) => Promise<void> | void\n >\n beforeBookingCancel?: Array<\n (args: {\n doc: Record<string, unknown>\n reason?: string\n req: PayloadRequest\n }) => Promise<void> | void\n >\n beforeBookingConfirm?: Array<\n (args: {\n doc: Record<string, unknown>\n newStatus: string\n req: PayloadRequest\n }) => Promise<void> | void\n >\n beforeBookingCreate?: Array<\n (args: {\n data: Record<string, unknown>\n req: PayloadRequest\n }) => Promise<Record<string, unknown>> | Record<string, unknown>\n >\n}\n\n// --- Resource owner mode ---\n\nexport type ResourceOwnerModeConfig = {\n /** Roles that can see all records (default: check req.user.collection === adminCollection) */\n adminRoles?: string[]\n /** Whether Services also get an owner field (default: false — Services are platform-managed) */\n ownedServices?: boolean\n /**\n * Collection the owner field relates to (where owners/staff live). Defaults to\n * `staffProvisioning.userCollection` when set, otherwise `slugs.customers`. Set\n * this when owners live in a different collection than your customers (e.g.\n * separate `users` and `customers` collections).\n */\n ownerCollection?: string\n /** Field name for the owner relationship on Resources (default: 'owner') */\n ownerField?: string\n /** User field holding the role for admin detection (default: staffProvisioning.roleField or 'role') */\n roleField?: string\n}\n\nexport type ResolvedResourceOwnerModeConfig = {\n adminRoles: string[]\n ownedServices: boolean\n ownerCollection?: string\n ownerField: string\n roleField: string\n}\n\n// --- Staff provisioning ---\n\nexport type StaffProvisioningConfig = {\n /** Stamp tenant / custom fields onto the provisioned Resource before create. */\n beforeCreate?: (args: {\n data: Record<string, unknown>\n req: PayloadRequest\n user: Record<string, unknown>\n }) => Promise<Record<string, unknown>> | Record<string, unknown>\n /** User field copied into Resource `name` (default 'name', falls back to email). */\n nameFrom?: string\n /** resourceType to stamp (default 'staff'). Must be a valid resourceType. */\n resourceType?: string\n /** Field on the user holding the role (default 'role'). */\n roleField?: string\n /** Role value(s) marking a user as staff. Required, non-empty. */\n staffRoles: string[]\n /** Auth collection holding staff users. Defaults to top-level `userCollection`. */\n userCollection?: string\n}\n\nexport type ResolvedStaffProvisioningConfig = {\n beforeCreate?: StaffProvisioningConfig['beforeCreate']\n nameFrom: string\n resourceType: string\n roleField: string\n staffRoles: string[]\n userCollection: string\n}\n\n// --- Plugin configuration ---\n\n/**\n * Per-collection override applied to a generated collection. `fields` is a\n * function receiving the plugin's default fields so you can append/reorder/\n * replace them; supplied `hooks` are merged with (not replacing) the plugin's;\n * `access` composes per operation; `slug` is ignored (use the `slugs` option).\n */\nexport type CollectionOverride = {\n fields?: (args: { defaultFields: Field[] }) => Field[]\n} & Omit<Partial<CollectionConfig>, 'fields' | 'slug'>\n\n/** One external busy interval returned by `getExternalBusy` (ISO date strings). */\nexport type ExternalBusyInterval = { end: string; label?: string; start: string }\n\n/**\n * App-supplied resolver mapping an external source (e.g. Google/Outlook sync\n * tables) to busy intervals for one resource over [start, end). Wired into\n * checkAvailability (bookings overlapping an interval are unavailable — the\n * interval blocks the WHOLE resource, all units) and into the\n * resource-availability endpoint (returned as `external` for distinct\n * rendering). The plugin calls it inside try/catch and treats an error as []\n * (fail-open): a calendar/sync failure must never block a real booking.\n * Called once per candidate window during slot computation (N calls per\n * request) — keep it cheap: read a local sync table or a per-request cache,\n * never a remote API directly.\n */\nexport type GetExternalBusy = (args: {\n end: Date\n req: PayloadRequest\n resourceId: number | string\n start: Date\n}) => Promise<ExternalBusyInterval[]>\n\nexport type ReservationPluginConfig = {\n /** Override access control per collection */\n access?: {\n customers?: CollectionConfig['access']\n reservations?: CollectionConfig['access']\n resources?: CollectionConfig['access']\n schedules?: CollectionConfig['access']\n services?: CollectionConfig['access']\n }\n /** Admin group name for all reservation collections */\n adminGroup?: string\n /** Allow bookings without a customer account by default (per-service override available) */\n allowGuestBooking?: boolean\n /** Hours of notice required before cancellation */\n cancellationNoticePeriod?: number\n /** Per-collection overrides applied to the generated collections (issue #4) */\n collectionOverrides?: {\n customers?: CollectionOverride\n reservations?: CollectionOverride\n resources?: CollectionOverride\n schedules?: CollectionOverride\n services?: CollectionOverride\n }\n /** Default buffer time in minutes between reservations */\n defaultBufferTime?: number\n /** Disable the plugin entirely */\n disabled?: boolean\n /** @deprecated Use `collectionOverrides.reservations.fields` instead. Extra fields appended to Reservations. */\n extraReservationFields?: Field[]\n /** Resolve external busy intervals (calendar sync etc.) folded into availability + calendar display. */\n getExternalBusy?: GetExternalBusy\n /** Plugin hooks for external integrations */\n hooks?: ReservationPluginHooks\n /** Configurable leave/exception type vocabulary (default: vacation/sick/personal/closure/other) */\n leaveTypes?: string[]\n /** Tenant scoping for the custom admin views (calendar, availability, dashboard). Applied only when the scoped collection has the tenant field AND the tenant cookie is set. */\n multiTenant?: {\n /** Cookie written by the tenant-selector (default 'payload-tenant'). */\n cookieName?: string\n /** Tenant field name on scoped collections (default 'tenant'). */\n tenantField?: string\n /**\n * Field on the tenant document holding its IANA timezone (default 'timezone').\n * When set and the selected tenant has a valid value, the admin views resolve\n * day-boundaries in that tenant's zone instead of the global `timezone`.\n */\n timezoneField?: string\n }\n /** Enable resource-owner multi-tenancy (opt-in) */\n resourceOwnerMode?: ResourceOwnerModeConfig\n /** Configurable resourceType vocabulary (default: staff/equipment/room) */\n resourceTypes?: string[]\n /** Override collection slugs */\n slugs?: {\n customers?: string\n media?: string\n reservations?: string\n resources?: string\n schedules?: string\n services?: string\n }\n /** Auto-provision a Resource from staff-role users (opt-in; requires resourceOwnerMode) */\n staffProvisioning?: StaffProvisioningConfig\n /** Configurable status machine (defaults to current behavior) */\n statusMachine?: Partial<StatusMachineConfig>\n /** IANA business timezone governing schedules and day boundaries (default 'UTC') */\n timezone?: string\n /** Which existing auth collection to extend with customer fields */\n userCollection?: string\n}\n\nexport type ResolvedReservationPluginConfig = {\n access: {\n customers?: CollectionConfig['access']\n reservations?: CollectionConfig['access']\n resources?: CollectionConfig['access']\n schedules?: CollectionConfig['access']\n services?: CollectionConfig['access']\n }\n adminGroup: string\n allowGuestBooking: boolean\n cancellationNoticePeriod: number\n collectionOverrides: {\n customers?: CollectionOverride\n reservations?: CollectionOverride\n resources?: CollectionOverride\n schedules?: CollectionOverride\n services?: CollectionOverride\n }\n defaultBufferTime: number\n disabled: boolean\n extraReservationFields: Field[]\n getExternalBusy: GetExternalBusy | undefined\n /** Whether the media collection (`slugs.media`) exists — set by the plugin; gates the image upload fields. */\n hasMediaCollection: boolean\n hooks: ReservationPluginHooks\n leaveTypes: string[]\n localized: boolean\n multiTenant: {\n cookieName: string\n tenantField: string\n timezoneField: string\n }\n resourceOwnerMode: ResolvedResourceOwnerModeConfig | undefined\n resourceTypes: string[]\n slugs: {\n customers: string\n media: string\n reservations: string\n resources: string\n schedules: string\n services: string\n }\n staffProvisioning: ResolvedStaffProvisioningConfig | undefined\n statusMachine: StatusMachineConfig\n timezone: string\n userCollection: string | undefined\n}\n\nexport type ReservationStatus = 'cancelled' | 'completed' | 'confirmed' | 'no-show' | 'pending'\n\nexport type DayOfWeek = 'fri' | 'mon' | 'sat' | 'sun' | 'thu' | 'tue' | 'wed'\n\nexport type ScheduleType = 'manual' | 'recurring'\n\n/** @deprecated Use DEFAULT_STATUS_MACHINE.transitions instead */\nexport const VALID_STATUS_TRANSITIONS: Record<ReservationStatus, ReservationStatus[]> =\n DEFAULT_STATUS_MACHINE.transitions as Record<ReservationStatus, ReservationStatus[]>\n"],"names":["DEFAULT_STATUS_MACHINE","blockingStatuses","cancelStatus","confirmStatus","defaultStatus","statuses","terminalStatuses","transitions","cancelled","completed","confirmed","pending","VALID_STATUS_TRANSITIONS"],"mappings":"AAsBA,OAAO,MAAMA,yBAA8C;IACzDC,kBAAkB;QAAC;QAAW;KAAY;IAC1CC,cAAc;IACdC,eAAe;IACfC,eAAe;IACfC,UAAU;QAAC;QAAW;QAAa;QAAa;QAAa;KAAU;IACvEC,kBAAkB;QAAC;QAAa;QAAa;KAAU;IACvDC,aAAa;QACXC,WAAW,EAAE;QACbC,WAAW,EAAE;QACbC,WAAW;YAAC;YAAa;YAAa;SAAU;QAChD,WAAW,EAAE;QACbC,SAAS;YAAC;YAAa;SAAY;IACrC;AACF,EAAC;AA+QD,+DAA+D,GAC/D,OAAO,MAAMC,2BACXZ,uBAAuBO,WAAW,CAAkD"}
|
|
1
|
+
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { CollectionConfig, Field, PayloadRequest } from 'payload'\n\n// --- Duration & Capacity models ---\n\nexport type DurationType = 'fixed' | 'flexible' | 'full-day'\n\nexport type CapacityMode = 'per-guest' | 'per-reservation'\n\n// --- Configurable status machine ---\n\nexport type StatusMachineConfig = {\n blockingStatuses: string[]\n /** Status treated as \"cancelled\" — fires beforeBookingCancel/afterBookingCancel, the cancellation notice period, and the cancellationReason field. */\n cancelStatus: string\n /** Status treated as \"confirmed\" — fires beforeBookingConfirm/afterBookingConfirm. */\n confirmStatus: string\n defaultStatus: string\n statuses: string[]\n terminalStatuses: string[]\n transitions: Record<string, string[]>\n}\n\nexport const DEFAULT_STATUS_MACHINE: StatusMachineConfig = {\n blockingStatuses: ['pending', 'confirmed'],\n cancelStatus: 'cancelled',\n confirmStatus: 'confirmed',\n defaultStatus: 'pending',\n statuses: ['pending', 'confirmed', 'completed', 'cancelled', 'no-show'],\n terminalStatuses: ['completed', 'cancelled', 'no-show'],\n transitions: {\n cancelled: [],\n completed: [],\n confirmed: ['completed', 'cancelled', 'no-show'],\n 'no-show': [],\n pending: ['confirmed', 'cancelled'],\n },\n}\n\n// --- Reservation item (for multi-resource bookings, Phase 3) ---\n\nexport type ReservationItemConfig = {\n endTime?: string\n guestCount?: number\n resource: string\n service?: string\n startTime?: string\n}\n\n// --- Plugin hooks for external integrations ---\n\nexport type ReservationPluginHooks = {\n afterBookingCancel?: Array<\n (args: { doc: Record<string, unknown>; req: PayloadRequest }) => Promise<void> | void\n >\n afterBookingConfirm?: Array<\n (args: { doc: Record<string, unknown>; req: PayloadRequest }) => Promise<void> | void\n >\n afterBookingCreate?: Array<\n (args: { doc: Record<string, unknown>; req: PayloadRequest }) => Promise<void> | void\n >\n afterStatusChange?: Array<\n (args: {\n doc: Record<string, unknown>\n newStatus: string\n previousStatus: string\n req: PayloadRequest\n }) => Promise<void> | void\n >\n beforeBookingCancel?: Array<\n (args: {\n doc: Record<string, unknown>\n reason?: string\n req: PayloadRequest\n }) => Promise<void> | void\n >\n beforeBookingConfirm?: Array<\n (args: {\n doc: Record<string, unknown>\n newStatus: string\n req: PayloadRequest\n }) => Promise<void> | void\n >\n beforeBookingCreate?: Array<\n (args: {\n data: Record<string, unknown>\n req: PayloadRequest\n }) => Promise<Record<string, unknown>> | Record<string, unknown>\n >\n}\n\n// --- Resource owner mode ---\n\nexport type ResourceOwnerModeConfig = {\n /** Roles that can see all records (default: check req.user.collection === adminCollection) */\n adminRoles?: string[]\n /** Whether Services also get an owner field (default: false — Services are platform-managed) */\n ownedServices?: boolean\n /**\n * Collection the owner field relates to (where owners/staff live). Defaults to\n * `staffProvisioning.userCollection` when set, otherwise `slugs.customers`. Set\n * this when owners live in a different collection than your customers (e.g.\n * separate `users` and `customers` collections).\n */\n ownerCollection?: string\n /** Field name for the owner relationship on Resources (default: 'owner') */\n ownerField?: string\n /** User field holding the role for admin detection (default: staffProvisioning.roleField or 'role') */\n roleField?: string\n}\n\nexport type ResolvedResourceOwnerModeConfig = {\n adminRoles: string[]\n ownedServices: boolean\n ownerCollection?: string\n ownerField: string\n roleField: string\n}\n\n// --- Staff provisioning ---\n\nexport type StaffProvisioningConfig = {\n /** Stamp tenant / custom fields onto the provisioned Resource before create. */\n beforeCreate?: (args: {\n data: Record<string, unknown>\n req: PayloadRequest\n user: Record<string, unknown>\n }) => Promise<Record<string, unknown>> | Record<string, unknown>\n /** User field copied into Resource `name` (default 'name', falls back to email). */\n nameFrom?: string\n /** resourceType to stamp (default 'staff'). Must be a valid resourceType. */\n resourceType?: string\n /** Field on the user holding the role (default 'role'). */\n roleField?: string\n /** Role value(s) marking a user as staff. Required, non-empty. */\n staffRoles: string[]\n /** Auth collection holding staff users. Defaults to top-level `userCollection`. */\n userCollection?: string\n}\n\nexport type ResolvedStaffProvisioningConfig = {\n beforeCreate?: StaffProvisioningConfig['beforeCreate']\n nameFrom: string\n resourceType: string\n roleField: string\n staffRoles: string[]\n userCollection: string\n}\n\n// --- Plugin configuration ---\n\n/**\n * Per-collection override applied to a generated collection. `fields` is a\n * function receiving the plugin's default fields so you can append/reorder/\n * replace them; supplied `hooks` are merged with (not replacing) the plugin's;\n * `access` composes per operation; `slug` is ignored (use the `slugs` option).\n */\nexport type CollectionOverride = {\n fields?: (args: { defaultFields: Field[] }) => Field[]\n} & Omit<Partial<CollectionConfig>, 'fields' | 'slug'>\n\n/** One external busy interval returned by `getExternalBusy` (ISO date strings). */\nexport type ExternalBusyInterval = { end: string; label?: string; start: string }\n\n/**\n * App-supplied resolver mapping an external source (e.g. Google/Outlook sync\n * tables) to busy intervals for one resource over [start, end). Wired into\n * checkAvailability (bookings overlapping an interval are unavailable — the\n * interval blocks the WHOLE resource, all units) and into the\n * resource-availability endpoint (returned as `external` for distinct\n * rendering). The plugin calls it inside try/catch and treats an error as []\n * (fail-open): a calendar/sync failure must never block a real booking.\n * Called once per candidate window during slot computation (N calls per\n * request) — keep it cheap: read a local sync table or a per-request cache,\n * never a remote API directly.\n */\nexport type GetExternalBusy = (args: {\n end: Date\n req: PayloadRequest\n resourceId: number | string\n start: Date\n}) => Promise<ExternalBusyInterval[]>\n\nexport type ReservationPluginConfig = {\n /** Override access control per collection */\n access?: {\n customers?: CollectionConfig['access']\n reservations?: CollectionConfig['access']\n resources?: CollectionConfig['access']\n schedules?: CollectionConfig['access']\n services?: CollectionConfig['access']\n }\n /** Admin group name for all reservation collections */\n adminGroup?: string\n /** Allow bookings without a customer account by default (per-service override available) */\n allowGuestBooking?: boolean\n /** Hours of notice required before cancellation */\n cancellationNoticePeriod?: number\n /** Per-collection overrides applied to the generated collections (issue #4) */\n collectionOverrides?: {\n customers?: CollectionOverride\n reservations?: CollectionOverride\n resources?: CollectionOverride\n schedules?: CollectionOverride\n services?: CollectionOverride\n }\n /** Emit info-level `reserve_debug` traces for slot generation and conflict detection (default false) */\n debug?: boolean\n /** Default buffer time in minutes between reservations */\n defaultBufferTime?: number\n /** Disable the plugin entirely */\n disabled?: boolean\n /**\n * Reject bookings against an inactive service or resource, and omit them from\n * availability. Default `true`. Set `false` to restore the previous behaviour\n * where `active` was advisory and only filtered in the admin UI.\n */\n enforceActive?: boolean\n /** @deprecated Use `collectionOverrides.reservations.fields` instead. Extra fields appended to Reservations. */\n extraReservationFields?: Field[]\n /** Resolve external busy intervals (calendar sync etc.) folded into availability + calendar display. */\n getExternalBusy?: GetExternalBusy\n /** Plugin hooks for external integrations */\n hooks?: ReservationPluginHooks\n /** Configurable leave/exception type vocabulary (default: vacation/sick/personal/closure/other) */\n leaveTypes?: string[]\n /** Tenant scoping for the custom admin views (calendar, availability, dashboard). Applied only when the scoped collection has the tenant field AND the tenant cookie is set. */\n multiTenant?: {\n /** Cookie written by the tenant-selector (default 'payload-tenant'). */\n cookieName?: string\n /** Tenant field name on scoped collections (default 'tenant'). */\n tenantField?: string\n /**\n * Field on the tenant document holding its IANA timezone (default 'timezone').\n * When set and the selected tenant has a valid value, the admin views resolve\n * day-boundaries in that tenant's zone instead of the global `timezone`.\n */\n timezoneField?: string\n }\n /** Enable resource-owner multi-tenancy (opt-in) */\n resourceOwnerMode?: ResourceOwnerModeConfig\n /** Configurable resourceType vocabulary (default: staff/equipment/room) */\n resourceTypes?: string[]\n /**\n * Short-lived slot claims taken while a customer completes checkout.\n *\n * Opt-in: when absent, no holds collection is created and availability\n * behaviour is byte-identical to before. Enabling it makes unexpired holds\n * occupy their resource in every availability check.\n */\n slotHolds?: {\n enabled?: boolean\n /** Minutes a hold survives before it stops occupying its slot. Default 10. */\n ttlMinutes?: number\n }\n /** Override collection slugs */\n slugs?: {\n customers?: string\n /** Slug for the slot-holds collection (only created when `slotHolds.enabled`). */\n holds?: string\n media?: string\n reservations?: string\n resources?: string\n schedules?: string\n services?: string\n }\n /** Auto-provision a Resource from staff-role users (opt-in; requires resourceOwnerMode) */\n staffProvisioning?: StaffProvisioningConfig\n /** Configurable status machine (defaults to current behavior) */\n statusMachine?: Partial<StatusMachineConfig>\n /** IANA business timezone governing schedules and day boundaries (default 'UTC') */\n timezone?: string\n /** Which existing auth collection to extend with customer fields */\n userCollection?: string\n}\n\nexport type ResolvedReservationPluginConfig = {\n access: {\n customers?: CollectionConfig['access']\n reservations?: CollectionConfig['access']\n resources?: CollectionConfig['access']\n schedules?: CollectionConfig['access']\n services?: CollectionConfig['access']\n }\n adminGroup: string\n allowGuestBooking: boolean\n cancellationNoticePeriod: number\n collectionOverrides: {\n customers?: CollectionOverride\n reservations?: CollectionOverride\n resources?: CollectionOverride\n schedules?: CollectionOverride\n services?: CollectionOverride\n }\n debug: boolean\n defaultBufferTime: number\n disabled: boolean\n enforceActive: boolean\n extraReservationFields: Field[]\n getExternalBusy: GetExternalBusy | undefined\n /** Whether the media collection (`slugs.media`) exists — set by the plugin; gates the image upload fields. */\n hasMediaCollection: boolean\n /** Set by the plugin once Resources is built: does it still expose a\n * top-level `services` relationship for the Services join to target? */\n hasResourceServicesField: boolean\n hooks: ReservationPluginHooks\n leaveTypes: string[]\n localized: boolean\n multiTenant: {\n cookieName: string\n tenantField: string\n timezoneField: string\n }\n resourceOwnerMode: ResolvedResourceOwnerModeConfig | undefined\n resourceTypes: string[]\n slotHolds: {\n enabled: boolean\n ttlMinutes: number\n }\n slugs: {\n customers: string\n holds: string\n media: string\n reservations: string\n resources: string\n schedules: string\n services: string\n }\n staffProvisioning: ResolvedStaffProvisioningConfig | undefined\n statusMachine: StatusMachineConfig\n timezone: string\n userCollection: string | undefined\n}\n\nexport type ReservationStatus = 'cancelled' | 'completed' | 'confirmed' | 'no-show' | 'pending'\n\nexport type DayOfWeek = 'fri' | 'mon' | 'sat' | 'sun' | 'thu' | 'tue' | 'wed'\n\nexport type ScheduleType = 'manual' | 'recurring'\n\n/** @deprecated Use DEFAULT_STATUS_MACHINE.transitions instead */\nexport const VALID_STATUS_TRANSITIONS: Record<ReservationStatus, ReservationStatus[]> =\n DEFAULT_STATUS_MACHINE.transitions as Record<ReservationStatus, ReservationStatus[]>\n"],"names":["DEFAULT_STATUS_MACHINE","blockingStatuses","cancelStatus","confirmStatus","defaultStatus","statuses","terminalStatuses","transitions","cancelled","completed","confirmed","pending","VALID_STATUS_TRANSITIONS"],"mappings":"AAsBA,OAAO,MAAMA,yBAA8C;IACzDC,kBAAkB;QAAC;QAAW;KAAY;IAC1CC,cAAc;IACdC,eAAe;IACfC,eAAe;IACfC,UAAU;QAAC;QAAW;QAAa;QAAa;QAAa;KAAU;IACvEC,kBAAkB;QAAC;QAAa;QAAa;KAAU;IACvDC,aAAa;QACXC,WAAW,EAAE;QACbC,WAAW,EAAE;QACbC,WAAW;YAAC;YAAa;YAAa;SAAU;QAChD,WAAW,EAAE;QACbC,SAAS;YAAC;YAAa;SAAY;IACrC;AACF,EAAC;AA+SD,+DAA+D,GAC/D,OAAO,MAAMC,2BACXZ,uBAAuBO,WAAW,CAAkD"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Weekday index for a calendar date, 0 = Sunday. TZ-independent pure calendar
|
|
3
|
+
* math — the UTC instant is a carrier for the date only, never a real time.
|
|
4
|
+
*/
|
|
5
|
+
export declare function weekdayIndexOfDayKey(dayKey: string): number;
|
|
6
|
+
/** The Sunday on or before `dayKey`. */
|
|
7
|
+
export declare function startOfWeekDayKey(dayKey: string): string;
|
|
8
|
+
/** The Sunday on or before the 1st of `dayKey`'s month — the month grid origin. */
|
|
9
|
+
export declare function monthGridStartDayKey(dayKey: string): string;
|
|
10
|
+
/** `count` consecutive day keys starting at `startDayKey`. */
|
|
11
|
+
export declare function dayKeySequence(startDayKey: string, count: number): string[];
|
|
12
|
+
/**
|
|
13
|
+
* The instant at `hour`:00 on `dayKey` in the BUSINESS timezone.
|
|
14
|
+
*
|
|
15
|
+
* This is the whole point of the module: calendar rows are labelled with
|
|
16
|
+
* getHourInTimezone(date, businessTZ), so the instant a row maps to must be
|
|
17
|
+
* built in that same zone. Using Date#setHours here builds it in the VIEWER's
|
|
18
|
+
* zone, which is how clicking "10:00" came to book a different hour.
|
|
19
|
+
*/
|
|
20
|
+
export declare function instantAtHour(dayKey: string, hour: number, timeZone: string): Date;
|
|
21
|
+
/**
|
|
22
|
+
* Like `instantAtHour`, but tolerates the exclusive end hour a grid window can
|
|
23
|
+
* carry: hour 24 is not a wall-clock time (`combineDayKeyAndTime` rejects
|
|
24
|
+
* "24:00"), it means midnight starting the FOLLOWING day, so carry it onto the
|
|
25
|
+
* next day key.
|
|
26
|
+
*
|
|
27
|
+
* The carry is deliberately a day-key increment rather than `+24h` on the day's
|
|
28
|
+
* start: on a DST-transition day the grid spans 23 or 25 real hours, and only
|
|
29
|
+
* re-resolving midnight in the business zone lands on the right instant.
|
|
30
|
+
*/
|
|
31
|
+
export declare function gridInstant(dayKey: string, hour: number, timeZone: string): Date;
|
|
32
|
+
/**
|
|
33
|
+
* A Date safe to hand to Intl for rendering `dayKey`'s calendar date.
|
|
34
|
+
*
|
|
35
|
+
* Noon is deliberate: far enough from both midnights that formatting it back
|
|
36
|
+
* with the SAME `timeZone` it was built with can never render the adjacent
|
|
37
|
+
* date, DST included — which is how every caller in this codebase uses it
|
|
38
|
+
* (construct and format in the one business zone). The margin is only ±12h
|
|
39
|
+
* from that zone's own offset, not unconditional: formatting in a *different*
|
|
40
|
+
* zone whose offset differs from `timeZone`'s by more than ~12h (e.g. noon
|
|
41
|
+
* built in Pacific/Auckland, UTC+12/+13, formatted in a UTC-11 zone) can still
|
|
42
|
+
* land on the adjacent day.
|
|
43
|
+
*/
|
|
44
|
+
export declare function displayDateForDayKey(dayKey: string, timeZone: string): Date;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { addDaysToDayKey, combineDayKeyAndTime } from './timezoneUtils.js';
|
|
2
|
+
const DAY_KEY_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
3
|
+
function assertDayKey(dayKey) {
|
|
4
|
+
if (!DAY_KEY_RE.test(dayKey)) {
|
|
5
|
+
throw new Error(`Invalid day key "${dayKey}" — expected YYYY-MM-DD`);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Weekday index for a calendar date, 0 = Sunday. TZ-independent pure calendar
|
|
10
|
+
* math — the UTC instant is a carrier for the date only, never a real time.
|
|
11
|
+
*/ export function weekdayIndexOfDayKey(dayKey) {
|
|
12
|
+
assertDayKey(dayKey);
|
|
13
|
+
return new Date(`${dayKey}T00:00:00Z`).getUTCDay();
|
|
14
|
+
}
|
|
15
|
+
/** The Sunday on or before `dayKey`. */ export function startOfWeekDayKey(dayKey) {
|
|
16
|
+
return addDaysToDayKey(dayKey, -weekdayIndexOfDayKey(dayKey));
|
|
17
|
+
}
|
|
18
|
+
/** The Sunday on or before the 1st of `dayKey`'s month — the month grid origin. */ export function monthGridStartDayKey(dayKey) {
|
|
19
|
+
assertDayKey(dayKey);
|
|
20
|
+
return startOfWeekDayKey(`${dayKey.slice(0, 8)}01`);
|
|
21
|
+
}
|
|
22
|
+
/** `count` consecutive day keys starting at `startDayKey`. */ export function dayKeySequence(startDayKey, count) {
|
|
23
|
+
assertDayKey(startDayKey);
|
|
24
|
+
const keys = [];
|
|
25
|
+
for(let i = 0; i < count; i++){
|
|
26
|
+
keys.push(addDaysToDayKey(startDayKey, i));
|
|
27
|
+
}
|
|
28
|
+
return keys;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The instant at `hour`:00 on `dayKey` in the BUSINESS timezone.
|
|
32
|
+
*
|
|
33
|
+
* This is the whole point of the module: calendar rows are labelled with
|
|
34
|
+
* getHourInTimezone(date, businessTZ), so the instant a row maps to must be
|
|
35
|
+
* built in that same zone. Using Date#setHours here builds it in the VIEWER's
|
|
36
|
+
* zone, which is how clicking "10:00" came to book a different hour.
|
|
37
|
+
*/ export function instantAtHour(dayKey, hour, timeZone) {
|
|
38
|
+
return combineDayKeyAndTime(dayKey, `${String(hour).padStart(2, '0')}:00`, timeZone);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Like `instantAtHour`, but tolerates the exclusive end hour a grid window can
|
|
42
|
+
* carry: hour 24 is not a wall-clock time (`combineDayKeyAndTime` rejects
|
|
43
|
+
* "24:00"), it means midnight starting the FOLLOWING day, so carry it onto the
|
|
44
|
+
* next day key.
|
|
45
|
+
*
|
|
46
|
+
* The carry is deliberately a day-key increment rather than `+24h` on the day's
|
|
47
|
+
* start: on a DST-transition day the grid spans 23 or 25 real hours, and only
|
|
48
|
+
* re-resolving midnight in the business zone lands on the right instant.
|
|
49
|
+
*/ export function gridInstant(dayKey, hour, timeZone) {
|
|
50
|
+
return instantAtHour(addDaysToDayKey(dayKey, Math.floor(hour / 24)), hour % 24, timeZone);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A Date safe to hand to Intl for rendering `dayKey`'s calendar date.
|
|
54
|
+
*
|
|
55
|
+
* Noon is deliberate: far enough from both midnights that formatting it back
|
|
56
|
+
* with the SAME `timeZone` it was built with can never render the adjacent
|
|
57
|
+
* date, DST included — which is how every caller in this codebase uses it
|
|
58
|
+
* (construct and format in the one business zone). The margin is only ±12h
|
|
59
|
+
* from that zone's own offset, not unconditional: formatting in a *different*
|
|
60
|
+
* zone whose offset differs from `timeZone`'s by more than ~12h (e.g. noon
|
|
61
|
+
* built in Pacific/Auckland, UTC+12/+13, formatted in a UTC-11 zone) can still
|
|
62
|
+
* land on the adjacent day.
|
|
63
|
+
*/ export function displayDateForDayKey(dayKey, timeZone) {
|
|
64
|
+
return combineDayKeyAndTime(dayKey, '12:00', timeZone);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
//# sourceMappingURL=calendarGrid.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utilities/calendarGrid.ts"],"sourcesContent":["import { addDaysToDayKey, combineDayKeyAndTime } from './timezoneUtils.js'\n\nconst DAY_KEY_RE = /^\\d{4}-\\d{2}-\\d{2}$/\n\nfunction assertDayKey(dayKey: string): void {\n if (!DAY_KEY_RE.test(dayKey)) {\n throw new Error(`Invalid day key \"${dayKey}\" — expected YYYY-MM-DD`)\n }\n}\n\n/**\n * Weekday index for a calendar date, 0 = Sunday. TZ-independent pure calendar\n * math — the UTC instant is a carrier for the date only, never a real time.\n */\nexport function weekdayIndexOfDayKey(dayKey: string): number {\n assertDayKey(dayKey)\n return new Date(`${dayKey}T00:00:00Z`).getUTCDay()\n}\n\n/** The Sunday on or before `dayKey`. */\nexport function startOfWeekDayKey(dayKey: string): string {\n return addDaysToDayKey(dayKey, -weekdayIndexOfDayKey(dayKey))\n}\n\n/** The Sunday on or before the 1st of `dayKey`'s month — the month grid origin. */\nexport function monthGridStartDayKey(dayKey: string): string {\n assertDayKey(dayKey)\n return startOfWeekDayKey(`${dayKey.slice(0, 8)}01`)\n}\n\n/** `count` consecutive day keys starting at `startDayKey`. */\nexport function dayKeySequence(startDayKey: string, count: number): string[] {\n assertDayKey(startDayKey)\n const keys: string[] = []\n for (let i = 0; i < count; i++) {\n keys.push(addDaysToDayKey(startDayKey, i))\n }\n return keys\n}\n\n/**\n * The instant at `hour`:00 on `dayKey` in the BUSINESS timezone.\n *\n * This is the whole point of the module: calendar rows are labelled with\n * getHourInTimezone(date, businessTZ), so the instant a row maps to must be\n * built in that same zone. Using Date#setHours here builds it in the VIEWER's\n * zone, which is how clicking \"10:00\" came to book a different hour.\n */\nexport function instantAtHour(dayKey: string, hour: number, timeZone: string): Date {\n return combineDayKeyAndTime(dayKey, `${String(hour).padStart(2, '0')}:00`, timeZone)\n}\n\n/**\n * Like `instantAtHour`, but tolerates the exclusive end hour a grid window can\n * carry: hour 24 is not a wall-clock time (`combineDayKeyAndTime` rejects\n * \"24:00\"), it means midnight starting the FOLLOWING day, so carry it onto the\n * next day key.\n *\n * The carry is deliberately a day-key increment rather than `+24h` on the day's\n * start: on a DST-transition day the grid spans 23 or 25 real hours, and only\n * re-resolving midnight in the business zone lands on the right instant.\n */\nexport function gridInstant(dayKey: string, hour: number, timeZone: string): Date {\n return instantAtHour(addDaysToDayKey(dayKey, Math.floor(hour / 24)), hour % 24, timeZone)\n}\n\n/**\n * A Date safe to hand to Intl for rendering `dayKey`'s calendar date.\n *\n * Noon is deliberate: far enough from both midnights that formatting it back\n * with the SAME `timeZone` it was built with can never render the adjacent\n * date, DST included — which is how every caller in this codebase uses it\n * (construct and format in the one business zone). The margin is only ±12h\n * from that zone's own offset, not unconditional: formatting in a *different*\n * zone whose offset differs from `timeZone`'s by more than ~12h (e.g. noon\n * built in Pacific/Auckland, UTC+12/+13, formatted in a UTC-11 zone) can still\n * land on the adjacent day.\n */\nexport function displayDateForDayKey(dayKey: string, timeZone: string): Date {\n return combineDayKeyAndTime(dayKey, '12:00', timeZone)\n}\n"],"names":["addDaysToDayKey","combineDayKeyAndTime","DAY_KEY_RE","assertDayKey","dayKey","test","Error","weekdayIndexOfDayKey","Date","getUTCDay","startOfWeekDayKey","monthGridStartDayKey","slice","dayKeySequence","startDayKey","count","keys","i","push","instantAtHour","hour","timeZone","String","padStart","gridInstant","Math","floor","displayDateForDayKey"],"mappings":"AAAA,SAASA,eAAe,EAAEC,oBAAoB,QAAQ,qBAAoB;AAE1E,MAAMC,aAAa;AAEnB,SAASC,aAAaC,MAAc;IAClC,IAAI,CAACF,WAAWG,IAAI,CAACD,SAAS;QAC5B,MAAM,IAAIE,MAAM,CAAC,iBAAiB,EAAEF,OAAO,uBAAuB,CAAC;IACrE;AACF;AAEA;;;CAGC,GACD,OAAO,SAASG,qBAAqBH,MAAc;IACjDD,aAAaC;IACb,OAAO,IAAII,KAAK,GAAGJ,OAAO,UAAU,CAAC,EAAEK,SAAS;AAClD;AAEA,sCAAsC,GACtC,OAAO,SAASC,kBAAkBN,MAAc;IAC9C,OAAOJ,gBAAgBI,QAAQ,CAACG,qBAAqBH;AACvD;AAEA,iFAAiF,GACjF,OAAO,SAASO,qBAAqBP,MAAc;IACjDD,aAAaC;IACb,OAAOM,kBAAkB,GAAGN,OAAOQ,KAAK,CAAC,GAAG,GAAG,EAAE,CAAC;AACpD;AAEA,4DAA4D,GAC5D,OAAO,SAASC,eAAeC,WAAmB,EAAEC,KAAa;IAC/DZ,aAAaW;IACb,MAAME,OAAiB,EAAE;IACzB,IAAK,IAAIC,IAAI,GAAGA,IAAIF,OAAOE,IAAK;QAC9BD,KAAKE,IAAI,CAAClB,gBAAgBc,aAAaG;IACzC;IACA,OAAOD;AACT;AAEA;;;;;;;CAOC,GACD,OAAO,SAASG,cAAcf,MAAc,EAAEgB,IAAY,EAAEC,QAAgB;IAC1E,OAAOpB,qBAAqBG,QAAQ,GAAGkB,OAAOF,MAAMG,QAAQ,CAAC,GAAG,KAAK,GAAG,CAAC,EAAEF;AAC7E;AAEA;;;;;;;;;CASC,GACD,OAAO,SAASG,YAAYpB,MAAc,EAAEgB,IAAY,EAAEC,QAAgB;IACxE,OAAOF,cAAcnB,gBAAgBI,QAAQqB,KAAKC,KAAK,CAACN,OAAO,MAAMA,OAAO,IAAIC;AAClF;AAEA;;;;;;;;;;;CAWC,GACD,OAAO,SAASM,qBAAqBvB,MAAc,EAAEiB,QAAgB;IACnE,OAAOpB,qBAAqBG,QAAQ,SAASiB;AAC/C"}
|
|
@@ -11,7 +11,12 @@ export declare function mergeReservationData(data: Record<string, unknown>, orig
|
|
|
11
11
|
* field, and a notes-only edit must not trigger re-validation.
|
|
12
12
|
*/
|
|
13
13
|
export declare function schedulingFieldsChanged({ blockingStatuses, data, originalDoc, }: {
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Omit to compare scheduling VALUES only, skipping the status clause below.
|
|
16
|
+
* `validateActive` needs that: a confirm or cancel must stay possible on a
|
|
17
|
+
* booking whose service or resource was deactivated after it was made.
|
|
18
|
+
*/
|
|
19
|
+
blockingStatuses?: string[];
|
|
15
20
|
data: Record<string, unknown>;
|
|
16
21
|
originalDoc: Record<string, unknown> | undefined;
|
|
17
22
|
}): boolean;
|
|
@@ -76,7 +76,7 @@ function itemsEqual(a, b) {
|
|
|
76
76
|
return true;
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
|
-
if ('status' in data && typeof data.status === 'string') {
|
|
79
|
+
if (blockingStatuses && 'status' in data && typeof data.status === 'string') {
|
|
80
80
|
const prevStatus = originalDoc.status;
|
|
81
81
|
if (data.status !== prevStatus && blockingStatuses.includes(data.status) && (prevStatus === undefined || !blockingStatuses.includes(prevStatus))) {
|
|
82
82
|
return true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utilities/reservationChanges.ts"],"sourcesContent":["import { extractId } from './resolveReservationItems.js'\n\n/**\n * Fields whose change means a reservation's slot occupancy may differ,\n * requiring conflict re-validation and endTime recomputation on update.\n */\nconst SCHEDULING_FIELDS = [\n 'endTime',\n 'guestCount',\n 'items',\n 'resource',\n 'service',\n 'startTime',\n] as const\n\n/**\n * Read-only merged view of an update: the original document overlaid with the\n * incoming partial patch. Used for validation only — never assign the result\n * back into `data`, or unchanged fields get written back to the database.\n */\nexport function mergeReservationData(\n data: Record<string, unknown>,\n originalDoc: Record<string, unknown> | undefined,\n): Record<string, unknown> {\n return { ...(originalDoc ?? {}), ...(data ?? {}) }\n}\n\nfunction normalizeDate(value: unknown): null | number {\n if (value === null || value === undefined || value === '') {\n return null\n }\n const time = new Date(value as string).getTime()\n return Number.isNaN(time) ? null : time\n}\n\nfunction normalizeRelationship(value: unknown): null | string {\n const id = extractId(value)\n return id === undefined ? null : String(id)\n}\n\nfunction itemsEqual(a: unknown, b: unknown): boolean {\n const listA = Array.isArray(a) ? (a as Array<Record<string, unknown>>) : []\n const listB = Array.isArray(b) ? (b as Array<Record<string, unknown>>) : []\n if (listA.length !== listB.length) {\n return false\n }\n return listA.every((itemA, i) => {\n const itemB = listB[i]\n return (\n normalizeRelationship(itemA.resource) === normalizeRelationship(itemB.resource) &&\n normalizeRelationship(itemA.service) === normalizeRelationship(itemB.service) &&\n normalizeDate(itemA.startTime) === normalizeDate(itemB.startTime) &&\n normalizeDate(itemA.endTime) === normalizeDate(itemB.endTime) &&\n ((itemA.guestCount ?? null) as null | number) ===\n ((itemB.guestCount ?? null) as null | number)\n )\n })\n}\n\n/**\n * True when the update patch changes any scheduling-relevant field, or moves\n * status from a non-blocking value into a blocking one (re-occupying a slot).\n * Key presence alone is not a change — full-document admin saves include every\n * field, and a notes-only edit must not trigger re-validation.\n */\nexport function schedulingFieldsChanged({\n blockingStatuses,\n data,\n originalDoc,\n}: {\n
|
|
1
|
+
{"version":3,"sources":["../../src/utilities/reservationChanges.ts"],"sourcesContent":["import { extractId } from './resolveReservationItems.js'\n\n/**\n * Fields whose change means a reservation's slot occupancy may differ,\n * requiring conflict re-validation and endTime recomputation on update.\n */\nconst SCHEDULING_FIELDS = [\n 'endTime',\n 'guestCount',\n 'items',\n 'resource',\n 'service',\n 'startTime',\n] as const\n\n/**\n * Read-only merged view of an update: the original document overlaid with the\n * incoming partial patch. Used for validation only — never assign the result\n * back into `data`, or unchanged fields get written back to the database.\n */\nexport function mergeReservationData(\n data: Record<string, unknown>,\n originalDoc: Record<string, unknown> | undefined,\n): Record<string, unknown> {\n return { ...(originalDoc ?? {}), ...(data ?? {}) }\n}\n\nfunction normalizeDate(value: unknown): null | number {\n if (value === null || value === undefined || value === '') {\n return null\n }\n const time = new Date(value as string).getTime()\n return Number.isNaN(time) ? null : time\n}\n\nfunction normalizeRelationship(value: unknown): null | string {\n const id = extractId(value)\n return id === undefined ? null : String(id)\n}\n\nfunction itemsEqual(a: unknown, b: unknown): boolean {\n const listA = Array.isArray(a) ? (a as Array<Record<string, unknown>>) : []\n const listB = Array.isArray(b) ? (b as Array<Record<string, unknown>>) : []\n if (listA.length !== listB.length) {\n return false\n }\n return listA.every((itemA, i) => {\n const itemB = listB[i]\n return (\n normalizeRelationship(itemA.resource) === normalizeRelationship(itemB.resource) &&\n normalizeRelationship(itemA.service) === normalizeRelationship(itemB.service) &&\n normalizeDate(itemA.startTime) === normalizeDate(itemB.startTime) &&\n normalizeDate(itemA.endTime) === normalizeDate(itemB.endTime) &&\n ((itemA.guestCount ?? null) as null | number) ===\n ((itemB.guestCount ?? null) as null | number)\n )\n })\n}\n\n/**\n * True when the update patch changes any scheduling-relevant field, or moves\n * status from a non-blocking value into a blocking one (re-occupying a slot).\n * Key presence alone is not a change — full-document admin saves include every\n * field, and a notes-only edit must not trigger re-validation.\n */\nexport function schedulingFieldsChanged({\n blockingStatuses,\n data,\n originalDoc,\n}: {\n /**\n * Omit to compare scheduling VALUES only, skipping the status clause below.\n * `validateActive` needs that: a confirm or cancel must stay possible on a\n * booking whose service or resource was deactivated after it was made.\n */\n blockingStatuses?: string[]\n data: Record<string, unknown>\n originalDoc: Record<string, unknown> | undefined\n}): boolean {\n if (!originalDoc) {\n return true\n }\n\n for (const field of SCHEDULING_FIELDS) {\n if (!(field in data)) {\n continue\n }\n const next = data[field]\n const prev = originalDoc[field]\n let changed: boolean\n switch (field) {\n case 'endTime':\n case 'startTime':\n changed = normalizeDate(next) !== normalizeDate(prev)\n break\n case 'guestCount':\n changed = ((next ?? null) as null | number) !== ((prev ?? null) as null | number)\n break\n case 'items':\n changed = !itemsEqual(next, prev)\n break\n default:\n changed = normalizeRelationship(next) !== normalizeRelationship(prev)\n }\n if (changed) {\n return true\n }\n }\n\n if (blockingStatuses && 'status' in data && typeof data.status === 'string') {\n const prevStatus = originalDoc.status as string | undefined\n if (\n data.status !== prevStatus &&\n blockingStatuses.includes(data.status) &&\n (prevStatus === undefined || !blockingStatuses.includes(prevStatus))\n ) {\n return true\n }\n }\n\n return false\n}\n"],"names":["extractId","SCHEDULING_FIELDS","mergeReservationData","data","originalDoc","normalizeDate","value","undefined","time","Date","getTime","Number","isNaN","normalizeRelationship","id","String","itemsEqual","a","b","listA","Array","isArray","listB","length","every","itemA","i","itemB","resource","service","startTime","endTime","guestCount","schedulingFieldsChanged","blockingStatuses","field","next","prev","changed","status","prevStatus","includes"],"mappings":"AAAA,SAASA,SAAS,QAAQ,+BAA8B;AAExD;;;CAGC,GACD,MAAMC,oBAAoB;IACxB;IACA;IACA;IACA;IACA;IACA;CACD;AAED;;;;CAIC,GACD,OAAO,SAASC,qBACdC,IAA6B,EAC7BC,WAAgD;IAEhD,OAAO;QAAE,GAAIA,eAAe,CAAC,CAAC;QAAG,GAAID,QAAQ,CAAC,CAAC;IAAE;AACnD;AAEA,SAASE,cAAcC,KAAc;IACnC,IAAIA,UAAU,QAAQA,UAAUC,aAAaD,UAAU,IAAI;QACzD,OAAO;IACT;IACA,MAAME,OAAO,IAAIC,KAAKH,OAAiBI,OAAO;IAC9C,OAAOC,OAAOC,KAAK,CAACJ,QAAQ,OAAOA;AACrC;AAEA,SAASK,sBAAsBP,KAAc;IAC3C,MAAMQ,KAAKd,UAAUM;IACrB,OAAOQ,OAAOP,YAAY,OAAOQ,OAAOD;AAC1C;AAEA,SAASE,WAAWC,CAAU,EAAEC,CAAU;IACxC,MAAMC,QAAQC,MAAMC,OAAO,CAACJ,KAAMA,IAAuC,EAAE;IAC3E,MAAMK,QAAQF,MAAMC,OAAO,CAACH,KAAMA,IAAuC,EAAE;IAC3E,IAAIC,MAAMI,MAAM,KAAKD,MAAMC,MAAM,EAAE;QACjC,OAAO;IACT;IACA,OAAOJ,MAAMK,KAAK,CAAC,CAACC,OAAOC;QACzB,MAAMC,QAAQL,KAAK,CAACI,EAAE;QACtB,OACEb,sBAAsBY,MAAMG,QAAQ,MAAMf,sBAAsBc,MAAMC,QAAQ,KAC9Ef,sBAAsBY,MAAMI,OAAO,MAAMhB,sBAAsBc,MAAME,OAAO,KAC5ExB,cAAcoB,MAAMK,SAAS,MAAMzB,cAAcsB,MAAMG,SAAS,KAChEzB,cAAcoB,MAAMM,OAAO,MAAM1B,cAAcsB,MAAMI,OAAO,KAC5D,AAAEN,CAAAA,MAAMO,UAAU,IAAI,IAAG,MACrBL,CAAAA,MAAMK,UAAU,IAAI,IAAG;IAE/B;AACF;AAEA;;;;;CAKC,GACD,OAAO,SAASC,wBAAwB,EACtCC,gBAAgB,EAChB/B,IAAI,EACJC,WAAW,EAUZ;IACC,IAAI,CAACA,aAAa;QAChB,OAAO;IACT;IAEA,KAAK,MAAM+B,SAASlC,kBAAmB;QACrC,IAAI,CAAEkC,CAAAA,SAAShC,IAAG,GAAI;YACpB;QACF;QACA,MAAMiC,OAAOjC,IAAI,CAACgC,MAAM;QACxB,MAAME,OAAOjC,WAAW,CAAC+B,MAAM;QAC/B,IAAIG;QACJ,OAAQH;YACN,KAAK;YACL,KAAK;gBACHG,UAAUjC,cAAc+B,UAAU/B,cAAcgC;gBAChD;YACF,KAAK;gBACHC,UAAU,AAAEF,CAAAA,QAAQ,IAAG,MAA2BC,CAAAA,QAAQ,IAAG;gBAC7D;YACF,KAAK;gBACHC,UAAU,CAACtB,WAAWoB,MAAMC;gBAC5B;YACF;gBACEC,UAAUzB,sBAAsBuB,UAAUvB,sBAAsBwB;QACpE;QACA,IAAIC,SAAS;YACX,OAAO;QACT;IACF;IAEA,IAAIJ,oBAAoB,YAAY/B,QAAQ,OAAOA,KAAKoC,MAAM,KAAK,UAAU;QAC3E,MAAMC,aAAapC,YAAYmC,MAAM;QACrC,IACEpC,KAAKoC,MAAM,KAAKC,cAChBN,iBAAiBO,QAAQ,CAACtC,KAAKoC,MAAM,KACpCC,CAAAA,eAAejC,aAAa,CAAC2B,iBAAiBO,QAAQ,CAACD,WAAU,GAClE;YACA,OAAO;QACT;IACF;IAEA,OAAO;AACT"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Structured fields attached to one debug line. */
|
|
2
|
+
export type ReserveDebugFields = Record<string, unknown>;
|
|
3
|
+
/** Minimal logger surface — Payload's Pino `logger.info(obj, msg?)`. */
|
|
4
|
+
type DebugLogger = {
|
|
5
|
+
info: (obj: unknown, msg?: string) => void;
|
|
6
|
+
};
|
|
7
|
+
export type ReserveDebug = {
|
|
8
|
+
/** New instance, same traceId, merging baseFields into every subsequent line. */
|
|
9
|
+
child: (baseFields: ReserveDebugFields) => ReserveDebug;
|
|
10
|
+
/** Emit one trace line (no-op when disabled). */
|
|
11
|
+
dbg: (stage: string, fields?: ReserveDebugFields) => void;
|
|
12
|
+
enabled: boolean;
|
|
13
|
+
traceId: string;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Create a debug tracer. When `enabled`, every `dbg(stage, fields)` emits a
|
|
17
|
+
* single Pino line:
|
|
18
|
+
* `logger.info({ event: 'reserve_debug', traceId, stage, ...base, ...fields }, 'reserve_debug')`.
|
|
19
|
+
* When disabled, `dbg` is an immediate no-op. Emitting at INFO is deliberate:
|
|
20
|
+
* the opt-in flag is the gate, so traces survive Pino's default info level in
|
|
21
|
+
* production (a `.debug()` line would be silently dropped there).
|
|
22
|
+
*/
|
|
23
|
+
export declare function createReserveDebug(logger: DebugLogger, enabled: boolean, traceId?: string, baseFields?: ReserveDebugFields): ReserveDebug;
|
|
24
|
+
/** Shared disabled tracer — the default for functions whose `debug` param is omitted. */
|
|
25
|
+
export declare const NOOP_RESERVE_DEBUG: ReserveDebug;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Structured fields attached to one debug line. */ /**
|
|
2
|
+
* Create a debug tracer. When `enabled`, every `dbg(stage, fields)` emits a
|
|
3
|
+
* single Pino line:
|
|
4
|
+
* `logger.info({ event: 'reserve_debug', traceId, stage, ...base, ...fields }, 'reserve_debug')`.
|
|
5
|
+
* When disabled, `dbg` is an immediate no-op. Emitting at INFO is deliberate:
|
|
6
|
+
* the opt-in flag is the gate, so traces survive Pino's default info level in
|
|
7
|
+
* production (a `.debug()` line would be silently dropped there).
|
|
8
|
+
*/ export function createReserveDebug(logger, enabled, traceId, baseFields = {}) {
|
|
9
|
+
const id = traceId ?? Math.random().toString(36).slice(2, 10);
|
|
10
|
+
return {
|
|
11
|
+
child: (extra)=>createReserveDebug(logger, enabled, id, {
|
|
12
|
+
...baseFields,
|
|
13
|
+
...extra
|
|
14
|
+
}),
|
|
15
|
+
dbg: (stage, fields = {})=>{
|
|
16
|
+
if (!enabled) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
logger.info({
|
|
20
|
+
event: 'reserve_debug',
|
|
21
|
+
stage,
|
|
22
|
+
traceId: id,
|
|
23
|
+
...baseFields,
|
|
24
|
+
...fields
|
|
25
|
+
}, 'reserve_debug');
|
|
26
|
+
},
|
|
27
|
+
enabled,
|
|
28
|
+
traceId: id
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/** Shared disabled tracer — the default for functions whose `debug` param is omitted. */ export const NOOP_RESERVE_DEBUG = createReserveDebug({
|
|
32
|
+
info: ()=>{}
|
|
33
|
+
}, false);
|
|
34
|
+
|
|
35
|
+
//# sourceMappingURL=reserveDebug.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utilities/reserveDebug.ts"],"sourcesContent":["/** Structured fields attached to one debug line. */\nexport type ReserveDebugFields = Record<string, unknown>\n\n/** Minimal logger surface — Payload's Pino `logger.info(obj, msg?)`. */\ntype DebugLogger = { info: (obj: unknown, msg?: string) => void }\n\nexport type ReserveDebug = {\n /** New instance, same traceId, merging baseFields into every subsequent line. */\n child: (baseFields: ReserveDebugFields) => ReserveDebug\n /** Emit one trace line (no-op when disabled). */\n dbg: (stage: string, fields?: ReserveDebugFields) => void\n enabled: boolean\n traceId: string\n}\n\n/**\n * Create a debug tracer. When `enabled`, every `dbg(stage, fields)` emits a\n * single Pino line:\n * `logger.info({ event: 'reserve_debug', traceId, stage, ...base, ...fields }, 'reserve_debug')`.\n * When disabled, `dbg` is an immediate no-op. Emitting at INFO is deliberate:\n * the opt-in flag is the gate, so traces survive Pino's default info level in\n * production (a `.debug()` line would be silently dropped there).\n */\nexport function createReserveDebug(\n logger: DebugLogger,\n enabled: boolean,\n traceId?: string,\n baseFields: ReserveDebugFields = {},\n): ReserveDebug {\n const id = traceId ?? Math.random().toString(36).slice(2, 10)\n return {\n child: (extra) => createReserveDebug(logger, enabled, id, { ...baseFields, ...extra }),\n dbg: (stage, fields = {}) => {\n if (!enabled) {\n return\n }\n logger.info(\n { event: 'reserve_debug', stage, traceId: id, ...baseFields, ...fields },\n 'reserve_debug',\n )\n },\n enabled,\n traceId: id,\n }\n}\n\n/** Shared disabled tracer — the default for functions whose `debug` param is omitted. */\nexport const NOOP_RESERVE_DEBUG: ReserveDebug = createReserveDebug({ info: () => {} }, false)\n"],"names":["createReserveDebug","logger","enabled","traceId","baseFields","id","Math","random","toString","slice","child","extra","dbg","stage","fields","info","event","NOOP_RESERVE_DEBUG"],"mappings":"AAAA,kDAAkD,GAelD;;;;;;;CAOC,GACD,OAAO,SAASA,mBACdC,MAAmB,EACnBC,OAAgB,EAChBC,OAAgB,EAChBC,aAAiC,CAAC,CAAC;IAEnC,MAAMC,KAAKF,WAAWG,KAAKC,MAAM,GAAGC,QAAQ,CAAC,IAAIC,KAAK,CAAC,GAAG;IAC1D,OAAO;QACLC,OAAO,CAACC,QAAUX,mBAAmBC,QAAQC,SAASG,IAAI;gBAAE,GAAGD,UAAU;gBAAE,GAAGO,KAAK;YAAC;QACpFC,KAAK,CAACC,OAAOC,SAAS,CAAC,CAAC;YACtB,IAAI,CAACZ,SAAS;gBACZ;YACF;YACAD,OAAOc,IAAI,CACT;gBAAEC,OAAO;gBAAiBH;gBAAOV,SAASE;gBAAI,GAAGD,UAAU;gBAAE,GAAGU,MAAM;YAAC,GACvE;QAEJ;QACAZ;QACAC,SAASE;IACX;AACF;AAEA,uFAAuF,GACvF,OAAO,MAAMY,qBAAmCjB,mBAAmB;IAAEe,MAAM,KAAO;AAAE,GAAG,OAAM"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export type ResolvedItem = {
|
|
2
2
|
endTime: string;
|
|
3
|
+
/** True when this item was synthesised from the top-level resource/startTime. */
|
|
4
|
+
fromParent?: boolean;
|
|
3
5
|
guestCount: number;
|
|
4
6
|
resource: number | string;
|
|
5
7
|
service?: number | string;
|
|
@@ -8,13 +10,22 @@ export type ResolvedItem = {
|
|
|
8
10
|
/**
|
|
9
11
|
* Normalize reservation data into a list of resource-level items.
|
|
10
12
|
*
|
|
11
|
-
* - If items[] is populated -> return items (filling defaults from parent)
|
|
13
|
+
* - If items[] is populated -> return items (filling defaults from parent), then
|
|
14
|
+
* append a synthesised item for the top-level resource/startTime/endTime UNLESS
|
|
15
|
+
* an items[] entry can already be shown to cover that same window (see B1 in
|
|
16
|
+
* the synthesis step below) — this is what makes the top-level `resource`
|
|
17
|
+
* conflict-checked even when items[] never names it.
|
|
12
18
|
* Items missing startTime or resource throw a ValidationError.
|
|
13
19
|
* Duplicate (resource, startTime) pairs throw a ValidationError.
|
|
20
|
+
* An inverted top-level (parent) window throws a ValidationError too — UNLESS
|
|
21
|
+
* `options.lenient` is set, in which case parent synthesis is silently
|
|
22
|
+
* skipped instead (see the lenient-mode note below).
|
|
14
23
|
* - If items[] is empty/absent -> return single item from top-level fields
|
|
15
24
|
*
|
|
16
25
|
* Every downstream function (conflict check, endTime calc, availability)
|
|
17
26
|
* works with ResolvedItem[], never with raw reservation data.
|
|
18
27
|
*/
|
|
19
|
-
export declare function resolveReservationItems(data: Record<string, unknown
|
|
28
|
+
export declare function resolveReservationItems(data: Record<string, unknown>, options?: {
|
|
29
|
+
lenient?: boolean;
|
|
30
|
+
}): ResolvedItem[];
|
|
20
31
|
export declare function extractId(value: unknown): number | string | undefined;
|