payload-reserve 1.5.0 → 2.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 +40 -3
- package/dist/collections/Reservations.js +19 -7
- package/dist/collections/Reservations.js.map +1 -1
- package/dist/collections/Resources.js +11 -8
- package/dist/collections/Resources.js.map +1 -1
- package/dist/collections/Schedules.js +12 -6
- package/dist/collections/Schedules.js.map +1 -1
- package/dist/collections/Services.js +19 -10
- package/dist/collections/Services.js.map +1 -1
- package/dist/components/AvailabilityOverview/index.js +70 -26
- package/dist/components/AvailabilityOverview/index.js.map +1 -1
- package/dist/components/CalendarView/CalendarView.module.css +9 -0
- package/dist/components/CalendarView/LaneTimelineView.d.ts +4 -1
- package/dist/components/CalendarView/LaneTimelineView.js +17 -12
- package/dist/components/CalendarView/LaneTimelineView.js.map +1 -1
- package/dist/components/CalendarView/index.js +154 -53
- package/dist/components/CalendarView/index.js.map +1 -1
- package/dist/components/CustomerField/index.js +8 -3
- package/dist/components/CustomerField/index.js.map +1 -1
- package/dist/components/DashboardWidget/DashboardWidgetServer.js +97 -21
- package/dist/components/DashboardWidget/DashboardWidgetServer.js.map +1 -1
- package/dist/defaults.js +46 -8
- package/dist/defaults.js.map +1 -1
- package/dist/endpoints/cancelBooking.js +1 -1
- package/dist/endpoints/cancelBooking.js.map +1 -1
- package/dist/endpoints/checkAvailability.js +56 -7
- package/dist/endpoints/checkAvailability.js.map +1 -1
- package/dist/endpoints/createBooking.js +19 -10
- package/dist/endpoints/createBooking.js.map +1 -1
- package/dist/endpoints/customerSearch.js +5 -2
- package/dist/endpoints/customerSearch.js.map +1 -1
- package/dist/endpoints/getSlots.js +56 -7
- package/dist/endpoints/getSlots.js.map +1 -1
- package/dist/endpoints/resourceAvailability.d.ts +2 -1
- package/dist/endpoints/resourceAvailability.js +85 -25
- package/dist/endpoints/resourceAvailability.js.map +1 -1
- package/dist/hooks/reservations/calculateEndTime.js +48 -20
- package/dist/hooks/reservations/calculateEndTime.js.map +1 -1
- package/dist/hooks/reservations/enforceCustomerOwnership.d.ts +11 -0
- package/dist/hooks/reservations/enforceCustomerOwnership.js +30 -0
- package/dist/hooks/reservations/enforceCustomerOwnership.js.map +1 -0
- package/dist/hooks/reservations/onStatusChange.js +10 -4
- package/dist/hooks/reservations/onStatusChange.js.map +1 -1
- package/dist/hooks/reservations/validateCancellation.js +3 -2
- package/dist/hooks/reservations/validateCancellation.js.map +1 -1
- package/dist/hooks/reservations/validateConflicts.js +23 -4
- package/dist/hooks/reservations/validateConflicts.js.map +1 -1
- package/dist/hooks/reservations/validateGuestBooking.js +3 -4
- package/dist/hooks/reservations/validateGuestBooking.js.map +1 -1
- package/dist/hooks/reservations/validateStatusTransition.js +2 -2
- package/dist/hooks/reservations/validateStatusTransition.js.map +1 -1
- package/dist/hooks/users/provisionStaffResource.js +5 -8
- package/dist/hooks/users/provisionStaffResource.js.map +1 -1
- package/dist/plugin.js +82 -13
- package/dist/plugin.js.map +1 -1
- package/dist/services/AvailabilityService.d.ts +54 -2
- package/dist/services/AvailabilityService.js +180 -46
- package/dist/services/AvailabilityService.js.map +1 -1
- package/dist/translations/ar.json +1 -0
- package/dist/translations/de.json +1 -0
- package/dist/translations/en.json +1 -0
- package/dist/translations/es.json +1 -0
- package/dist/translations/fa.json +1 -0
- package/dist/translations/fr.json +1 -0
- package/dist/translations/hi.json +1 -0
- package/dist/translations/id.json +1 -0
- package/dist/translations/pl.json +1 -0
- package/dist/translations/ru.json +1 -0
- package/dist/translations/tr.json +1 -0
- package/dist/translations/zh.json +1 -0
- package/dist/types.d.ts +50 -1
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -1
- package/dist/utilities/collectionOverrides.d.ts +14 -0
- package/dist/utilities/collectionOverrides.js +47 -0
- package/dist/utilities/collectionOverrides.js.map +1 -0
- package/dist/utilities/ownerAccess.d.ts +6 -0
- package/dist/utilities/ownerAccess.js +25 -12
- package/dist/utilities/ownerAccess.js.map +1 -1
- package/dist/utilities/reservationChanges.d.ts +17 -0
- package/dist/utilities/reservationChanges.js +88 -0
- package/dist/utilities/reservationChanges.js.map +1 -0
- package/dist/utilities/scheduleUtils.d.ts +14 -8
- package/dist/utilities/scheduleUtils.js +26 -19
- package/dist/utilities/scheduleUtils.js.map +1 -1
- package/dist/utilities/tenantFilter.d.ts +25 -0
- package/dist/utilities/tenantFilter.js +56 -0
- package/dist/utilities/tenantFilter.js.map +1 -0
- package/dist/utilities/timezoneUtils.d.ts +39 -0
- package/dist/utilities/timezoneUtils.js +134 -0
- package/dist/utilities/timezoneUtils.js.map +1 -0
- package/dist/utilities/useTenantFilter.d.ts +6 -0
- package/dist/utilities/useTenantFilter.js +28 -0
- package/dist/utilities/useTenantFilter.js.map +1 -0
- package/package.json +2 -1
|
@@ -99,6 +99,7 @@
|
|
|
99
99
|
"fieldCustomerCreateNew": "Utwórz nowego klienta",
|
|
100
100
|
"fieldCustomerClear": "Wyczyść wybór",
|
|
101
101
|
"calendarPending": "Oczekujące",
|
|
102
|
+
"calendarShowingNofM": "Wyświetlono {{shown}} z {{total}} rezerwacji — zawęź zakres lub filtruj, aby zobaczyć resztę.",
|
|
102
103
|
"pendingDateTime": "Data / Czas",
|
|
103
104
|
"pendingActions": "Akcje",
|
|
104
105
|
"pendingSelectAll": "Zaznacz wszystkie",
|
|
@@ -99,6 +99,7 @@
|
|
|
99
99
|
"fieldCustomerCreateNew": "Создать нового клиента",
|
|
100
100
|
"fieldCustomerClear": "Очистить выбор",
|
|
101
101
|
"calendarPending": "Ожидает",
|
|
102
|
+
"calendarShowingNofM": "Показано {{shown}} из {{total}} броней — сузьте диапазон или примените фильтр, чтобы увидеть остальные.",
|
|
102
103
|
"pendingDateTime": "Дата / Время",
|
|
103
104
|
"pendingActions": "Действия",
|
|
104
105
|
"pendingSelectAll": "Выбрать все",
|
|
@@ -99,6 +99,7 @@
|
|
|
99
99
|
"fieldCustomerCreateNew": "Yeni müşteri oluştur",
|
|
100
100
|
"fieldCustomerClear": "Seçimi temizle",
|
|
101
101
|
"calendarPending": "Beklemede",
|
|
102
|
+
"calendarShowingNofM": "{{total}} rezervasyondan {{shown}} tanesi gösteriliyor — geri kalanını görmek için aralığı daraltın veya filtreleyin.",
|
|
102
103
|
"pendingDateTime": "Tarih / Saat",
|
|
103
104
|
"pendingActions": "İşlemler",
|
|
104
105
|
"pendingSelectAll": "Tümünü seç",
|
|
@@ -99,6 +99,7 @@
|
|
|
99
99
|
"fieldCustomerCreateNew": "新建客户",
|
|
100
100
|
"fieldCustomerClear": "清除选择",
|
|
101
101
|
"calendarPending": "待处理",
|
|
102
|
+
"calendarShowingNofM": "显示 {{total}} 个预约中的 {{shown}} 个 — 缩小范围或筛选以查看其余部分。",
|
|
102
103
|
"pendingDateTime": "日期 / 时间",
|
|
103
104
|
"pendingActions": "操作",
|
|
104
105
|
"pendingSelectAll": "全选",
|
package/dist/types.d.ts
CHANGED
|
@@ -3,6 +3,10 @@ export type DurationType = 'fixed' | 'flexible' | 'full-day';
|
|
|
3
3
|
export type CapacityMode = 'per-guest' | 'per-reservation';
|
|
4
4
|
export type StatusMachineConfig = {
|
|
5
5
|
blockingStatuses: string[];
|
|
6
|
+
/** Status treated as "cancelled" — fires beforeBookingCancel/afterBookingCancel, the cancellation notice period, and the cancellationReason field. */
|
|
7
|
+
cancelStatus: string;
|
|
8
|
+
/** Status treated as "confirmed" — fires beforeBookingConfirm/afterBookingConfirm. */
|
|
9
|
+
confirmStatus: string;
|
|
6
10
|
defaultStatus: string;
|
|
7
11
|
statuses: string[];
|
|
8
12
|
terminalStatuses: string[];
|
|
@@ -64,12 +68,15 @@ export type ResourceOwnerModeConfig = {
|
|
|
64
68
|
ownerCollection?: string;
|
|
65
69
|
/** Field name for the owner relationship on Resources (default: 'owner') */
|
|
66
70
|
ownerField?: string;
|
|
71
|
+
/** User field holding the role for admin detection (default: staffProvisioning.roleField or 'role') */
|
|
72
|
+
roleField?: string;
|
|
67
73
|
};
|
|
68
74
|
export type ResolvedResourceOwnerModeConfig = {
|
|
69
75
|
adminRoles: string[];
|
|
70
76
|
ownedServices: boolean;
|
|
71
77
|
ownerCollection?: string;
|
|
72
78
|
ownerField: string;
|
|
79
|
+
roleField: string;
|
|
73
80
|
};
|
|
74
81
|
export type StaffProvisioningConfig = {
|
|
75
82
|
/** Stamp tenant / custom fields onto the provisioned Resource before create. */
|
|
@@ -97,6 +104,17 @@ export type ResolvedStaffProvisioningConfig = {
|
|
|
97
104
|
staffRoles: string[];
|
|
98
105
|
userCollection: string;
|
|
99
106
|
};
|
|
107
|
+
/**
|
|
108
|
+
* Per-collection override applied to a generated collection. `fields` is a
|
|
109
|
+
* function receiving the plugin's default fields so you can append/reorder/
|
|
110
|
+
* replace them; supplied `hooks` are merged with (not replacing) the plugin's;
|
|
111
|
+
* `access` composes per operation; `slug` is ignored (use the `slugs` option).
|
|
112
|
+
*/
|
|
113
|
+
export type CollectionOverride = {
|
|
114
|
+
fields?: (args: {
|
|
115
|
+
defaultFields: Field[];
|
|
116
|
+
}) => Field[];
|
|
117
|
+
} & Omit<Partial<CollectionConfig>, 'fields' | 'slug'>;
|
|
100
118
|
export type ReservationPluginConfig = {
|
|
101
119
|
/** Override access control per collection */
|
|
102
120
|
access?: {
|
|
@@ -112,16 +130,31 @@ export type ReservationPluginConfig = {
|
|
|
112
130
|
allowGuestBooking?: boolean;
|
|
113
131
|
/** Hours of notice required before cancellation */
|
|
114
132
|
cancellationNoticePeriod?: number;
|
|
133
|
+
/** Per-collection overrides applied to the generated collections (issue #4) */
|
|
134
|
+
collectionOverrides?: {
|
|
135
|
+
customers?: CollectionOverride;
|
|
136
|
+
reservations?: CollectionOverride;
|
|
137
|
+
resources?: CollectionOverride;
|
|
138
|
+
schedules?: CollectionOverride;
|
|
139
|
+
services?: CollectionOverride;
|
|
140
|
+
};
|
|
115
141
|
/** Default buffer time in minutes between reservations */
|
|
116
142
|
defaultBufferTime?: number;
|
|
117
143
|
/** Disable the plugin entirely */
|
|
118
144
|
disabled?: boolean;
|
|
119
|
-
/** Extra fields
|
|
145
|
+
/** @deprecated Use `collectionOverrides.reservations.fields` instead. Extra fields appended to Reservations. */
|
|
120
146
|
extraReservationFields?: Field[];
|
|
121
147
|
/** Plugin hooks for external integrations */
|
|
122
148
|
hooks?: ReservationPluginHooks;
|
|
123
149
|
/** Configurable leave/exception type vocabulary (default: vacation/sick/personal/closure/other) */
|
|
124
150
|
leaveTypes?: string[];
|
|
151
|
+
/** 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. */
|
|
152
|
+
multiTenant?: {
|
|
153
|
+
/** Cookie written by the tenant-selector (default 'payload-tenant'). */
|
|
154
|
+
cookieName?: string;
|
|
155
|
+
/** Tenant field name on scoped collections (default 'tenant'). */
|
|
156
|
+
tenantField?: string;
|
|
157
|
+
};
|
|
125
158
|
/** Enable resource-owner multi-tenancy (opt-in) */
|
|
126
159
|
resourceOwnerMode?: ResourceOwnerModeConfig;
|
|
127
160
|
/** Configurable resourceType vocabulary (default: staff/equipment/room) */
|
|
@@ -139,6 +172,8 @@ export type ReservationPluginConfig = {
|
|
|
139
172
|
staffProvisioning?: StaffProvisioningConfig;
|
|
140
173
|
/** Configurable status machine (defaults to current behavior) */
|
|
141
174
|
statusMachine?: Partial<StatusMachineConfig>;
|
|
175
|
+
/** IANA business timezone governing schedules and day boundaries (default 'UTC') */
|
|
176
|
+
timezone?: string;
|
|
142
177
|
/** Which existing auth collection to extend with customer fields */
|
|
143
178
|
userCollection?: string;
|
|
144
179
|
};
|
|
@@ -153,12 +188,25 @@ export type ResolvedReservationPluginConfig = {
|
|
|
153
188
|
adminGroup: string;
|
|
154
189
|
allowGuestBooking: boolean;
|
|
155
190
|
cancellationNoticePeriod: number;
|
|
191
|
+
collectionOverrides: {
|
|
192
|
+
customers?: CollectionOverride;
|
|
193
|
+
reservations?: CollectionOverride;
|
|
194
|
+
resources?: CollectionOverride;
|
|
195
|
+
schedules?: CollectionOverride;
|
|
196
|
+
services?: CollectionOverride;
|
|
197
|
+
};
|
|
156
198
|
defaultBufferTime: number;
|
|
157
199
|
disabled: boolean;
|
|
158
200
|
extraReservationFields: Field[];
|
|
201
|
+
/** Whether the media collection (`slugs.media`) exists — set by the plugin; gates the image upload fields. */
|
|
202
|
+
hasMediaCollection: boolean;
|
|
159
203
|
hooks: ReservationPluginHooks;
|
|
160
204
|
leaveTypes: string[];
|
|
161
205
|
localized: boolean;
|
|
206
|
+
multiTenant: {
|
|
207
|
+
cookieName: string;
|
|
208
|
+
tenantField: string;
|
|
209
|
+
};
|
|
162
210
|
resourceOwnerMode: ResolvedResourceOwnerModeConfig | undefined;
|
|
163
211
|
resourceTypes: string[];
|
|
164
212
|
slugs: {
|
|
@@ -171,6 +219,7 @@ export type ResolvedReservationPluginConfig = {
|
|
|
171
219
|
};
|
|
172
220
|
staffProvisioning: ResolvedStaffProvisioningConfig | undefined;
|
|
173
221
|
statusMachine: StatusMachineConfig;
|
|
222
|
+
timezone: string;
|
|
174
223
|
userCollection: string | undefined;
|
|
175
224
|
};
|
|
176
225
|
export type ReservationStatus = 'cancelled' | 'completed' | 'confirmed' | 'no-show' | 'pending';
|
package/dist/types.js
CHANGED
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 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 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}\n\nexport type ResolvedResourceOwnerModeConfig = {\n adminRoles: string[]\n ownedServices: boolean\n ownerCollection?: string\n ownerField: 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\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 /** Default buffer time in minutes between reservations */\n defaultBufferTime?: number\n /** Disable the plugin entirely */\n disabled?: boolean\n /** Extra fields to append to the Reservations collection */\n extraReservationFields?: Field[]\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 /** 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 /** 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 defaultBufferTime: number\n disabled: boolean\n extraReservationFields: Field[]\n hooks: ReservationPluginHooks\n leaveTypes: string[]\n localized: boolean\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 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","defaultStatus","statuses","terminalStatuses","transitions","cancelled","completed","confirmed","pending","VALID_STATUS_TRANSITIONS"],"mappings":"AAkBA,OAAO,MAAMA,yBAA8C;IACzDC,kBAAkB;QAAC;QAAW;KAAY;IAC1CC,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;AAmMD,+DAA+D,GAC/D,OAAO,MAAMC,2BACXV,uBAAuBK,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\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 /** 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 /** 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 /** 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 }\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+OD,+DAA+D,GAC/D,OAAO,MAAMC,2BACXZ,uBAAuBO,WAAW,CAAkD"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CollectionConfig } from 'payload';
|
|
2
|
+
import type { CollectionOverride } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Apply a per-collection override to a generated collection, protecting the
|
|
5
|
+
* plugin's load-bearing behavior:
|
|
6
|
+
* - `fields`: a function receiving the plugin's default fields, returning the
|
|
7
|
+
* final list (append/reorder/replace — covers the issue #4 join-field case).
|
|
8
|
+
* - `hooks`: MERGED per event array (plugin hooks run first, then the user's) —
|
|
9
|
+
* an override can add hooks but never clobber conflict detection / status hooks.
|
|
10
|
+
* - `access`: composed per operation (rules the override omits survive).
|
|
11
|
+
* - `slug`: ignored (the `slugs` option owns slugs).
|
|
12
|
+
* - everything else (admin, labels, custom, …): shallow-merged.
|
|
13
|
+
*/
|
|
14
|
+
export declare function applyCollectionOverride(collection: CollectionConfig, override?: CollectionOverride): CollectionConfig;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { composeAccess } from './ownerAccess.js';
|
|
2
|
+
/**
|
|
3
|
+
* Apply a per-collection override to a generated collection, protecting the
|
|
4
|
+
* plugin's load-bearing behavior:
|
|
5
|
+
* - `fields`: a function receiving the plugin's default fields, returning the
|
|
6
|
+
* final list (append/reorder/replace — covers the issue #4 join-field case).
|
|
7
|
+
* - `hooks`: MERGED per event array (plugin hooks run first, then the user's) —
|
|
8
|
+
* an override can add hooks but never clobber conflict detection / status hooks.
|
|
9
|
+
* - `access`: composed per operation (rules the override omits survive).
|
|
10
|
+
* - `slug`: ignored (the `slugs` option owns slugs).
|
|
11
|
+
* - everything else (admin, labels, custom, …): shallow-merged.
|
|
12
|
+
*/ export function applyCollectionOverride(collection, override) {
|
|
13
|
+
if (!override) {
|
|
14
|
+
return collection;
|
|
15
|
+
}
|
|
16
|
+
// Pull out the specially-handled keys; the rest shallow-merges. `slug` is
|
|
17
|
+
// omitted from CollectionOverride's type, but strip it defensively in case a
|
|
18
|
+
// caller cast around the type — the `slugs` option owns slugs.
|
|
19
|
+
const { access, fields, hooks, ...rest } = override;
|
|
20
|
+
delete rest.slug;
|
|
21
|
+
const mergedFields = fields ? fields({
|
|
22
|
+
defaultFields: collection.fields
|
|
23
|
+
}) : collection.fields;
|
|
24
|
+
// Merge hooks per event array: plugin's first, then the override's.
|
|
25
|
+
const mergedHooks = {
|
|
26
|
+
...collection.hooks
|
|
27
|
+
};
|
|
28
|
+
if (hooks) {
|
|
29
|
+
for (const key of Object.keys(hooks)){
|
|
30
|
+
const pluginHooks = collection.hooks?.[key] ?? [];
|
|
31
|
+
const overrideHooks = hooks[key] ?? [];
|
|
32
|
+
mergedHooks[key] = [
|
|
33
|
+
...pluginHooks,
|
|
34
|
+
...overrideHooks
|
|
35
|
+
];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
...collection,
|
|
40
|
+
...rest,
|
|
41
|
+
access: composeAccess(collection.access ?? {}, access),
|
|
42
|
+
fields: mergedFields,
|
|
43
|
+
hooks: mergedHooks
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
//# sourceMappingURL=collectionOverrides.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utilities/collectionOverrides.ts"],"sourcesContent":["import type { CollectionConfig } from 'payload'\n\nimport type { CollectionOverride } from '../types.js'\n\nimport { composeAccess } from './ownerAccess.js'\n\n/**\n * Apply a per-collection override to a generated collection, protecting the\n * plugin's load-bearing behavior:\n * - `fields`: a function receiving the plugin's default fields, returning the\n * final list (append/reorder/replace — covers the issue #4 join-field case).\n * - `hooks`: MERGED per event array (plugin hooks run first, then the user's) —\n * an override can add hooks but never clobber conflict detection / status hooks.\n * - `access`: composed per operation (rules the override omits survive).\n * - `slug`: ignored (the `slugs` option owns slugs).\n * - everything else (admin, labels, custom, …): shallow-merged.\n */\nexport function applyCollectionOverride(\n collection: CollectionConfig,\n override?: CollectionOverride,\n): CollectionConfig {\n if (!override) {\n return collection\n }\n\n // Pull out the specially-handled keys; the rest shallow-merges. `slug` is\n // omitted from CollectionOverride's type, but strip it defensively in case a\n // caller cast around the type — the `slugs` option owns slugs.\n const { access, fields, hooks, ...rest } = override\n delete (rest as { slug?: unknown }).slug\n\n const mergedFields = fields ? fields({ defaultFields: collection.fields }) : collection.fields\n\n // Merge hooks per event array: plugin's first, then the override's.\n const mergedHooks: CollectionConfig['hooks'] = { ...collection.hooks }\n if (hooks) {\n for (const key of Object.keys(hooks) as Array<keyof NonNullable<CollectionConfig['hooks']>>) {\n const pluginHooks = (collection.hooks?.[key] ?? []) as unknown[]\n const overrideHooks = (hooks[key] ?? []) as unknown[]\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ;(mergedHooks as any)[key] = [...pluginHooks, ...overrideHooks]\n }\n }\n\n return {\n ...collection,\n ...rest,\n access: composeAccess(collection.access ?? {}, access),\n fields: mergedFields,\n hooks: mergedHooks,\n }\n}\n"],"names":["composeAccess","applyCollectionOverride","collection","override","access","fields","hooks","rest","slug","mergedFields","defaultFields","mergedHooks","key","Object","keys","pluginHooks","overrideHooks"],"mappings":"AAIA,SAASA,aAAa,QAAQ,mBAAkB;AAEhD;;;;;;;;;;CAUC,GACD,OAAO,SAASC,wBACdC,UAA4B,EAC5BC,QAA6B;IAE7B,IAAI,CAACA,UAAU;QACb,OAAOD;IACT;IAEA,0EAA0E;IAC1E,6EAA6E;IAC7E,+DAA+D;IAC/D,MAAM,EAAEE,MAAM,EAAEC,MAAM,EAAEC,KAAK,EAAE,GAAGC,MAAM,GAAGJ;IAC3C,OAAO,AAACI,KAA4BC,IAAI;IAExC,MAAMC,eAAeJ,SAASA,OAAO;QAAEK,eAAeR,WAAWG,MAAM;IAAC,KAAKH,WAAWG,MAAM;IAE9F,oEAAoE;IACpE,MAAMM,cAAyC;QAAE,GAAGT,WAAWI,KAAK;IAAC;IACrE,IAAIA,OAAO;QACT,KAAK,MAAMM,OAAOC,OAAOC,IAAI,CAACR,OAA+D;YAC3F,MAAMS,cAAeb,WAAWI,KAAK,EAAE,CAACM,IAAI,IAAI,EAAE;YAClD,MAAMI,gBAAiBV,KAAK,CAACM,IAAI,IAAI,EAAE;YAErCD,WAAmB,CAACC,IAAI,GAAG;mBAAIG;mBAAgBC;aAAc;QACjE;IACF;IAEA,OAAO;QACL,GAAGd,UAAU;QACb,GAAGK,IAAI;QACPH,QAAQJ,cAAcE,WAAWE,MAAM,IAAI,CAAC,GAAGA;QAC/CC,QAAQI;QACRH,OAAOK;IACT;AACF"}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { CollectionConfig } from 'payload';
|
|
2
2
|
import type { ResolvedResourceOwnerModeConfig } from '../types.js';
|
|
3
3
|
type CollectionAccess = NonNullable<CollectionConfig['access']>;
|
|
4
|
+
/**
|
|
5
|
+
* Overlay app-provided access overrides onto a base (owner-mode) access object,
|
|
6
|
+
* per operation. Specifying only `read` keeps the base's create/update/delete
|
|
7
|
+
* rules intact, instead of replacing them wholesale (review C9).
|
|
8
|
+
*/
|
|
9
|
+
export declare function composeAccess(base: CollectionAccess, override: CollectionConfig['access']): CollectionAccess;
|
|
4
10
|
/**
|
|
5
11
|
* Access factories for Resources collection.
|
|
6
12
|
* Owners may read/update/delete their own resources; anyone authenticated may create.
|
|
@@ -1,13 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Overlay app-provided access overrides onto a base (owner-mode) access object,
|
|
3
|
+
* per operation. Specifying only `read` keeps the base's create/update/delete
|
|
4
|
+
* rules intact, instead of replacing them wholesale (review C9).
|
|
5
|
+
*/ export function composeAccess(base, override) {
|
|
6
|
+
return {
|
|
7
|
+
...base,
|
|
8
|
+
...override ?? {}
|
|
9
|
+
};
|
|
10
|
+
}
|
|
1
11
|
/**
|
|
2
12
|
* Returns true if the requesting user is considered an "admin" for resource-owner mode:
|
|
3
13
|
* - No user → deny
|
|
4
|
-
* - adminRoles provided → user
|
|
14
|
+
* - adminRoles provided → the user's `roleField` value must be in that list
|
|
5
15
|
* - adminRoles empty → no bypass role; all authenticated users are treated as owners
|
|
6
|
-
|
|
16
|
+
*
|
|
17
|
+
* Reads the configured `roleField` (not a hardcoded `user.role`) so apps using a
|
|
18
|
+
* `roles: string[]` field — or any custom role field — aren't silently demoted.
|
|
19
|
+
*/ function isAdmin(user, adminRoles, roleField) {
|
|
7
20
|
if (!adminRoles.length) {
|
|
8
21
|
return false;
|
|
9
22
|
}
|
|
10
|
-
const role = user
|
|
23
|
+
const role = user[roleField];
|
|
11
24
|
if (!role) {
|
|
12
25
|
return false;
|
|
13
26
|
}
|
|
@@ -17,13 +30,13 @@
|
|
|
17
30
|
* Access factories for Resources collection.
|
|
18
31
|
* Owners may read/update/delete their own resources; anyone authenticated may create.
|
|
19
32
|
*/ export function makeResourceOwnerAccess(rom) {
|
|
20
|
-
const { adminRoles, ownerField } = rom;
|
|
33
|
+
const { adminRoles, ownerField, roleField } = rom;
|
|
21
34
|
const ownerOrAdmin = ({ req })=>{
|
|
22
35
|
if (!req.user) {
|
|
23
36
|
return false;
|
|
24
37
|
}
|
|
25
38
|
const user = req.user;
|
|
26
|
-
if (isAdmin(user, adminRoles)) {
|
|
39
|
+
if (isAdmin(user, adminRoles, roleField)) {
|
|
27
40
|
return true;
|
|
28
41
|
}
|
|
29
42
|
return {
|
|
@@ -43,13 +56,13 @@
|
|
|
43
56
|
* Access factories for Schedules collection.
|
|
44
57
|
* A schedule's ownership is determined through its `resource.owner` relationship.
|
|
45
58
|
*/ export function makeScheduleOwnerAccess(rom) {
|
|
46
|
-
const { adminRoles, ownerField } = rom;
|
|
59
|
+
const { adminRoles, ownerField, roleField } = rom;
|
|
47
60
|
const ownerOrAdmin = ({ req })=>{
|
|
48
61
|
if (!req.user) {
|
|
49
62
|
return false;
|
|
50
63
|
}
|
|
51
64
|
const user = req.user;
|
|
52
|
-
if (isAdmin(user, adminRoles)) {
|
|
65
|
+
if (isAdmin(user, adminRoles, roleField)) {
|
|
53
66
|
return true;
|
|
54
67
|
}
|
|
55
68
|
return {
|
|
@@ -70,13 +83,13 @@
|
|
|
70
83
|
* Resource owners can see reservations for their resources (read-only);
|
|
71
84
|
* mutations are admin-only to prevent owners from unilaterally cancelling guest bookings.
|
|
72
85
|
*/ export function makeReservationOwnerAccess(rom) {
|
|
73
|
-
const { adminRoles, ownerField } = rom;
|
|
86
|
+
const { adminRoles, ownerField, roleField } = rom;
|
|
74
87
|
const readAccess = ({ req })=>{
|
|
75
88
|
if (!req.user) {
|
|
76
89
|
return false;
|
|
77
90
|
}
|
|
78
91
|
const user = req.user;
|
|
79
|
-
if (isAdmin(user, adminRoles)) {
|
|
92
|
+
if (isAdmin(user, adminRoles, roleField)) {
|
|
80
93
|
return true;
|
|
81
94
|
}
|
|
82
95
|
return {
|
|
@@ -90,7 +103,7 @@
|
|
|
90
103
|
return false;
|
|
91
104
|
}
|
|
92
105
|
const user = req.user;
|
|
93
|
-
return isAdmin(user, adminRoles);
|
|
106
|
+
return isAdmin(user, adminRoles, roleField);
|
|
94
107
|
};
|
|
95
108
|
return {
|
|
96
109
|
create: adminOnly,
|
|
@@ -102,13 +115,13 @@
|
|
|
102
115
|
/**
|
|
103
116
|
* Access factories for Services collection when `ownedServices: true`.
|
|
104
117
|
*/ export function makeServiceOwnerAccess(rom, ownerField) {
|
|
105
|
-
const { adminRoles } = rom;
|
|
118
|
+
const { adminRoles, roleField } = rom;
|
|
106
119
|
const ownerOrAdmin = ({ req })=>{
|
|
107
120
|
if (!req.user) {
|
|
108
121
|
return false;
|
|
109
122
|
}
|
|
110
123
|
const user = req.user;
|
|
111
|
-
if (isAdmin(user, adminRoles)) {
|
|
124
|
+
if (isAdmin(user, adminRoles, roleField)) {
|
|
112
125
|
return true;
|
|
113
126
|
}
|
|
114
127
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utilities/ownerAccess.ts"],"sourcesContent":["import type { Access, CollectionConfig, PayloadRequest } from 'payload'\n\nimport type { ResolvedResourceOwnerModeConfig } from '../types.js'\n\ntype CollectionAccess = NonNullable<CollectionConfig['access']>\n\n/**\n * Returns true if the requesting user is considered an \"admin\" for resource-owner mode:\n * - No user → deny\n * - adminRoles provided → user
|
|
1
|
+
{"version":3,"sources":["../../src/utilities/ownerAccess.ts"],"sourcesContent":["import type { Access, CollectionConfig, PayloadRequest } from 'payload'\n\nimport type { ResolvedResourceOwnerModeConfig } from '../types.js'\n\ntype CollectionAccess = NonNullable<CollectionConfig['access']>\n\n/**\n * Overlay app-provided access overrides onto a base (owner-mode) access object,\n * per operation. Specifying only `read` keeps the base's create/update/delete\n * rules intact, instead of replacing them wholesale (review C9).\n */\nexport function composeAccess(\n base: CollectionAccess,\n override: CollectionConfig['access'],\n): CollectionAccess {\n return { ...base, ...(override ?? {}) }\n}\n\n/**\n * Returns true if the requesting user is considered an \"admin\" for resource-owner mode:\n * - No user → deny\n * - adminRoles provided → the user's `roleField` value must be in that list\n * - adminRoles empty → no bypass role; all authenticated users are treated as owners\n *\n * Reads the configured `roleField` (not a hardcoded `user.role`) so apps using a\n * `roles: string[]` field — or any custom role field — aren't silently demoted.\n */\nfunction isAdmin(user: Record<string, unknown>, adminRoles: string[], roleField: string): boolean {\n if (!adminRoles.length) {return false}\n const role = user[roleField] as string | string[] | undefined\n if (!role) {return false}\n return Array.isArray(role) ? role.some((r) => adminRoles.includes(r)) : adminRoles.includes(role)\n}\n\n/**\n * Access factories for Resources collection.\n * Owners may read/update/delete their own resources; anyone authenticated may create.\n */\nexport function makeResourceOwnerAccess(rom: ResolvedResourceOwnerModeConfig): CollectionAccess {\n const { adminRoles, ownerField, roleField } = rom\n\n const ownerOrAdmin: Access = ({ req }: { req: PayloadRequest }) => {\n if (!req.user) {return false}\n const user = req.user as Record<string, unknown>\n if (isAdmin(user, adminRoles, roleField)) {return true}\n return { [ownerField]: { equals: user.id } }\n }\n\n return {\n create: ({ req }: { req: PayloadRequest }) => Boolean(req.user),\n delete: ownerOrAdmin,\n read: ownerOrAdmin,\n update: ownerOrAdmin,\n }\n}\n\n/**\n * Access factories for Schedules collection.\n * A schedule's ownership is determined through its `resource.owner` relationship.\n */\nexport function makeScheduleOwnerAccess(rom: ResolvedResourceOwnerModeConfig): CollectionAccess {\n const { adminRoles, ownerField, roleField } = rom\n\n const ownerOrAdmin: Access = ({ req }: { req: PayloadRequest }) => {\n if (!req.user) {return false}\n const user = req.user as Record<string, unknown>\n if (isAdmin(user, adminRoles, roleField)) {return true}\n return { [`resource.${ownerField}`]: { equals: user.id } }\n }\n\n return {\n create: ({ req }: { req: PayloadRequest }) => Boolean(req.user),\n delete: ownerOrAdmin,\n read: ownerOrAdmin,\n update: ownerOrAdmin,\n }\n}\n\n/**\n * Access factories for Reservations collection.\n * Resource owners can see reservations for their resources (read-only);\n * mutations are admin-only to prevent owners from unilaterally cancelling guest bookings.\n */\nexport function makeReservationOwnerAccess(\n rom: ResolvedResourceOwnerModeConfig,\n): CollectionAccess {\n const { adminRoles, ownerField, roleField } = rom\n\n const readAccess: Access = ({ req }: { req: PayloadRequest }) => {\n if (!req.user) {return false}\n const user = req.user as Record<string, unknown>\n if (isAdmin(user, adminRoles, roleField)) {return true}\n return { [`resource.${ownerField}`]: { equals: user.id } }\n }\n\n const adminOnly: Access = ({ req }: { req: PayloadRequest }) => {\n if (!req.user) {return false}\n const user = req.user as Record<string, unknown>\n return isAdmin(user, adminRoles, roleField)\n }\n\n return {\n create: adminOnly,\n delete: adminOnly,\n read: readAccess,\n update: adminOnly,\n }\n}\n\n/**\n * Access factories for Services collection when `ownedServices: true`.\n */\nexport function makeServiceOwnerAccess(\n rom: ResolvedResourceOwnerModeConfig,\n ownerField: string,\n): CollectionAccess {\n const { adminRoles, roleField } = rom\n\n const ownerOrAdmin: Access = ({ req }: { req: PayloadRequest }) => {\n if (!req.user) {return false}\n const user = req.user as Record<string, unknown>\n if (isAdmin(user, adminRoles, roleField)) {return true}\n return { [ownerField]: { equals: user.id } }\n }\n\n return {\n create: ({ req }: { req: PayloadRequest }) => Boolean(req.user),\n delete: ownerOrAdmin,\n read: ownerOrAdmin,\n update: ownerOrAdmin,\n }\n}\n"],"names":["composeAccess","base","override","isAdmin","user","adminRoles","roleField","length","role","Array","isArray","some","r","includes","makeResourceOwnerAccess","rom","ownerField","ownerOrAdmin","req","equals","id","create","Boolean","delete","read","update","makeScheduleOwnerAccess","makeReservationOwnerAccess","readAccess","adminOnly","makeServiceOwnerAccess"],"mappings":"AAMA;;;;CAIC,GACD,OAAO,SAASA,cACdC,IAAsB,EACtBC,QAAoC;IAEpC,OAAO;QAAE,GAAGD,IAAI;QAAE,GAAIC,YAAY,CAAC,CAAC;IAAE;AACxC;AAEA;;;;;;;;CAQC,GACD,SAASC,QAAQC,IAA6B,EAAEC,UAAoB,EAAEC,SAAiB;IACrF,IAAI,CAACD,WAAWE,MAAM,EAAE;QAAC,OAAO;IAAK;IACrC,MAAMC,OAAOJ,IAAI,CAACE,UAAU;IAC5B,IAAI,CAACE,MAAM;QAAC,OAAO;IAAK;IACxB,OAAOC,MAAMC,OAAO,CAACF,QAAQA,KAAKG,IAAI,CAAC,CAACC,IAAMP,WAAWQ,QAAQ,CAACD,MAAMP,WAAWQ,QAAQ,CAACL;AAC9F;AAEA;;;CAGC,GACD,OAAO,SAASM,wBAAwBC,GAAoC;IAC1E,MAAM,EAAEV,UAAU,EAAEW,UAAU,EAAEV,SAAS,EAAE,GAAGS;IAE9C,MAAME,eAAuB,CAAC,EAAEC,GAAG,EAA2B;QAC5D,IAAI,CAACA,IAAId,IAAI,EAAE;YAAC,OAAO;QAAK;QAC5B,MAAMA,OAAOc,IAAId,IAAI;QACrB,IAAID,QAAQC,MAAMC,YAAYC,YAAY;YAAC,OAAO;QAAI;QACtD,OAAO;YAAE,CAACU,WAAW,EAAE;gBAAEG,QAAQf,KAAKgB,EAAE;YAAC;QAAE;IAC7C;IAEA,OAAO;QACLC,QAAQ,CAAC,EAAEH,GAAG,EAA2B,GAAKI,QAAQJ,IAAId,IAAI;QAC9DmB,QAAQN;QACRO,MAAMP;QACNQ,QAAQR;IACV;AACF;AAEA;;;CAGC,GACD,OAAO,SAASS,wBAAwBX,GAAoC;IAC1E,MAAM,EAAEV,UAAU,EAAEW,UAAU,EAAEV,SAAS,EAAE,GAAGS;IAE9C,MAAME,eAAuB,CAAC,EAAEC,GAAG,EAA2B;QAC5D,IAAI,CAACA,IAAId,IAAI,EAAE;YAAC,OAAO;QAAK;QAC5B,MAAMA,OAAOc,IAAId,IAAI;QACrB,IAAID,QAAQC,MAAMC,YAAYC,YAAY;YAAC,OAAO;QAAI;QACtD,OAAO;YAAE,CAAC,CAAC,SAAS,EAAEU,YAAY,CAAC,EAAE;gBAAEG,QAAQf,KAAKgB,EAAE;YAAC;QAAE;IAC3D;IAEA,OAAO;QACLC,QAAQ,CAAC,EAAEH,GAAG,EAA2B,GAAKI,QAAQJ,IAAId,IAAI;QAC9DmB,QAAQN;QACRO,MAAMP;QACNQ,QAAQR;IACV;AACF;AAEA;;;;CAIC,GACD,OAAO,SAASU,2BACdZ,GAAoC;IAEpC,MAAM,EAAEV,UAAU,EAAEW,UAAU,EAAEV,SAAS,EAAE,GAAGS;IAE9C,MAAMa,aAAqB,CAAC,EAAEV,GAAG,EAA2B;QAC1D,IAAI,CAACA,IAAId,IAAI,EAAE;YAAC,OAAO;QAAK;QAC5B,MAAMA,OAAOc,IAAId,IAAI;QACrB,IAAID,QAAQC,MAAMC,YAAYC,YAAY;YAAC,OAAO;QAAI;QACtD,OAAO;YAAE,CAAC,CAAC,SAAS,EAAEU,YAAY,CAAC,EAAE;gBAAEG,QAAQf,KAAKgB,EAAE;YAAC;QAAE;IAC3D;IAEA,MAAMS,YAAoB,CAAC,EAAEX,GAAG,EAA2B;QACzD,IAAI,CAACA,IAAId,IAAI,EAAE;YAAC,OAAO;QAAK;QAC5B,MAAMA,OAAOc,IAAId,IAAI;QACrB,OAAOD,QAAQC,MAAMC,YAAYC;IACnC;IAEA,OAAO;QACLe,QAAQQ;QACRN,QAAQM;QACRL,MAAMI;QACNH,QAAQI;IACV;AACF;AAEA;;CAEC,GACD,OAAO,SAASC,uBACdf,GAAoC,EACpCC,UAAkB;IAElB,MAAM,EAAEX,UAAU,EAAEC,SAAS,EAAE,GAAGS;IAElC,MAAME,eAAuB,CAAC,EAAEC,GAAG,EAA2B;QAC5D,IAAI,CAACA,IAAId,IAAI,EAAE;YAAC,OAAO;QAAK;QAC5B,MAAMA,OAAOc,IAAId,IAAI;QACrB,IAAID,QAAQC,MAAMC,YAAYC,YAAY;YAAC,OAAO;QAAI;QACtD,OAAO;YAAE,CAACU,WAAW,EAAE;gBAAEG,QAAQf,KAAKgB,EAAE;YAAC;QAAE;IAC7C;IAEA,OAAO;QACLC,QAAQ,CAAC,EAAEH,GAAG,EAA2B,GAAKI,QAAQJ,IAAId,IAAI;QAC9DmB,QAAQN;QACRO,MAAMP;QACNQ,QAAQR;IACV;AACF"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only merged view of an update: the original document overlaid with the
|
|
3
|
+
* incoming partial patch. Used for validation only — never assign the result
|
|
4
|
+
* back into `data`, or unchanged fields get written back to the database.
|
|
5
|
+
*/
|
|
6
|
+
export declare function mergeReservationData(data: Record<string, unknown>, originalDoc: Record<string, unknown> | undefined): Record<string, unknown>;
|
|
7
|
+
/**
|
|
8
|
+
* True when the update patch changes any scheduling-relevant field, or moves
|
|
9
|
+
* status from a non-blocking value into a blocking one (re-occupying a slot).
|
|
10
|
+
* Key presence alone is not a change — full-document admin saves include every
|
|
11
|
+
* field, and a notes-only edit must not trigger re-validation.
|
|
12
|
+
*/
|
|
13
|
+
export declare function schedulingFieldsChanged({ blockingStatuses, data, originalDoc, }: {
|
|
14
|
+
blockingStatuses: string[];
|
|
15
|
+
data: Record<string, unknown>;
|
|
16
|
+
originalDoc: Record<string, unknown> | undefined;
|
|
17
|
+
}): boolean;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { extractId } from './resolveReservationItems.js';
|
|
2
|
+
/**
|
|
3
|
+
* Fields whose change means a reservation's slot occupancy may differ,
|
|
4
|
+
* requiring conflict re-validation and endTime recomputation on update.
|
|
5
|
+
*/ const SCHEDULING_FIELDS = [
|
|
6
|
+
'endTime',
|
|
7
|
+
'guestCount',
|
|
8
|
+
'items',
|
|
9
|
+
'resource',
|
|
10
|
+
'service',
|
|
11
|
+
'startTime'
|
|
12
|
+
];
|
|
13
|
+
/**
|
|
14
|
+
* Read-only merged view of an update: the original document overlaid with the
|
|
15
|
+
* incoming partial patch. Used for validation only — never assign the result
|
|
16
|
+
* back into `data`, or unchanged fields get written back to the database.
|
|
17
|
+
*/ export function mergeReservationData(data, originalDoc) {
|
|
18
|
+
return {
|
|
19
|
+
...originalDoc ?? {},
|
|
20
|
+
...data ?? {}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function normalizeDate(value) {
|
|
24
|
+
if (value === null || value === undefined || value === '') {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const time = new Date(value).getTime();
|
|
28
|
+
return Number.isNaN(time) ? null : time;
|
|
29
|
+
}
|
|
30
|
+
function normalizeRelationship(value) {
|
|
31
|
+
const id = extractId(value);
|
|
32
|
+
return id === undefined ? null : String(id);
|
|
33
|
+
}
|
|
34
|
+
function itemsEqual(a, b) {
|
|
35
|
+
const listA = Array.isArray(a) ? a : [];
|
|
36
|
+
const listB = Array.isArray(b) ? b : [];
|
|
37
|
+
if (listA.length !== listB.length) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
return listA.every((itemA, i)=>{
|
|
41
|
+
const itemB = listB[i];
|
|
42
|
+
return normalizeRelationship(itemA.resource) === normalizeRelationship(itemB.resource) && normalizeRelationship(itemA.service) === normalizeRelationship(itemB.service) && normalizeDate(itemA.startTime) === normalizeDate(itemB.startTime) && normalizeDate(itemA.endTime) === normalizeDate(itemB.endTime) && (itemA.guestCount ?? null) === (itemB.guestCount ?? null);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* True when the update patch changes any scheduling-relevant field, or moves
|
|
47
|
+
* status from a non-blocking value into a blocking one (re-occupying a slot).
|
|
48
|
+
* Key presence alone is not a change — full-document admin saves include every
|
|
49
|
+
* field, and a notes-only edit must not trigger re-validation.
|
|
50
|
+
*/ export function schedulingFieldsChanged({ blockingStatuses, data, originalDoc }) {
|
|
51
|
+
if (!originalDoc) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
for (const field of SCHEDULING_FIELDS){
|
|
55
|
+
if (!(field in data)) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const next = data[field];
|
|
59
|
+
const prev = originalDoc[field];
|
|
60
|
+
let changed;
|
|
61
|
+
switch(field){
|
|
62
|
+
case 'endTime':
|
|
63
|
+
case 'startTime':
|
|
64
|
+
changed = normalizeDate(next) !== normalizeDate(prev);
|
|
65
|
+
break;
|
|
66
|
+
case 'guestCount':
|
|
67
|
+
changed = (next ?? null) !== (prev ?? null);
|
|
68
|
+
break;
|
|
69
|
+
case 'items':
|
|
70
|
+
changed = !itemsEqual(next, prev);
|
|
71
|
+
break;
|
|
72
|
+
default:
|
|
73
|
+
changed = normalizeRelationship(next) !== normalizeRelationship(prev);
|
|
74
|
+
}
|
|
75
|
+
if (changed) {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if ('status' in data && typeof data.status === 'string') {
|
|
80
|
+
const prevStatus = originalDoc.status;
|
|
81
|
+
if (data.status !== prevStatus && blockingStatuses.includes(data.status) && (prevStatus === undefined || !blockingStatuses.includes(prevStatus))) {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
//# sourceMappingURL=reservationChanges.js.map
|
|
@@ -0,0 +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 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 ('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,EAKZ;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,IAAI,YAAYnC,QAAQ,OAAOA,KAAKoC,MAAM,KAAK,UAAU;QACvD,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"}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { DayOfWeek } from '../types.js';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* @deprecated Server-TZ-dependent legacy helper — no longer used by the plugin's
|
|
4
|
+
* own resolution path. Use timezoneUtils (getDayOfWeekFromDayKey / combineDayKeyAndTime).
|
|
4
5
|
*/
|
|
5
6
|
export declare function getDayOfWeek(date: Date): DayOfWeek;
|
|
6
7
|
/**
|
|
7
|
-
*
|
|
8
|
+
* @deprecated Server-TZ-dependent legacy helper — no longer used by the plugin's
|
|
9
|
+
* own resolution path. Use timezoneUtils (getDayOfWeekFromDayKey / combineDayKeyAndTime).
|
|
8
10
|
*/
|
|
9
11
|
export declare function dateMatchesDay(date: Date, day: DayOfWeek): boolean;
|
|
10
12
|
/**
|
|
@@ -15,17 +17,19 @@ export declare function parseTime(time: string): {
|
|
|
15
17
|
minutes: number;
|
|
16
18
|
};
|
|
17
19
|
/**
|
|
18
|
-
*
|
|
20
|
+
* @deprecated Server-TZ-dependent legacy helper — no longer used by the plugin's
|
|
21
|
+
* own resolution path. Use timezoneUtils (getDayOfWeekFromDayKey / combineDayKeyAndTime).
|
|
19
22
|
*/
|
|
20
23
|
export declare function combineDateAndTime(date: Date, time: string): Date;
|
|
21
24
|
/**
|
|
22
|
-
* Check if a
|
|
25
|
+
* Check if a calendar day is an exception day in the schedule.
|
|
23
26
|
* Supports range exceptions via optional endDate (inclusive on both ends).
|
|
27
|
+
* `date` may be a Date instant (keyed in `timeZone`) or a YYYY-MM-DD day key.
|
|
24
28
|
*/
|
|
25
|
-
export declare function isExceptionDate(date: Date, exceptions: Array<{
|
|
29
|
+
export declare function isExceptionDate(date: Date | string, exceptions: Array<{
|
|
26
30
|
date: string;
|
|
27
31
|
endDate?: string;
|
|
28
|
-
}
|
|
32
|
+
}>, timeZone?: string): boolean;
|
|
29
33
|
type RecurringSlot = {
|
|
30
34
|
day: DayOfWeek;
|
|
31
35
|
endTime: string;
|
|
@@ -51,7 +55,9 @@ type TimeRange = {
|
|
|
51
55
|
start: Date;
|
|
52
56
|
};
|
|
53
57
|
/**
|
|
54
|
-
* Resolve a schedule to concrete available time ranges for a
|
|
58
|
+
* Resolve a schedule to concrete available time ranges for a calendar day.
|
|
59
|
+
* `date` may be a Date instant (keyed in `timeZone`) or a YYYY-MM-DD day key.
|
|
60
|
+
* All wall-clock times (HH:mm) are interpreted in `timeZone`.
|
|
55
61
|
*/
|
|
56
|
-
export declare function resolveScheduleForDate(schedule: Schedule, date: Date): TimeRange[];
|
|
62
|
+
export declare function resolveScheduleForDate(schedule: Schedule, date: Date | string, timeZone?: string): TimeRange[];
|
|
57
63
|
export {};
|