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
package/README.md
CHANGED
|
@@ -17,15 +17,16 @@ Designed for salons, clinics, hotels, restaurants, event venues, and any busines
|
|
|
17
17
|
- **5 Domain Collections** — Services, Resources, Schedules, Reservations, and Customers (standalone or user-collection extension)
|
|
18
18
|
- **User Collection Extension** — Optionally extend your existing auth collection with booking fields; set `userCollection: undefined` (default) to use a standalone Customers collection
|
|
19
19
|
- **Resource Owner Multi-Tenancy** — Opt-in `resourceOwnerMode` wires ownership access control so each resource owner (host) sees only their own listings and reservations
|
|
20
|
-
- **Configurable Status Machine** — Define your own statuses, transitions, blocking states, and
|
|
21
|
-
- **Double-Booking Prevention** — Server-side conflict detection
|
|
20
|
+
- **Configurable Status Machine** — Define your own statuses, transitions, blocking states, terminal states, and the `confirmStatus`/`cancelStatus` that drive the confirm/cancel hooks and cancellation policy
|
|
21
|
+
- **Double-Booking Prevention** — Server-side conflict detection that enforces both bookings' buffer times and checks each resource only for its own item window; respects capacity modes
|
|
22
|
+
- **Business Timezone** — Set a plugin-level `timezone` (IANA, default `'UTC'`) so schedules, day boundaries, and the admin calendar resolve in your business's timezone regardless of server location
|
|
22
23
|
- **Auto End Time** — Calculates `endTime` from `startTime + service.duration` automatically
|
|
23
24
|
- **Three Duration Types** — `fixed` (service duration), `flexible` (customer-specified end), and `full-day` bookings
|
|
24
25
|
- **Multi-Resource Bookings** — Single reservation that spans multiple resources simultaneously via the `items` array
|
|
25
26
|
- **Capacity and Inventory** — `quantity > 1` allows multiple concurrent bookings per resource; `capacityMode` (`per-reservation` | `per-guest`) controls how capacity is counted
|
|
26
27
|
- **Guest Bookings** — Account-less reservations with inline contact details (name + email/phone); `allowGuestBooking` plugin option and per-service `inherit`/`enabled`/`disabled` override; guests receive a `cancellationToken` via the `afterBookingCreate` hook for cancel-link delivery
|
|
27
28
|
- **Idempotency** — Optional `idempotencyKey` prevents duplicate submissions
|
|
28
|
-
- **
|
|
29
|
+
- **Collection Overrides** — Customize any generated collection (add fields like a `join`, tweak admin options, attach your own hooks) via `collectionOverrides` without forking — the plugin's hooks and access are merged, not clobbered (supersedes the deprecated `extraReservationFields`)
|
|
29
30
|
- **Cancellation Policy** — Configurable minimum notice period enforcement
|
|
30
31
|
- **Plugin Hooks API** — Seven lifecycle hooks (`beforeBookingCreate`, `afterBookingCreate`, `beforeBookingConfirm`, `afterBookingConfirm`, `beforeBookingCancel`, `afterBookingCancel`, `afterStatusChange`) for integrating email, Stripe, and external systems
|
|
31
32
|
- **Availability Service** — Pure functions and DB helpers for slot generation (15-min step) and conflict checking with guest-count-aware filtering
|
|
@@ -240,6 +241,42 @@ payloadReserve({
|
|
|
240
241
|
|
|
241
242
|
The first entry of `resourceTypes` becomes the default value for the `Resource.resourceType` field.
|
|
242
243
|
|
|
244
|
+
### Business Timezone
|
|
245
|
+
|
|
246
|
+
Set a plugin-level `timezone` (IANA name, default `'UTC'`) to govern all schedule resolution — what `HH:mm` schedule times mean, which calendar day a `date=YYYY-MM-DD` query maps to, exception-day matching, and full-day booking boundaries:
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
payloadReserve({
|
|
250
|
+
timezone: 'America/New_York', // default: 'UTC'
|
|
251
|
+
})
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Without this, day resolution mixes server-local and UTC semantics — on non-UTC servers the slots API can resolve the wrong calendar day. With `timezone` set, all server-side day math runs via the built-in `Intl` API (no extra dependency). `'UTC'` on a UTC server is identical to the previous behaviour. The configured timezone is exposed to admin components via `config.admin.custom.reservationTimezone`.
|
|
255
|
+
|
|
256
|
+
### Collection Overrides
|
|
257
|
+
|
|
258
|
+
Customize any generated collection without forking the plugin via `collectionOverrides`. Each entry is a `Partial<CollectionConfig>` (minus `fields`/`slug`) plus a `fields` function that receives the plugin's default fields:
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
payloadReserve({
|
|
262
|
+
collectionOverrides: {
|
|
263
|
+
services: {
|
|
264
|
+
// append a join field back to Resources (the inverse of Resources.services)
|
|
265
|
+
fields: ({ defaultFields }) => [
|
|
266
|
+
...defaultFields,
|
|
267
|
+
{ name: 'referencedResources', type: 'join', collection: 'resources', on: 'services' },
|
|
268
|
+
],
|
|
269
|
+
},
|
|
270
|
+
reservations: {
|
|
271
|
+
admin: { group: 'Bookings' }, // shallow-merged
|
|
272
|
+
hooks: { afterChange: [sendConfirmationEmail] }, // MERGED with the plugin's hooks
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
})
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
The plugin's load-bearing behavior is protected: supplied `hooks` are **merged** (the plugin's conflict-detection/status hooks always run, and run first — an override can add hooks but not remove them), `access` **composes** per operation (rules you omit keep the plugin's owner-mode/default behavior), and `slug` is ignored (use the `slugs` option). The `customers` override applies only when the standalone Customers collection is generated (ignored when `userCollection` is set — your auth collection is yours to edit directly). `collectionOverrides` supersedes the deprecated `extraReservationFields`.
|
|
279
|
+
|
|
243
280
|
### Optional `Resource.services`
|
|
244
281
|
|
|
245
282
|
The `services` relationship on Resources is now optional. This lets a freshly provisioned staff Resource exist before services are assigned, avoiding validation errors during auto-provisioning.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { calculateEndTime } from '../hooks/reservations/calculateEndTime.js';
|
|
2
2
|
import { checkIdempotency } from '../hooks/reservations/checkIdempotency.js';
|
|
3
|
+
import { enforceCustomerOwnership } from '../hooks/reservations/enforceCustomerOwnership.js';
|
|
3
4
|
import { expandRequiredResources } from '../hooks/reservations/expandRequiredResources.js';
|
|
4
5
|
import { onStatusChange } from '../hooks/reservations/onStatusChange.js';
|
|
5
6
|
import { validateCancellation } from '../hooks/reservations/validateCancellation.js';
|
|
@@ -7,7 +8,7 @@ import { validateConflicts } from '../hooks/reservations/validateConflicts.js';
|
|
|
7
8
|
import { validateGuestBooking } from '../hooks/reservations/validateGuestBooking.js';
|
|
8
9
|
import { validateStatusTransition } from '../hooks/reservations/validateStatusTransition.js';
|
|
9
10
|
import { statusToI18nKey } from '../utilities/i18nUtils.js';
|
|
10
|
-
import { makeReservationOwnerAccess } from '../utilities/ownerAccess.js';
|
|
11
|
+
import { composeAccess, makeReservationOwnerAccess } from '../utilities/ownerAccess.js';
|
|
11
12
|
function createPluginHooksBeforeCreate(hooks) {
|
|
12
13
|
return async ({ context, data, operation, req })=>{
|
|
13
14
|
if (context?.skipReservationHooks) {
|
|
@@ -30,7 +31,10 @@ function createPluginHooksBeforeCreate(hooks) {
|
|
|
30
31
|
};
|
|
31
32
|
}
|
|
32
33
|
function createPluginHooksAfterCreate(hooks) {
|
|
33
|
-
return async ({ doc, operation, req })=>{
|
|
34
|
+
return async ({ context, doc, operation, req })=>{
|
|
35
|
+
if (context?.skipReservationHooks) {
|
|
36
|
+
return doc;
|
|
37
|
+
}
|
|
34
38
|
if (operation === 'create' && hooks.afterBookingCreate) {
|
|
35
39
|
const docRecord = doc;
|
|
36
40
|
for (const hook of hooks.afterBookingCreate){
|
|
@@ -46,7 +50,7 @@ function createPluginHooksAfterCreate(hooks) {
|
|
|
46
50
|
export function createReservationsCollection(config) {
|
|
47
51
|
const { statusMachine } = config;
|
|
48
52
|
const rom = config.resourceOwnerMode;
|
|
49
|
-
const access =
|
|
53
|
+
const access = composeAccess(rom ? makeReservationOwnerAccess(rom) : {}, config.access.reservations);
|
|
50
54
|
return {
|
|
51
55
|
slug: config.slugs.reservations,
|
|
52
56
|
access,
|
|
@@ -124,7 +128,11 @@ export function createReservationsCollection(config) {
|
|
|
124
128
|
name: 'cancellationToken',
|
|
125
129
|
type: 'text',
|
|
126
130
|
access: {
|
|
127
|
-
|
|
131
|
+
// Server-generated secret: never settable via the API (the
|
|
132
|
+
// validateGuestBooking hook stamps it), readable only by staff/admin.
|
|
133
|
+
create: ()=>false,
|
|
134
|
+
read: ({ req })=>Boolean(req.user) && req.user.collection !== config.slugs.customers,
|
|
135
|
+
update: ()=>false
|
|
128
136
|
},
|
|
129
137
|
admin: {
|
|
130
138
|
hidden: true
|
|
@@ -174,7 +182,7 @@ export function createReservationsCollection(config) {
|
|
|
174
182
|
name: 'cancellationReason',
|
|
175
183
|
type: 'textarea',
|
|
176
184
|
admin: {
|
|
177
|
-
condition: (_, siblingData)=>siblingData?.status ===
|
|
185
|
+
condition: (_, siblingData)=>siblingData?.status === statusMachine.cancelStatus
|
|
178
186
|
},
|
|
179
187
|
label: ({ t })=>t('reservation:fieldCancellationReason')
|
|
180
188
|
},
|
|
@@ -259,13 +267,17 @@ export function createReservationsCollection(config) {
|
|
|
259
267
|
],
|
|
260
268
|
beforeChange: [
|
|
261
269
|
createPluginHooksBeforeCreate(config.hooks),
|
|
270
|
+
enforceCustomerOwnership(config),
|
|
262
271
|
checkIdempotency(config),
|
|
263
272
|
validateGuestBooking(config),
|
|
264
273
|
expandRequiredResources(config),
|
|
265
274
|
calculateEndTime(config),
|
|
266
275
|
validateConflicts(config),
|
|
267
|
-
validateStatusTransition
|
|
268
|
-
|
|
276
|
+
// validateCancellation runs BEFORE validateStatusTransition so a cancel
|
|
277
|
+
// rejected by the notice period never fires the beforeBookingCancel
|
|
278
|
+
// plugin hooks (e.g. refund initiation) for an update that won't land.
|
|
279
|
+
validateCancellation(config),
|
|
280
|
+
validateStatusTransition(config)
|
|
269
281
|
]
|
|
270
282
|
},
|
|
271
283
|
labels: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/collections/Reservations.ts"],"sourcesContent":["import type {\n CollectionAfterChangeHook,\n CollectionBeforeChangeHook,\n CollectionConfig,\n CollectionSlug,\n} from 'payload'\n\nimport type { PluginT } from '../translations/index.js'\nimport type { ReservationPluginHooks, ResolvedReservationPluginConfig } from '../types.js'\n\nimport { calculateEndTime } from '../hooks/reservations/calculateEndTime.js'\nimport { checkIdempotency } from '../hooks/reservations/checkIdempotency.js'\nimport { expandRequiredResources } from '../hooks/reservations/expandRequiredResources.js'\nimport { onStatusChange } from '../hooks/reservations/onStatusChange.js'\nimport { validateCancellation } from '../hooks/reservations/validateCancellation.js'\nimport { validateConflicts } from '../hooks/reservations/validateConflicts.js'\nimport { validateGuestBooking } from '../hooks/reservations/validateGuestBooking.js'\nimport { validateStatusTransition } from '../hooks/reservations/validateStatusTransition.js'\nimport { statusToI18nKey } from '../utilities/i18nUtils.js'\nimport { makeReservationOwnerAccess } from '../utilities/ownerAccess.js'\n\nfunction createPluginHooksBeforeCreate(\n hooks: ReservationPluginHooks,\n): CollectionBeforeChangeHook {\n return async ({ context, data, operation, req }) => {\n if (context?.skipReservationHooks) {return data}\n\n if (operation === 'create' && hooks.beforeBookingCreate) {\n let mutatedData = data\n for (const hook of hooks.beforeBookingCreate) {\n const result = await hook({ data: mutatedData, req })\n if (result) {mutatedData = result}\n }\n return mutatedData\n }\n\n return data\n }\n}\n\nfunction createPluginHooksAfterCreate(\n hooks: ReservationPluginHooks,\n): CollectionAfterChangeHook {\n return async ({ doc, operation, req }) => {\n if (operation === 'create' && hooks.afterBookingCreate) {\n const docRecord = doc as Record<string, unknown>\n for (const hook of hooks.afterBookingCreate) {\n await hook({ doc: docRecord, req })\n }\n }\n return doc\n }\n}\n\nexport function createReservationsCollection(\n config: ResolvedReservationPluginConfig,\n): CollectionConfig {\n const { statusMachine } = config\n const rom = config.resourceOwnerMode\n const access =\n config.access.reservations ?? (rom ? makeReservationOwnerAccess(rom) : {})\n\n return {\n slug: config.slugs.reservations,\n access,\n admin: {\n components: {\n views: {\n list: {\n Component: 'payload-reserve/client#CalendarView',\n },\n },\n },\n group: config.adminGroup,\n listSearchableFields: ['status'],\n useAsTitle: 'startTime',\n },\n fields: [\n {\n name: 'service',\n type: 'relationship',\n label: ({ t }) => (t as PluginT)('reservation:fieldService'),\n relationTo: config.slugs.services as unknown as CollectionSlug,\n required: true,\n },\n {\n name: 'resource',\n type: 'relationship',\n label: ({ t }) => (t as PluginT)('reservation:fieldResource'),\n relationTo: config.slugs.resources as unknown as CollectionSlug,\n required: true,\n },\n {\n name: 'customer',\n type: 'relationship',\n admin: {\n allowCreate: true,\n allowEdit: true,\n components: {\n Field: 'payload-reserve/client#CustomerField',\n },\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldCustomer'),\n relationTo: config.slugs.customers as unknown as CollectionSlug,\n required: false,\n },\n {\n name: 'guest',\n type: 'group',\n admin: {\n description: ({ t }) => (t as PluginT)('reservation:fieldGuestDesc'),\n },\n fields: [\n {\n name: 'name',\n type: 'text',\n label: ({ t }) => (t as PluginT)('reservation:fieldGuestName'),\n maxLength: 200,\n },\n {\n name: 'email',\n type: 'email',\n label: ({ t }) => (t as PluginT)('reservation:fieldGuestEmail'),\n },\n {\n name: 'phone',\n type: 'text',\n label: ({ t }) => (t as PluginT)('reservation:fieldGuestPhone'),\n maxLength: 50,\n },\n ],\n label: ({ t }) => (t as PluginT)('reservation:fieldGuest'),\n },\n {\n name: 'cancellationToken',\n type: 'text',\n access: {\n read: ({ req }) =>\n Boolean(req.user) && req.user!.collection !== config.slugs.customers,\n },\n admin: {\n hidden: true,\n },\n index: true,\n },\n {\n name: 'startTime',\n type: 'date',\n admin: {\n components: {\n Field: 'payload-reserve/client#AvailabilityTimeField',\n },\n date: {\n pickerAppearance: 'dayAndTime',\n },\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldStartTime'),\n required: true,\n },\n {\n name: 'endTime',\n type: 'date',\n admin: {\n date: {\n pickerAppearance: 'dayAndTime',\n },\n readOnly: true,\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldEndTime'),\n },\n {\n name: 'status',\n type: 'select',\n defaultValue: statusMachine.defaultStatus,\n label: ({ t }) => (t as PluginT)('reservation:fieldStatus'),\n options: statusMachine.statuses.map((s) => ({\n label: ({ t }) => {\n const key = statusToI18nKey(s)\n const translated = (t as PluginT)(key)\n return translated !== key ? translated : s.charAt(0).toUpperCase() + s.slice(1)\n },\n value: s,\n })),\n },\n {\n name: 'cancellationReason',\n type: 'textarea',\n admin: {\n condition: (_, siblingData) => siblingData?.status === 'cancelled',\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldCancellationReason'),\n },\n {\n name: 'guestCount',\n type: 'number',\n defaultValue: 1,\n label: ({ t }) => (t as PluginT)('reservation:fieldGuestCount'),\n min: 1,\n },\n {\n name: 'notes',\n type: 'textarea',\n label: ({ t }) => (t as PluginT)('reservation:fieldNotes'),\n },\n {\n name: 'items',\n type: 'array',\n admin: {\n description: ({ t }) => (t as PluginT)('reservation:fieldItemsDesc'),\n },\n fields: [\n {\n name: 'resource',\n type: 'relationship',\n label: ({ t }) => (t as PluginT)('reservation:fieldResource'),\n relationTo: config.slugs.resources as unknown as CollectionSlug,\n required: true,\n },\n {\n name: 'service',\n type: 'relationship',\n label: ({ t }) => (t as PluginT)('reservation:fieldService'),\n relationTo: config.slugs.services as unknown as CollectionSlug,\n },\n {\n name: 'startTime',\n type: 'date',\n admin: { date: { pickerAppearance: 'dayAndTime' } },\n label: ({ t }) => (t as PluginT)('reservation:fieldStartTime'),\n },\n {\n name: 'endTime',\n type: 'date',\n admin: { date: { pickerAppearance: 'dayAndTime' }, readOnly: false },\n label: ({ t }) => (t as PluginT)('reservation:fieldEndTime'),\n },\n {\n name: 'guestCount',\n type: 'number',\n label: ({ t }) => (t as PluginT)('reservation:fieldGuestCount'),\n min: 1,\n },\n ],\n label: ({ t }) => (t as PluginT)('reservation:fieldItems'),\n },\n {\n name: 'idempotencyKey',\n type: 'text',\n admin: { position: 'sidebar', readOnly: true },\n index: true,\n unique: true,\n },\n ...config.extraReservationFields,\n ],\n hooks: {\n afterChange: [\n createPluginHooksAfterCreate(config.hooks),\n onStatusChange(config),\n ],\n beforeChange: [\n createPluginHooksBeforeCreate(config.hooks),\n checkIdempotency(config),\n validateGuestBooking(config),\n expandRequiredResources(config),\n calculateEndTime(config),\n validateConflicts(config),\n validateStatusTransition(config),\n validateCancellation(config),\n ],\n },\n labels: {\n plural: ({ t }) => (t as PluginT)('reservation:collectionReservations'),\n singular: ({ t }) => (t as PluginT)('reservation:collectionReservations'),\n },\n }\n}\n"],"names":["calculateEndTime","checkIdempotency","expandRequiredResources","onStatusChange","validateCancellation","validateConflicts","validateGuestBooking","validateStatusTransition","statusToI18nKey","makeReservationOwnerAccess","createPluginHooksBeforeCreate","hooks","context","data","operation","req","skipReservationHooks","beforeBookingCreate","mutatedData","hook","result","createPluginHooksAfterCreate","doc","afterBookingCreate","docRecord","createReservationsCollection","config","statusMachine","rom","resourceOwnerMode","access","reservations","slug","slugs","admin","components","views","list","Component","group","adminGroup","listSearchableFields","useAsTitle","fields","name","type","label","t","relationTo","services","required","resources","allowCreate","allowEdit","Field","customers","description","maxLength","read","Boolean","user","collection","hidden","index","date","pickerAppearance","readOnly","defaultValue","defaultStatus","options","statuses","map","s","key","translated","charAt","toUpperCase","slice","value","condition","_","siblingData","status","min","position","unique","extraReservationFields","afterChange","beforeChange","labels","plural","singular"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/collections/Reservations.ts"],"sourcesContent":["import type {\n CollectionAfterChangeHook,\n CollectionBeforeChangeHook,\n CollectionConfig,\n CollectionSlug,\n} from 'payload'\n\nimport type { PluginT } from '../translations/index.js'\nimport type { ReservationPluginHooks, ResolvedReservationPluginConfig } from '../types.js'\n\nimport { calculateEndTime } from '../hooks/reservations/calculateEndTime.js'\nimport { checkIdempotency } from '../hooks/reservations/checkIdempotency.js'\nimport { enforceCustomerOwnership } from '../hooks/reservations/enforceCustomerOwnership.js'\nimport { expandRequiredResources } from '../hooks/reservations/expandRequiredResources.js'\nimport { onStatusChange } from '../hooks/reservations/onStatusChange.js'\nimport { validateCancellation } from '../hooks/reservations/validateCancellation.js'\nimport { validateConflicts } from '../hooks/reservations/validateConflicts.js'\nimport { validateGuestBooking } from '../hooks/reservations/validateGuestBooking.js'\nimport { validateStatusTransition } from '../hooks/reservations/validateStatusTransition.js'\nimport { statusToI18nKey } from '../utilities/i18nUtils.js'\nimport { composeAccess, makeReservationOwnerAccess } from '../utilities/ownerAccess.js'\n\nfunction createPluginHooksBeforeCreate(\n hooks: ReservationPluginHooks,\n): CollectionBeforeChangeHook {\n return async ({ context, data, operation, req }) => {\n if (context?.skipReservationHooks) {return data}\n\n if (operation === 'create' && hooks.beforeBookingCreate) {\n let mutatedData = data\n for (const hook of hooks.beforeBookingCreate) {\n const result = await hook({ data: mutatedData, req })\n if (result) {mutatedData = result}\n }\n return mutatedData\n }\n\n return data\n }\n}\n\nfunction createPluginHooksAfterCreate(\n hooks: ReservationPluginHooks,\n): CollectionAfterChangeHook {\n return async ({ context, doc, operation, req }) => {\n if (context?.skipReservationHooks) {return doc}\n if (operation === 'create' && hooks.afterBookingCreate) {\n const docRecord = doc as Record<string, unknown>\n for (const hook of hooks.afterBookingCreate) {\n await hook({ doc: docRecord, req })\n }\n }\n return doc\n }\n}\n\nexport function createReservationsCollection(\n config: ResolvedReservationPluginConfig,\n): CollectionConfig {\n const { statusMachine } = config\n const rom = config.resourceOwnerMode\n const access =\n composeAccess(rom ? makeReservationOwnerAccess(rom) : {}, config.access.reservations)\n\n return {\n slug: config.slugs.reservations,\n access,\n admin: {\n components: {\n views: {\n list: {\n Component: 'payload-reserve/client#CalendarView',\n },\n },\n },\n group: config.adminGroup,\n listSearchableFields: ['status'],\n useAsTitle: 'startTime',\n },\n fields: [\n {\n name: 'service',\n type: 'relationship',\n label: ({ t }) => (t as PluginT)('reservation:fieldService'),\n relationTo: config.slugs.services as unknown as CollectionSlug,\n required: true,\n },\n {\n name: 'resource',\n type: 'relationship',\n label: ({ t }) => (t as PluginT)('reservation:fieldResource'),\n relationTo: config.slugs.resources as unknown as CollectionSlug,\n required: true,\n },\n {\n name: 'customer',\n type: 'relationship',\n admin: {\n allowCreate: true,\n allowEdit: true,\n components: {\n Field: 'payload-reserve/client#CustomerField',\n },\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldCustomer'),\n relationTo: config.slugs.customers as unknown as CollectionSlug,\n required: false,\n },\n {\n name: 'guest',\n type: 'group',\n admin: {\n description: ({ t }) => (t as PluginT)('reservation:fieldGuestDesc'),\n },\n fields: [\n {\n name: 'name',\n type: 'text',\n label: ({ t }) => (t as PluginT)('reservation:fieldGuestName'),\n maxLength: 200,\n },\n {\n name: 'email',\n type: 'email',\n label: ({ t }) => (t as PluginT)('reservation:fieldGuestEmail'),\n },\n {\n name: 'phone',\n type: 'text',\n label: ({ t }) => (t as PluginT)('reservation:fieldGuestPhone'),\n maxLength: 50,\n },\n ],\n label: ({ t }) => (t as PluginT)('reservation:fieldGuest'),\n },\n {\n name: 'cancellationToken',\n type: 'text',\n access: {\n // Server-generated secret: never settable via the API (the\n // validateGuestBooking hook stamps it), readable only by staff/admin.\n create: () => false,\n read: ({ req }) =>\n Boolean(req.user) && req.user!.collection !== config.slugs.customers,\n update: () => false,\n },\n admin: {\n hidden: true,\n },\n index: true,\n },\n {\n name: 'startTime',\n type: 'date',\n admin: {\n components: {\n Field: 'payload-reserve/client#AvailabilityTimeField',\n },\n date: {\n pickerAppearance: 'dayAndTime',\n },\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldStartTime'),\n required: true,\n },\n {\n name: 'endTime',\n type: 'date',\n admin: {\n date: {\n pickerAppearance: 'dayAndTime',\n },\n readOnly: true,\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldEndTime'),\n },\n {\n name: 'status',\n type: 'select',\n defaultValue: statusMachine.defaultStatus,\n label: ({ t }) => (t as PluginT)('reservation:fieldStatus'),\n options: statusMachine.statuses.map((s) => ({\n label: ({ t }) => {\n const key = statusToI18nKey(s)\n const translated = (t as PluginT)(key)\n return translated !== key ? translated : s.charAt(0).toUpperCase() + s.slice(1)\n },\n value: s,\n })),\n },\n {\n name: 'cancellationReason',\n type: 'textarea',\n admin: {\n condition: (_, siblingData) => siblingData?.status === statusMachine.cancelStatus,\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldCancellationReason'),\n },\n {\n name: 'guestCount',\n type: 'number',\n defaultValue: 1,\n label: ({ t }) => (t as PluginT)('reservation:fieldGuestCount'),\n min: 1,\n },\n {\n name: 'notes',\n type: 'textarea',\n label: ({ t }) => (t as PluginT)('reservation:fieldNotes'),\n },\n {\n name: 'items',\n type: 'array',\n admin: {\n description: ({ t }) => (t as PluginT)('reservation:fieldItemsDesc'),\n },\n fields: [\n {\n name: 'resource',\n type: 'relationship',\n label: ({ t }) => (t as PluginT)('reservation:fieldResource'),\n relationTo: config.slugs.resources as unknown as CollectionSlug,\n required: true,\n },\n {\n name: 'service',\n type: 'relationship',\n label: ({ t }) => (t as PluginT)('reservation:fieldService'),\n relationTo: config.slugs.services as unknown as CollectionSlug,\n },\n {\n name: 'startTime',\n type: 'date',\n admin: { date: { pickerAppearance: 'dayAndTime' } },\n label: ({ t }) => (t as PluginT)('reservation:fieldStartTime'),\n },\n {\n name: 'endTime',\n type: 'date',\n admin: { date: { pickerAppearance: 'dayAndTime' }, readOnly: false },\n label: ({ t }) => (t as PluginT)('reservation:fieldEndTime'),\n },\n {\n name: 'guestCount',\n type: 'number',\n label: ({ t }) => (t as PluginT)('reservation:fieldGuestCount'),\n min: 1,\n },\n ],\n label: ({ t }) => (t as PluginT)('reservation:fieldItems'),\n },\n {\n name: 'idempotencyKey',\n type: 'text',\n admin: { position: 'sidebar', readOnly: true },\n index: true,\n unique: true,\n },\n ...config.extraReservationFields,\n ],\n hooks: {\n afterChange: [\n createPluginHooksAfterCreate(config.hooks),\n onStatusChange(config),\n ],\n beforeChange: [\n createPluginHooksBeforeCreate(config.hooks),\n enforceCustomerOwnership(config),\n checkIdempotency(config),\n validateGuestBooking(config),\n expandRequiredResources(config),\n calculateEndTime(config),\n validateConflicts(config),\n // validateCancellation runs BEFORE validateStatusTransition so a cancel\n // rejected by the notice period never fires the beforeBookingCancel\n // plugin hooks (e.g. refund initiation) for an update that won't land.\n validateCancellation(config),\n validateStatusTransition(config),\n ],\n },\n labels: {\n plural: ({ t }) => (t as PluginT)('reservation:collectionReservations'),\n singular: ({ t }) => (t as PluginT)('reservation:collectionReservations'),\n },\n }\n}\n"],"names":["calculateEndTime","checkIdempotency","enforceCustomerOwnership","expandRequiredResources","onStatusChange","validateCancellation","validateConflicts","validateGuestBooking","validateStatusTransition","statusToI18nKey","composeAccess","makeReservationOwnerAccess","createPluginHooksBeforeCreate","hooks","context","data","operation","req","skipReservationHooks","beforeBookingCreate","mutatedData","hook","result","createPluginHooksAfterCreate","doc","afterBookingCreate","docRecord","createReservationsCollection","config","statusMachine","rom","resourceOwnerMode","access","reservations","slug","slugs","admin","components","views","list","Component","group","adminGroup","listSearchableFields","useAsTitle","fields","name","type","label","t","relationTo","services","required","resources","allowCreate","allowEdit","Field","customers","description","maxLength","create","read","Boolean","user","collection","update","hidden","index","date","pickerAppearance","readOnly","defaultValue","defaultStatus","options","statuses","map","s","key","translated","charAt","toUpperCase","slice","value","condition","_","siblingData","status","cancelStatus","min","position","unique","extraReservationFields","afterChange","beforeChange","labels","plural","singular"],"mappings":"AAUA,SAASA,gBAAgB,QAAQ,4CAA2C;AAC5E,SAASC,gBAAgB,QAAQ,4CAA2C;AAC5E,SAASC,wBAAwB,QAAQ,oDAAmD;AAC5F,SAASC,uBAAuB,QAAQ,mDAAkD;AAC1F,SAASC,cAAc,QAAQ,0CAAyC;AACxE,SAASC,oBAAoB,QAAQ,gDAA+C;AACpF,SAASC,iBAAiB,QAAQ,6CAA4C;AAC9E,SAASC,oBAAoB,QAAQ,gDAA+C;AACpF,SAASC,wBAAwB,QAAQ,oDAAmD;AAC5F,SAASC,eAAe,QAAQ,4BAA2B;AAC3D,SAASC,aAAa,EAAEC,0BAA0B,QAAQ,8BAA6B;AAEvF,SAASC,8BACPC,KAA6B;IAE7B,OAAO,OAAO,EAAEC,OAAO,EAAEC,IAAI,EAAEC,SAAS,EAAEC,GAAG,EAAE;QAC7C,IAAIH,SAASI,sBAAsB;YAAC,OAAOH;QAAI;QAE/C,IAAIC,cAAc,YAAYH,MAAMM,mBAAmB,EAAE;YACvD,IAAIC,cAAcL;YAClB,KAAK,MAAMM,QAAQR,MAAMM,mBAAmB,CAAE;gBAC5C,MAAMG,SAAS,MAAMD,KAAK;oBAAEN,MAAMK;oBAAaH;gBAAI;gBACnD,IAAIK,QAAQ;oBAACF,cAAcE;gBAAM;YACnC;YACA,OAAOF;QACT;QAEA,OAAOL;IACT;AACF;AAEA,SAASQ,6BACPV,KAA6B;IAE7B,OAAO,OAAO,EAAEC,OAAO,EAAEU,GAAG,EAAER,SAAS,EAAEC,GAAG,EAAE;QAC5C,IAAIH,SAASI,sBAAsB;YAAC,OAAOM;QAAG;QAC9C,IAAIR,cAAc,YAAYH,MAAMY,kBAAkB,EAAE;YACtD,MAAMC,YAAYF;YAClB,KAAK,MAAMH,QAAQR,MAAMY,kBAAkB,CAAE;gBAC3C,MAAMJ,KAAK;oBAAEG,KAAKE;oBAAWT;gBAAI;YACnC;QACF;QACA,OAAOO;IACT;AACF;AAEA,OAAO,SAASG,6BACdC,MAAuC;IAEvC,MAAM,EAAEC,aAAa,EAAE,GAAGD;IAC1B,MAAME,MAAMF,OAAOG,iBAAiB;IACpC,MAAMC,SACJtB,cAAcoB,MAAMnB,2BAA2BmB,OAAO,CAAC,GAAGF,OAAOI,MAAM,CAACC,YAAY;IAEtF,OAAO;QACLC,MAAMN,OAAOO,KAAK,CAACF,YAAY;QAC/BD;QACAI,OAAO;YACLC,YAAY;gBACVC,OAAO;oBACLC,MAAM;wBACJC,WAAW;oBACb;gBACF;YACF;YACAC,OAAOb,OAAOc,UAAU;YACxBC,sBAAsB;gBAAC;aAAS;YAChCC,YAAY;QACd;QACAC,QAAQ;YACN;gBACEC,MAAM;gBACNC,MAAM;gBACNC,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;gBACjCC,YAAYtB,OAAOO,KAAK,CAACgB,QAAQ;gBACjCC,UAAU;YACZ;YACA;gBACEN,MAAM;gBACNC,MAAM;gBACNC,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;gBACjCC,YAAYtB,OAAOO,KAAK,CAACkB,SAAS;gBAClCD,UAAU;YACZ;YACA;gBACEN,MAAM;gBACNC,MAAM;gBACNX,OAAO;oBACLkB,aAAa;oBACbC,WAAW;oBACXlB,YAAY;wBACVmB,OAAO;oBACT;gBACF;gBACAR,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;gBACjCC,YAAYtB,OAAOO,KAAK,CAACsB,SAAS;gBAClCL,UAAU;YACZ;YACA;gBACEN,MAAM;gBACNC,MAAM;gBACNX,OAAO;oBACLsB,aAAa,CAAC,EAAET,CAAC,EAAE,GAAK,AAACA,EAAc;gBACzC;gBACAJ,QAAQ;oBACN;wBACEC,MAAM;wBACNC,MAAM;wBACNC,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;wBACjCU,WAAW;oBACb;oBACA;wBACEb,MAAM;wBACNC,MAAM;wBACNC,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;oBACnC;oBACA;wBACEH,MAAM;wBACNC,MAAM;wBACNC,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;wBACjCU,WAAW;oBACb;iBACD;gBACDX,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;YACnC;YACA;gBACEH,MAAM;gBACNC,MAAM;gBACNf,QAAQ;oBACN,2DAA2D;oBAC3D,sEAAsE;oBACtE4B,QAAQ,IAAM;oBACdC,MAAM,CAAC,EAAE5C,GAAG,EAAE,GACZ6C,QAAQ7C,IAAI8C,IAAI,KAAK9C,IAAI8C,IAAI,CAAEC,UAAU,KAAKpC,OAAOO,KAAK,CAACsB,SAAS;oBACtEQ,QAAQ,IAAM;gBAChB;gBACA7B,OAAO;oBACL8B,QAAQ;gBACV;gBACAC,OAAO;YACT;YACA;gBACErB,MAAM;gBACNC,MAAM;gBACNX,OAAO;oBACLC,YAAY;wBACVmB,OAAO;oBACT;oBACAY,MAAM;wBACJC,kBAAkB;oBACpB;gBACF;gBACArB,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;gBACjCG,UAAU;YACZ;YACA;gBACEN,MAAM;gBACNC,MAAM;gBACNX,OAAO;oBACLgC,MAAM;wBACJC,kBAAkB;oBACpB;oBACAC,UAAU;gBACZ;gBACAtB,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;YACnC;YACA;gBACEH,MAAM;gBACNC,MAAM;gBACNwB,cAAc1C,cAAc2C,aAAa;gBACzCxB,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;gBACjCwB,SAAS5C,cAAc6C,QAAQ,CAACC,GAAG,CAAC,CAACC,IAAO,CAAA;wBAC1C5B,OAAO,CAAC,EAAEC,CAAC,EAAE;4BACX,MAAM4B,MAAMpE,gBAAgBmE;4BAC5B,MAAME,aAAa,AAAC7B,EAAc4B;4BAClC,OAAOC,eAAeD,MAAMC,aAAaF,EAAEG,MAAM,CAAC,GAAGC,WAAW,KAAKJ,EAAEK,KAAK,CAAC;wBAC/E;wBACAC,OAAON;oBACT,CAAA;YACF;YACA;gBACE9B,MAAM;gBACNC,MAAM;gBACNX,OAAO;oBACL+C,WAAW,CAACC,GAAGC,cAAgBA,aAAaC,WAAWzD,cAAc0D,YAAY;gBACnF;gBACAvC,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;YACnC;YACA;gBACEH,MAAM;gBACNC,MAAM;gBACNwB,cAAc;gBACdvB,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;gBACjCuC,KAAK;YACP;YACA;gBACE1C,MAAM;gBACNC,MAAM;gBACNC,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;YACnC;YACA;gBACEH,MAAM;gBACNC,MAAM;gBACNX,OAAO;oBACLsB,aAAa,CAAC,EAAET,CAAC,EAAE,GAAK,AAACA,EAAc;gBACzC;gBACAJ,QAAQ;oBACN;wBACEC,MAAM;wBACNC,MAAM;wBACNC,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;wBACjCC,YAAYtB,OAAOO,KAAK,CAACkB,SAAS;wBAClCD,UAAU;oBACZ;oBACA;wBACEN,MAAM;wBACNC,MAAM;wBACNC,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;wBACjCC,YAAYtB,OAAOO,KAAK,CAACgB,QAAQ;oBACnC;oBACA;wBACEL,MAAM;wBACNC,MAAM;wBACNX,OAAO;4BAAEgC,MAAM;gCAAEC,kBAAkB;4BAAa;wBAAE;wBAClDrB,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;oBACnC;oBACA;wBACEH,MAAM;wBACNC,MAAM;wBACNX,OAAO;4BAAEgC,MAAM;gCAAEC,kBAAkB;4BAAa;4BAAGC,UAAU;wBAAM;wBACnEtB,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;oBACnC;oBACA;wBACEH,MAAM;wBACNC,MAAM;wBACNC,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;wBACjCuC,KAAK;oBACP;iBACD;gBACDxC,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;YACnC;YACA;gBACEH,MAAM;gBACNC,MAAM;gBACNX,OAAO;oBAAEqD,UAAU;oBAAWnB,UAAU;gBAAK;gBAC7CH,OAAO;gBACPuB,QAAQ;YACV;eACG9D,OAAO+D,sBAAsB;SACjC;QACD9E,OAAO;YACL+E,aAAa;gBACXrE,6BAA6BK,OAAOf,KAAK;gBACzCT,eAAewB;aAChB;YACDiE,cAAc;gBACZjF,8BAA8BgB,OAAOf,KAAK;gBAC1CX,yBAAyB0B;gBACzB3B,iBAAiB2B;gBACjBrB,qBAAqBqB;gBACrBzB,wBAAwByB;gBACxB5B,iBAAiB4B;gBACjBtB,kBAAkBsB;gBAClB,wEAAwE;gBACxE,oEAAoE;gBACpE,uEAAuE;gBACvEvB,qBAAqBuB;gBACrBpB,yBAAyBoB;aAC1B;QACH;QACAkE,QAAQ;YACNC,QAAQ,CAAC,EAAE9C,CAAC,EAAE,GAAK,AAACA,EAAc;YAClC+C,UAAU,CAAC,EAAE/C,CAAC,EAAE,GAAK,AAACA,EAAc;QACtC;IACF;AACF"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { makeResourceOwnerAccess } from '../utilities/ownerAccess.js';
|
|
1
|
+
import { composeAccess, makeResourceOwnerAccess } from '../utilities/ownerAccess.js';
|
|
2
2
|
import { buildSelectOptions } from '../utilities/selectOptions.js';
|
|
3
3
|
/**
|
|
4
4
|
* Owner-field beforeChange logic, extracted for testability. On create the
|
|
@@ -40,7 +40,7 @@ export function createResourcesCollection(config) {
|
|
|
40
40
|
required: true
|
|
41
41
|
} : null;
|
|
42
42
|
// Determine access: app override → owner-mode auto-wired → unrestricted
|
|
43
|
-
const access =
|
|
43
|
+
const access = composeAccess(rom ? makeResourceOwnerAccess(rom) : {}, config.access.resources);
|
|
44
44
|
return {
|
|
45
45
|
slug: config.slugs.resources,
|
|
46
46
|
access,
|
|
@@ -59,12 +59,14 @@ export function createResourcesCollection(config) {
|
|
|
59
59
|
maxLength: 200,
|
|
60
60
|
required: true
|
|
61
61
|
},
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
...config.hasMediaCollection ? [
|
|
63
|
+
{
|
|
64
|
+
name: 'image',
|
|
65
|
+
type: 'upload',
|
|
66
|
+
label: ({ t })=>t('reservation:fieldImage'),
|
|
67
|
+
relationTo: config.slugs.media
|
|
68
|
+
}
|
|
69
|
+
] : [],
|
|
68
70
|
{
|
|
69
71
|
name: 'description',
|
|
70
72
|
type: 'textarea',
|
|
@@ -124,6 +126,7 @@ export function createResourcesCollection(config) {
|
|
|
124
126
|
name: 'timezone',
|
|
125
127
|
type: 'text',
|
|
126
128
|
admin: {
|
|
129
|
+
description: 'Deprecated — not used by the plugin. Schedule resolution uses the plugin-level `timezone` option.',
|
|
127
130
|
position: 'sidebar'
|
|
128
131
|
},
|
|
129
132
|
label: ({ t })=>t('reservation:fieldTimezone')
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/collections/Resources.ts"],"sourcesContent":["import type { CollectionConfig, CollectionSlug, Field } from 'payload'\n\nimport type { PluginT } from '../translations/index.js'\nimport type { ResolvedReservationPluginConfig } from '../types.js'\n\nimport { makeResourceOwnerAccess } from '../utilities/ownerAccess.js'\nimport { buildSelectOptions } from '../utilities/selectOptions.js'\n\n/**\n * Owner-field beforeChange logic, extracted for testability. On create the\n * owner defaults to the requesting user; on other operations the existing\n * value is preserved. Staff provisioning assigns the correct owner by creating\n * the Resource AS the new staff user (impersonation in the provisioning hook),\n * so this function needs no special-case branch.\n */\nexport function resolveOwnerValue({\n operation,\n req,\n value,\n}: {\n operation: string | undefined\n req: { user?: { id: unknown } | null }\n value: unknown\n}): unknown {\n if (operation === 'create' && req.user) {\n return req.user.id\n }\n return value\n}\n\nexport function createResourcesCollection(\n config: ResolvedReservationPluginConfig,\n): CollectionConfig {\n const rom = config.resourceOwnerMode\n const ownerField = rom?.ownerField ?? 'owner'\n // The owner relationship points to where owners/staff live: an explicit\n // ownerCollection, else the staff-provisioning user collection, else customers.\n // (Previously hardcoded to customers, which broke separate users/customers setups.)\n const ownerCollection =\n rom?.ownerCollection ?? config.staffProvisioning?.userCollection ?? config.slugs.customers\n\n // Build the owner field when resourceOwnerMode is enabled\n const ownerFieldDef: Field | null = rom\n ? {\n name: ownerField,\n type: 'relationship',\n admin: {\n position: 'sidebar',\n },\n hooks: {\n beforeChange: [\n ({ operation, req, value }) => resolveOwnerValue({ operation, req, value }),\n ],\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldOwner'),\n relationTo: ownerCollection as unknown as CollectionSlug,\n required: true,\n }\n : null\n\n // Determine access: app override → owner-mode auto-wired → unrestricted\n const access =\n
|
|
1
|
+
{"version":3,"sources":["../../src/collections/Resources.ts"],"sourcesContent":["import type { CollectionConfig, CollectionSlug, Field } from 'payload'\n\nimport type { PluginT } from '../translations/index.js'\nimport type { ResolvedReservationPluginConfig } from '../types.js'\n\nimport { composeAccess, makeResourceOwnerAccess } from '../utilities/ownerAccess.js'\nimport { buildSelectOptions } from '../utilities/selectOptions.js'\n\n/**\n * Owner-field beforeChange logic, extracted for testability. On create the\n * owner defaults to the requesting user; on other operations the existing\n * value is preserved. Staff provisioning assigns the correct owner by creating\n * the Resource AS the new staff user (impersonation in the provisioning hook),\n * so this function needs no special-case branch.\n */\nexport function resolveOwnerValue({\n operation,\n req,\n value,\n}: {\n operation: string | undefined\n req: { user?: { id: unknown } | null }\n value: unknown\n}): unknown {\n if (operation === 'create' && req.user) {\n return req.user.id\n }\n return value\n}\n\nexport function createResourcesCollection(\n config: ResolvedReservationPluginConfig,\n): CollectionConfig {\n const rom = config.resourceOwnerMode\n const ownerField = rom?.ownerField ?? 'owner'\n // The owner relationship points to where owners/staff live: an explicit\n // ownerCollection, else the staff-provisioning user collection, else customers.\n // (Previously hardcoded to customers, which broke separate users/customers setups.)\n const ownerCollection =\n rom?.ownerCollection ?? config.staffProvisioning?.userCollection ?? config.slugs.customers\n\n // Build the owner field when resourceOwnerMode is enabled\n const ownerFieldDef: Field | null = rom\n ? {\n name: ownerField,\n type: 'relationship',\n admin: {\n position: 'sidebar',\n },\n hooks: {\n beforeChange: [\n ({ operation, req, value }) => resolveOwnerValue({ operation, req, value }),\n ],\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldOwner'),\n relationTo: ownerCollection as unknown as CollectionSlug,\n required: true,\n }\n : null\n\n // Determine access: app override → owner-mode auto-wired → unrestricted\n const access =\n composeAccess(rom ? makeResourceOwnerAccess(rom) : {}, config.access.resources)\n\n return {\n slug: config.slugs.resources,\n access,\n admin: {\n group: config.adminGroup,\n useAsTitle: 'name',\n },\n fields: [\n {\n name: 'name',\n type: 'text',\n label: ({ t }) => (t as PluginT)('reservation:fieldName'),\n ...(config.localized ? { localized: true } : {}),\n maxLength: 200,\n required: true,\n },\n ...(config.hasMediaCollection\n ? [\n {\n name: 'image',\n type: 'upload' as const,\n label: ({ t }: { t: unknown }) => (t as PluginT)('reservation:fieldImage'),\n relationTo: config.slugs.media as unknown as CollectionSlug,\n },\n ]\n : []),\n {\n name: 'description',\n type: 'textarea',\n label: ({ t }) => (t as PluginT)('reservation:fieldDescription'),\n ...(config.localized ? { localized: true } : {}),\n },\n {\n name: 'services',\n type: 'relationship',\n hasMany: true,\n label: ({ t }) => (t as PluginT)('reservation:fieldServices'),\n relationTo: config.slugs.services as unknown as CollectionSlug,\n },\n {\n name: 'active',\n type: 'checkbox',\n admin: {\n position: 'sidebar',\n },\n defaultValue: true,\n label: ({ t }) => (t as PluginT)('reservation:fieldActive'),\n },\n {\n name: 'quantity',\n type: 'number',\n admin: {\n position: 'sidebar',\n },\n defaultValue: 1,\n label: ({ t }) => (t as PluginT)('reservation:fieldQuantity'),\n min: 1,\n required: true,\n },\n {\n name: 'capacityMode',\n type: 'select',\n admin: {\n condition: (data) => (data?.quantity ?? 1) > 1,\n position: 'sidebar',\n },\n defaultValue: 'per-reservation',\n label: ({ t }) => (t as PluginT)('reservation:fieldCapacityMode'),\n options: [\n {\n label: ({ t }) => (t as PluginT)('reservation:capacityPerReservation'),\n value: 'per-reservation',\n },\n {\n label: ({ t }) => (t as PluginT)('reservation:capacityPerGuest'),\n value: 'per-guest',\n },\n ],\n },\n {\n name: 'timezone',\n type: 'text',\n admin: {\n description:\n 'Deprecated — not used by the plugin. Schedule resolution uses the plugin-level `timezone` option.',\n position: 'sidebar',\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldTimezone'),\n },\n {\n name: 'resourceType',\n type: 'select',\n admin: {\n position: 'sidebar',\n },\n defaultValue: config.resourceTypes[0],\n label: ({ t }) => (t as PluginT)('reservation:fieldResourceType'),\n options: buildSelectOptions(config.resourceTypes),\n },\n ...(ownerFieldDef ? [ownerFieldDef] : []),\n ],\n labels: {\n plural: ({ t }) => (t as PluginT)('reservation:collectionResources'),\n singular: ({ t }) => (t as PluginT)('reservation:collectionResources'),\n },\n }\n}\n"],"names":["composeAccess","makeResourceOwnerAccess","buildSelectOptions","resolveOwnerValue","operation","req","value","user","id","createResourcesCollection","config","rom","resourceOwnerMode","ownerField","ownerCollection","staffProvisioning","userCollection","slugs","customers","ownerFieldDef","name","type","admin","position","hooks","beforeChange","label","t","relationTo","required","access","resources","slug","group","adminGroup","useAsTitle","fields","localized","maxLength","hasMediaCollection","media","hasMany","services","defaultValue","min","condition","data","quantity","options","description","resourceTypes","labels","plural","singular"],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ValidationError } from 'payload';
|
|
2
|
-
import { makeScheduleOwnerAccess } from '../utilities/ownerAccess.js';
|
|
2
|
+
import { composeAccess, makeScheduleOwnerAccess } from '../utilities/ownerAccess.js';
|
|
3
3
|
import { buildSelectOptions } from '../utilities/selectOptions.js';
|
|
4
|
+
import { getDayKeyInTimezone } from '../utilities/timezoneUtils.js';
|
|
4
5
|
const TIME_REGEX = /^(?:[01]\d|2[0-3]):[0-5]\d$/;
|
|
5
6
|
const validateTime = (value)=>{
|
|
6
7
|
if (!value) {
|
|
@@ -10,7 +11,7 @@ const validateTime = (value)=>{
|
|
|
10
11
|
};
|
|
11
12
|
export function createSchedulesCollection(config) {
|
|
12
13
|
const rom = config.resourceOwnerMode;
|
|
13
|
-
const access =
|
|
14
|
+
const access = composeAccess(rom ? makeScheduleOwnerAccess(rom) : {}, config.access.schedules);
|
|
14
15
|
return {
|
|
15
16
|
slug: config.slugs.schedules,
|
|
16
17
|
access,
|
|
@@ -207,9 +208,14 @@ export function createSchedulesCollection(config) {
|
|
|
207
208
|
hooks: {
|
|
208
209
|
beforeValidate: [
|
|
209
210
|
({ data })=>{
|
|
211
|
+
// Only compare well-formed HH:mm values — otherwise a malformed time
|
|
212
|
+
// ('9:00') would lexically compare wrong and surface this misleading
|
|
213
|
+
// message instead of the field validator's format error.
|
|
214
|
+
const HHMM = /^\d{2}:\d{2}$/;
|
|
215
|
+
const ordered = (start, end)=>!start || !end || !HHMM.test(start) || !HHMM.test(end) || start < end;
|
|
210
216
|
const slots = data?.recurringSlots ?? [];
|
|
211
217
|
for (const slot of slots){
|
|
212
|
-
if (slot.startTime
|
|
218
|
+
if (!ordered(slot.startTime, slot.endTime)) {
|
|
213
219
|
throw new ValidationError({
|
|
214
220
|
errors: [
|
|
215
221
|
{
|
|
@@ -222,7 +228,7 @@ export function createSchedulesCollection(config) {
|
|
|
222
228
|
}
|
|
223
229
|
const manual = data?.manualSlots ?? [];
|
|
224
230
|
for (const slot of manual){
|
|
225
|
-
if (slot.startTime
|
|
231
|
+
if (!ordered(slot.startTime, slot.endTime)) {
|
|
226
232
|
throw new ValidationError({
|
|
227
233
|
errors: [
|
|
228
234
|
{
|
|
@@ -236,8 +242,8 @@ export function createSchedulesCollection(config) {
|
|
|
236
242
|
const exceptions = data?.exceptions ?? [];
|
|
237
243
|
for (const exc of exceptions){
|
|
238
244
|
if (exc.date && exc.endDate) {
|
|
239
|
-
const start = new Date(exc.date).
|
|
240
|
-
const end = new Date(exc.endDate).
|
|
245
|
+
const start = getDayKeyInTimezone(new Date(exc.date), config.timezone);
|
|
246
|
+
const end = getDayKeyInTimezone(new Date(exc.endDate), config.timezone);
|
|
241
247
|
if (end < start) {
|
|
242
248
|
throw new ValidationError({
|
|
243
249
|
errors: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/collections/Schedules.ts"],"sourcesContent":["import type { CollectionConfig, CollectionSlug } from 'payload'\n\nimport { ValidationError } from 'payload'\n\nimport type { PluginT } from '../translations/index.js'\nimport type { ResolvedReservationPluginConfig } from '../types.js'\n\nimport { makeScheduleOwnerAccess } from '../utilities/ownerAccess.js'\nimport { buildSelectOptions } from '../utilities/selectOptions.js'\n\nconst TIME_REGEX = /^(?:[01]\\d|2[0-3]):[0-5]\\d$/\n\nconst validateTime = (value: null | string | undefined): string | true => {\n if (!value) { return true } // required handles emptiness\n return TIME_REGEX.test(value) || 'Invalid time format. Use HH:mm (e.g., 09:00, 17:30)'\n}\n\nexport function createSchedulesCollection(\n config: ResolvedReservationPluginConfig,\n): CollectionConfig {\n const rom = config.resourceOwnerMode\n const access =\n config.access.schedules ?? (rom ? makeScheduleOwnerAccess(rom) : {})\n\n return {\n slug: config.slugs.schedules,\n access,\n admin: {\n group: config.adminGroup,\n useAsTitle: 'name',\n },\n fields: [\n {\n name: 'name',\n type: 'text',\n label: ({ t }) => (t as PluginT)('reservation:fieldName'),\n required: true,\n },\n {\n name: 'resource',\n type: 'relationship',\n label: ({ t }) => (t as PluginT)('reservation:fieldResource'),\n relationTo: config.slugs.resources as unknown as CollectionSlug,\n required: true,\n },\n {\n name: 'scheduleType',\n type: 'select',\n defaultValue: 'recurring',\n label: ({ t }) => (t as PluginT)('reservation:fieldScheduleType'),\n options: [\n {\n label: ({ t }) => (t as PluginT)('reservation:scheduleTypeRecurring'),\n value: 'recurring',\n },\n {\n label: ({ t }) => (t as PluginT)('reservation:scheduleTypeManual'),\n value: 'manual',\n },\n ],\n },\n {\n name: 'recurringSlots',\n type: 'array',\n admin: {\n condition: (_, siblingData) => siblingData?.scheduleType === 'recurring',\n },\n fields: [\n {\n name: 'day',\n type: 'select',\n label: ({ t }) => (t as PluginT)('reservation:fieldDay'),\n options: [\n { label: ({ t }) => (t as PluginT)('reservation:dayMonday'), value: 'mon' },\n { label: ({ t }) => (t as PluginT)('reservation:dayTuesday'), value: 'tue' },\n { label: ({ t }) => (t as PluginT)('reservation:dayWednesday'), value: 'wed' },\n { label: ({ t }) => (t as PluginT)('reservation:dayThursday'), value: 'thu' },\n { label: ({ t }) => (t as PluginT)('reservation:dayFriday'), value: 'fri' },\n { label: ({ t }) => (t as PluginT)('reservation:daySaturday'), value: 'sat' },\n { label: ({ t }) => (t as PluginT)('reservation:daySunday'), value: 'sun' },\n ],\n required: true,\n },\n {\n name: 'startTime',\n type: 'text',\n admin: {\n placeholder: '09:00',\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldStartTimeHHmm'),\n required: true,\n validate: validateTime,\n },\n {\n name: 'endTime',\n type: 'text',\n admin: {\n placeholder: '17:00',\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldEndTimeHHmm'),\n required: true,\n validate: validateTime,\n },\n ],\n label: ({ t }) => (t as PluginT)('reservation:fieldRecurringSlots'),\n },\n {\n name: 'manualSlots',\n type: 'array',\n admin: {\n condition: (_, siblingData) => siblingData?.scheduleType === 'manual',\n },\n fields: [\n {\n name: 'date',\n type: 'date',\n admin: {\n date: {\n pickerAppearance: 'dayOnly',\n },\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldDate'),\n required: true,\n },\n {\n name: 'startTime',\n type: 'text',\n admin: {\n placeholder: '09:00',\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldStartTimeHHmm'),\n required: true,\n validate: validateTime,\n },\n {\n name: 'endTime',\n type: 'text',\n admin: {\n placeholder: '17:00',\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldEndTimeHHmm'),\n required: true,\n validate: validateTime,\n },\n ],\n label: ({ t }) => (t as PluginT)('reservation:fieldManualSlots'),\n },\n {\n name: 'exceptions',\n type: 'array',\n fields: [\n {\n name: 'date',\n type: 'date',\n admin: { date: { pickerAppearance: 'dayOnly' } },\n label: ({ t }) => (t as PluginT)('reservation:fieldDate'),\n required: true,\n },\n {\n name: 'endDate',\n type: 'date',\n admin: { date: { pickerAppearance: 'dayOnly' } },\n label: ({ t }) => (t as PluginT)('reservation:fieldEndDate'),\n },\n {\n name: 'type',\n type: 'select',\n label: ({ t }) => (t as PluginT)('reservation:fieldLeaveType'),\n options: buildSelectOptions(config.leaveTypes),\n },\n {\n name: 'reason',\n type: 'text',\n label: ({ t }) => (t as PluginT)('reservation:fieldReason'),\n },\n ],\n label: ({ t }) => (t as PluginT)('reservation:fieldExceptions'),\n },\n {\n name: 'active',\n type: 'checkbox',\n admin: {\n position: 'sidebar',\n },\n defaultValue: true,\n label: ({ t }) => (t as PluginT)('reservation:fieldActive'),\n },\n ],\n hooks: {\n beforeValidate: [\n ({ data }) => {\n const slots = (data?.recurringSlots as Array<{ endTime?: string; startTime?: string }>) ?? []\n for (const slot of slots) {\n if (slot.startTime && slot.endTime && slot.startTime >= slot.endTime) {\n throw new ValidationError({\n errors: [{ message: 'endTime must be after startTime', path: 'recurringSlots' }],\n })\n }\n }\n const manual = (data?.manualSlots as Array<{ endTime?: string; startTime?: string }>) ?? []\n for (const slot of manual) {\n if (slot.startTime && slot.endTime && slot.startTime >= slot.endTime) {\n throw new ValidationError({\n errors: [{ message: 'endTime must be after startTime', path: 'manualSlots' }],\n })\n }\n }\n const exceptions = (data?.exceptions as Array<{ date?: string; endDate?: string }>) ?? []\n for (const exc of exceptions) {\n if (exc.date && exc.endDate) {\n const start = new Date(exc.date).toISOString().split('T')[0]\n const end = new Date(exc.endDate).toISOString().split('T')[0]\n if (end < start) {\n throw new ValidationError({\n errors: [{ message: 'exception endDate must be on or after date', path: 'exceptions' }],\n })\n }\n }\n }\n return data\n },\n ],\n },\n labels: {\n plural: ({ t }) => (t as PluginT)('reservation:collectionSchedules'),\n singular: ({ t }) => (t as PluginT)('reservation:collectionSchedules'),\n },\n }\n}\n"],"names":["ValidationError","makeScheduleOwnerAccess","buildSelectOptions","TIME_REGEX","validateTime","value","test","createSchedulesCollection","config","rom","resourceOwnerMode","access","schedules","slug","slugs","admin","group","adminGroup","useAsTitle","fields","name","type","label","t","required","relationTo","resources","defaultValue","options","condition","_","siblingData","scheduleType","placeholder","validate","date","pickerAppearance","leaveTypes","position","hooks","beforeValidate","data","slots","recurringSlots","slot","startTime","endTime","errors","message","path","manual","manualSlots","exceptions","exc","endDate","start","Date","toISOString","split","end","labels","plural","singular"],"mappings":"AAEA,SAASA,eAAe,QAAQ,UAAS;AAKzC,SAASC,uBAAuB,QAAQ,8BAA6B;AACrE,SAASC,kBAAkB,QAAQ,gCAA+B;AAElE,MAAMC,aAAa;AAEnB,MAAMC,eAAe,CAACC;IACpB,IAAI,CAACA,OAAO;QAAE,OAAO;IAAK,EAAE,6BAA6B;IACzD,OAAOF,WAAWG,IAAI,CAACD,UAAU;AACnC;AAEA,OAAO,SAASE,0BACdC,MAAuC;IAEvC,MAAMC,MAAMD,OAAOE,iBAAiB;IACpC,MAAMC,SACJH,OAAOG,MAAM,CAACC,SAAS,IAAKH,CAAAA,MAAMR,wBAAwBQ,OAAO,CAAC,CAAA;IAEpE,OAAO;QACLI,MAAML,OAAOM,KAAK,CAACF,SAAS;QAC5BD;QACAI,OAAO;YACLC,OAAOR,OAAOS,UAAU;YACxBC,YAAY;QACd;QACAC,QAAQ;YACN;gBACEC,MAAM;gBACNC,MAAM;gBACNC,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;gBACjCC,UAAU;YACZ;YACA;gBACEJ,MAAM;gBACNC,MAAM;gBACNC,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;gBACjCE,YAAYjB,OAAOM,KAAK,CAACY,SAAS;gBAClCF,UAAU;YACZ;YACA;gBACEJ,MAAM;gBACNC,MAAM;gBACNM,cAAc;gBACdL,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;gBACjCK,SAAS;oBACP;wBACEN,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;wBACjClB,OAAO;oBACT;oBACA;wBACEiB,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;wBACjClB,OAAO;oBACT;iBACD;YACH;YACA;gBACEe,MAAM;gBACNC,MAAM;gBACNN,OAAO;oBACLc,WAAW,CAACC,GAAGC,cAAgBA,aAAaC,iBAAiB;gBAC/D;gBACAb,QAAQ;oBACN;wBACEC,MAAM;wBACNC,MAAM;wBACNC,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;wBACjCK,SAAS;4BACP;gCAAEN,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;gCAA0BlB,OAAO;4BAAM;4BAC1E;gCAAEiB,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;gCAA2BlB,OAAO;4BAAM;4BAC3E;gCAAEiB,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;gCAA6BlB,OAAO;4BAAM;4BAC7E;gCAAEiB,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;gCAA4BlB,OAAO;4BAAM;4BAC5E;gCAAEiB,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;gCAA0BlB,OAAO;4BAAM;4BAC1E;gCAAEiB,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;gCAA4BlB,OAAO;4BAAM;4BAC5E;gCAAEiB,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;gCAA0BlB,OAAO;4BAAM;yBAC3E;wBACDmB,UAAU;oBACZ;oBACA;wBACEJ,MAAM;wBACNC,MAAM;wBACNN,OAAO;4BACLkB,aAAa;wBACf;wBACAX,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;wBACjCC,UAAU;wBACVU,UAAU9B;oBACZ;oBACA;wBACEgB,MAAM;wBACNC,MAAM;wBACNN,OAAO;4BACLkB,aAAa;wBACf;wBACAX,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;wBACjCC,UAAU;wBACVU,UAAU9B;oBACZ;iBACD;gBACDkB,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;YACnC;YACA;gBACEH,MAAM;gBACNC,MAAM;gBACNN,OAAO;oBACLc,WAAW,CAACC,GAAGC,cAAgBA,aAAaC,iBAAiB;gBAC/D;gBACAb,QAAQ;oBACN;wBACEC,MAAM;wBACNC,MAAM;wBACNN,OAAO;4BACLoB,MAAM;gCACJC,kBAAkB;4BACpB;wBACF;wBACAd,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;wBACjCC,UAAU;oBACZ;oBACA;wBACEJ,MAAM;wBACNC,MAAM;wBACNN,OAAO;4BACLkB,aAAa;wBACf;wBACAX,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;wBACjCC,UAAU;wBACVU,UAAU9B;oBACZ;oBACA;wBACEgB,MAAM;wBACNC,MAAM;wBACNN,OAAO;4BACLkB,aAAa;wBACf;wBACAX,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;wBACjCC,UAAU;wBACVU,UAAU9B;oBACZ;iBACD;gBACDkB,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;YACnC;YACA;gBACEH,MAAM;gBACNC,MAAM;gBACNF,QAAQ;oBACN;wBACEC,MAAM;wBACNC,MAAM;wBACNN,OAAO;4BAAEoB,MAAM;gCAAEC,kBAAkB;4BAAU;wBAAE;wBAC/Cd,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;wBACjCC,UAAU;oBACZ;oBACA;wBACEJ,MAAM;wBACNC,MAAM;wBACNN,OAAO;4BAAEoB,MAAM;gCAAEC,kBAAkB;4BAAU;wBAAE;wBAC/Cd,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;oBACnC;oBACA;wBACEH,MAAM;wBACNC,MAAM;wBACNC,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;wBACjCK,SAAS1B,mBAAmBM,OAAO6B,UAAU;oBAC/C;oBACA;wBACEjB,MAAM;wBACNC,MAAM;wBACNC,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;oBACnC;iBACD;gBACDD,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;YACnC;YACA;gBACEH,MAAM;gBACNC,MAAM;gBACNN,OAAO;oBACLuB,UAAU;gBACZ;gBACAX,cAAc;gBACdL,OAAO,CAAC,EAAEC,CAAC,EAAE,GAAK,AAACA,EAAc;YACnC;SACD;QACDgB,OAAO;YACLC,gBAAgB;gBACd,CAAC,EAAEC,IAAI,EAAE;oBACP,MAAMC,QAAQ,AAACD,MAAME,kBAAsE,EAAE;oBAC7F,KAAK,MAAMC,QAAQF,MAAO;wBACxB,IAAIE,KAAKC,SAAS,IAAID,KAAKE,OAAO,IAAIF,KAAKC,SAAS,IAAID,KAAKE,OAAO,EAAE;4BACpE,MAAM,IAAI9C,gBAAgB;gCACxB+C,QAAQ;oCAAC;wCAAEC,SAAS;wCAAmCC,MAAM;oCAAiB;iCAAE;4BAClF;wBACF;oBACF;oBACA,MAAMC,SAAS,AAACT,MAAMU,eAAmE,EAAE;oBAC3F,KAAK,MAAMP,QAAQM,OAAQ;wBACzB,IAAIN,KAAKC,SAAS,IAAID,KAAKE,OAAO,IAAIF,KAAKC,SAAS,IAAID,KAAKE,OAAO,EAAE;4BACpE,MAAM,IAAI9C,gBAAgB;gCACxB+C,QAAQ;oCAAC;wCAAEC,SAAS;wCAAmCC,MAAM;oCAAc;iCAAE;4BAC/E;wBACF;oBACF;oBACA,MAAMG,aAAa,AAACX,MAAMW,cAA6D,EAAE;oBACzF,KAAK,MAAMC,OAAOD,WAAY;wBAC5B,IAAIC,IAAIlB,IAAI,IAAIkB,IAAIC,OAAO,EAAE;4BAC3B,MAAMC,QAAQ,IAAIC,KAAKH,IAAIlB,IAAI,EAAEsB,WAAW,GAAGC,KAAK,CAAC,IAAI,CAAC,EAAE;4BAC5D,MAAMC,MAAM,IAAIH,KAAKH,IAAIC,OAAO,EAAEG,WAAW,GAAGC,KAAK,CAAC,IAAI,CAAC,EAAE;4BAC7D,IAAIC,MAAMJ,OAAO;gCACf,MAAM,IAAIvD,gBAAgB;oCACxB+C,QAAQ;wCAAC;4CAAEC,SAAS;4CAA8CC,MAAM;wCAAa;qCAAE;gCACzF;4BACF;wBACF;oBACF;oBACA,OAAOR;gBACT;aACD;QACH;QACAmB,QAAQ;YACNC,QAAQ,CAAC,EAAEtC,CAAC,EAAE,GAAK,AAACA,EAAc;YAClCuC,UAAU,CAAC,EAAEvC,CAAC,EAAE,GAAK,AAACA,EAAc;QACtC;IACF;AACF"}
|
|
1
|
+
{"version":3,"sources":["../../src/collections/Schedules.ts"],"sourcesContent":["import type { CollectionConfig, CollectionSlug } from 'payload'\n\nimport { ValidationError } from 'payload'\n\nimport type { PluginT } from '../translations/index.js'\nimport type { ResolvedReservationPluginConfig } from '../types.js'\n\nimport { composeAccess, makeScheduleOwnerAccess } from '../utilities/ownerAccess.js'\nimport { buildSelectOptions } from '../utilities/selectOptions.js'\nimport { getDayKeyInTimezone } from '../utilities/timezoneUtils.js'\n\nconst TIME_REGEX = /^(?:[01]\\d|2[0-3]):[0-5]\\d$/\n\nconst validateTime = (value: null | string | undefined): string | true => {\n if (!value) { return true } // required handles emptiness\n return TIME_REGEX.test(value) || 'Invalid time format. Use HH:mm (e.g., 09:00, 17:30)'\n}\n\nexport function createSchedulesCollection(\n config: ResolvedReservationPluginConfig,\n): CollectionConfig {\n const rom = config.resourceOwnerMode\n const access =\n composeAccess(rom ? makeScheduleOwnerAccess(rom) : {}, config.access.schedules)\n\n return {\n slug: config.slugs.schedules,\n access,\n admin: {\n group: config.adminGroup,\n useAsTitle: 'name',\n },\n fields: [\n {\n name: 'name',\n type: 'text',\n label: ({ t }) => (t as PluginT)('reservation:fieldName'),\n required: true,\n },\n {\n name: 'resource',\n type: 'relationship',\n label: ({ t }) => (t as PluginT)('reservation:fieldResource'),\n relationTo: config.slugs.resources as unknown as CollectionSlug,\n required: true,\n },\n {\n name: 'scheduleType',\n type: 'select',\n defaultValue: 'recurring',\n label: ({ t }) => (t as PluginT)('reservation:fieldScheduleType'),\n options: [\n {\n label: ({ t }) => (t as PluginT)('reservation:scheduleTypeRecurring'),\n value: 'recurring',\n },\n {\n label: ({ t }) => (t as PluginT)('reservation:scheduleTypeManual'),\n value: 'manual',\n },\n ],\n },\n {\n name: 'recurringSlots',\n type: 'array',\n admin: {\n condition: (_, siblingData) => siblingData?.scheduleType === 'recurring',\n },\n fields: [\n {\n name: 'day',\n type: 'select',\n label: ({ t }) => (t as PluginT)('reservation:fieldDay'),\n options: [\n { label: ({ t }) => (t as PluginT)('reservation:dayMonday'), value: 'mon' },\n { label: ({ t }) => (t as PluginT)('reservation:dayTuesday'), value: 'tue' },\n { label: ({ t }) => (t as PluginT)('reservation:dayWednesday'), value: 'wed' },\n { label: ({ t }) => (t as PluginT)('reservation:dayThursday'), value: 'thu' },\n { label: ({ t }) => (t as PluginT)('reservation:dayFriday'), value: 'fri' },\n { label: ({ t }) => (t as PluginT)('reservation:daySaturday'), value: 'sat' },\n { label: ({ t }) => (t as PluginT)('reservation:daySunday'), value: 'sun' },\n ],\n required: true,\n },\n {\n name: 'startTime',\n type: 'text',\n admin: {\n placeholder: '09:00',\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldStartTimeHHmm'),\n required: true,\n validate: validateTime,\n },\n {\n name: 'endTime',\n type: 'text',\n admin: {\n placeholder: '17:00',\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldEndTimeHHmm'),\n required: true,\n validate: validateTime,\n },\n ],\n label: ({ t }) => (t as PluginT)('reservation:fieldRecurringSlots'),\n },\n {\n name: 'manualSlots',\n type: 'array',\n admin: {\n condition: (_, siblingData) => siblingData?.scheduleType === 'manual',\n },\n fields: [\n {\n name: 'date',\n type: 'date',\n admin: {\n date: {\n pickerAppearance: 'dayOnly',\n },\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldDate'),\n required: true,\n },\n {\n name: 'startTime',\n type: 'text',\n admin: {\n placeholder: '09:00',\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldStartTimeHHmm'),\n required: true,\n validate: validateTime,\n },\n {\n name: 'endTime',\n type: 'text',\n admin: {\n placeholder: '17:00',\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldEndTimeHHmm'),\n required: true,\n validate: validateTime,\n },\n ],\n label: ({ t }) => (t as PluginT)('reservation:fieldManualSlots'),\n },\n {\n name: 'exceptions',\n type: 'array',\n fields: [\n {\n name: 'date',\n type: 'date',\n admin: { date: { pickerAppearance: 'dayOnly' } },\n label: ({ t }) => (t as PluginT)('reservation:fieldDate'),\n required: true,\n },\n {\n name: 'endDate',\n type: 'date',\n admin: { date: { pickerAppearance: 'dayOnly' } },\n label: ({ t }) => (t as PluginT)('reservation:fieldEndDate'),\n },\n {\n name: 'type',\n type: 'select',\n label: ({ t }) => (t as PluginT)('reservation:fieldLeaveType'),\n options: buildSelectOptions(config.leaveTypes),\n },\n {\n name: 'reason',\n type: 'text',\n label: ({ t }) => (t as PluginT)('reservation:fieldReason'),\n },\n ],\n label: ({ t }) => (t as PluginT)('reservation:fieldExceptions'),\n },\n {\n name: 'active',\n type: 'checkbox',\n admin: {\n position: 'sidebar',\n },\n defaultValue: true,\n label: ({ t }) => (t as PluginT)('reservation:fieldActive'),\n },\n ],\n hooks: {\n beforeValidate: [\n ({ data }) => {\n // Only compare well-formed HH:mm values — otherwise a malformed time\n // ('9:00') would lexically compare wrong and surface this misleading\n // message instead of the field validator's format error.\n const HHMM = /^\\d{2}:\\d{2}$/\n const ordered = (start?: string, end?: string) =>\n !start || !end || !HHMM.test(start) || !HHMM.test(end) || start < end\n const slots = (data?.recurringSlots as Array<{ endTime?: string; startTime?: string }>) ?? []\n for (const slot of slots) {\n if (!ordered(slot.startTime, slot.endTime)) {\n throw new ValidationError({\n errors: [{ message: 'endTime must be after startTime', path: 'recurringSlots' }],\n })\n }\n }\n const manual = (data?.manualSlots as Array<{ endTime?: string; startTime?: string }>) ?? []\n for (const slot of manual) {\n if (!ordered(slot.startTime, slot.endTime)) {\n throw new ValidationError({\n errors: [{ message: 'endTime must be after startTime', path: 'manualSlots' }],\n })\n }\n }\n const exceptions = (data?.exceptions as Array<{ date?: string; endDate?: string }>) ?? []\n for (const exc of exceptions) {\n if (exc.date && exc.endDate) {\n const start = getDayKeyInTimezone(new Date(exc.date), config.timezone)\n const end = getDayKeyInTimezone(new Date(exc.endDate), config.timezone)\n if (end < start) {\n throw new ValidationError({\n errors: [{ message: 'exception endDate must be on or after date', path: 'exceptions' }],\n })\n }\n }\n }\n return data\n },\n ],\n },\n labels: {\n plural: ({ t }) => (t as PluginT)('reservation:collectionSchedules'),\n singular: ({ t }) => (t as PluginT)('reservation:collectionSchedules'),\n },\n }\n}\n"],"names":["ValidationError","composeAccess","makeScheduleOwnerAccess","buildSelectOptions","getDayKeyInTimezone","TIME_REGEX","validateTime","value","test","createSchedulesCollection","config","rom","resourceOwnerMode","access","schedules","slug","slugs","admin","group","adminGroup","useAsTitle","fields","name","type","label","t","required","relationTo","resources","defaultValue","options","condition","_","siblingData","scheduleType","placeholder","validate","date","pickerAppearance","leaveTypes","position","hooks","beforeValidate","data","HHMM","ordered","start","end","slots","recurringSlots","slot","startTime","endTime","errors","message","path","manual","manualSlots","exceptions","exc","endDate","Date","timezone","labels","plural","singular"],"mappings":"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"}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { makeServiceOwnerAccess } from '../utilities/ownerAccess.js';
|
|
1
|
+
import { composeAccess, makeServiceOwnerAccess } from '../utilities/ownerAccess.js';
|
|
2
2
|
export function createServicesCollection(config) {
|
|
3
3
|
const rom = config.resourceOwnerMode;
|
|
4
4
|
const ownedServices = rom?.ownedServices ?? false;
|
|
5
5
|
const ownerField = rom?.ownerField ?? 'owner';
|
|
6
|
+
// Owner relationship points where owners/staff live — same resolution as
|
|
7
|
+
// Resources; previously hardcoded to customers, breaking separate user setups.
|
|
8
|
+
const ownerCollection = rom?.ownerCollection ?? config.staffProvisioning?.userCollection ?? config.slugs.customers;
|
|
6
9
|
// Owner field on Services (only when ownedServices: true)
|
|
7
10
|
const ownerFieldDef = rom && ownedServices ? {
|
|
8
11
|
name: ownerField,
|
|
@@ -21,11 +24,11 @@ export function createServicesCollection(config) {
|
|
|
21
24
|
]
|
|
22
25
|
},
|
|
23
26
|
label: ({ t })=>t('reservation:fieldOwner'),
|
|
24
|
-
relationTo:
|
|
27
|
+
relationTo: ownerCollection,
|
|
25
28
|
required: true
|
|
26
29
|
} : null;
|
|
27
|
-
//
|
|
28
|
-
const access =
|
|
30
|
+
// Owner-mode base rules with any app overrides composed on top (per operation)
|
|
31
|
+
const access = composeAccess(rom && ownedServices ? makeServiceOwnerAccess(rom, ownerField) : {}, config.access.services);
|
|
29
32
|
return {
|
|
30
33
|
slug: config.slugs.services,
|
|
31
34
|
access,
|
|
@@ -44,12 +47,14 @@ export function createServicesCollection(config) {
|
|
|
44
47
|
maxLength: 200,
|
|
45
48
|
required: true
|
|
46
49
|
},
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
...config.hasMediaCollection ? [
|
|
51
|
+
{
|
|
52
|
+
name: 'image',
|
|
53
|
+
type: 'upload',
|
|
54
|
+
label: ({ t })=>t('reservation:fieldImage'),
|
|
55
|
+
relationTo: config.slugs.media
|
|
56
|
+
}
|
|
57
|
+
] : [],
|
|
53
58
|
{
|
|
54
59
|
name: 'description',
|
|
55
60
|
type: 'textarea',
|
|
@@ -96,10 +101,13 @@ export function createServicesCollection(config) {
|
|
|
96
101
|
min: 0
|
|
97
102
|
},
|
|
98
103
|
{
|
|
104
|
+
// max < 24h keeps the conflict-detection coarse pre-filter
|
|
105
|
+
// (COARSE_MARGIN_MS = 24h) provably sufficient.
|
|
99
106
|
name: 'bufferTimeBefore',
|
|
100
107
|
type: 'number',
|
|
101
108
|
defaultValue: 0,
|
|
102
109
|
label: ({ t })=>t('reservation:fieldBufferTimeBefore'),
|
|
110
|
+
max: 1439,
|
|
103
111
|
min: 0
|
|
104
112
|
},
|
|
105
113
|
{
|
|
@@ -107,6 +115,7 @@ export function createServicesCollection(config) {
|
|
|
107
115
|
type: 'number',
|
|
108
116
|
defaultValue: 0,
|
|
109
117
|
label: ({ t })=>t('reservation:fieldBufferTimeAfter'),
|
|
118
|
+
max: 1439,
|
|
110
119
|
min: 0
|
|
111
120
|
},
|
|
112
121
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/collections/Services.ts"],"sourcesContent":["import type { CollectionConfig, CollectionSlug, Field } from 'payload'\n\nimport type { PluginT } from '../translations/index.js'\nimport type { ResolvedReservationPluginConfig } from '../types.js'\n\nimport { makeServiceOwnerAccess } from '../utilities/ownerAccess.js'\n\nexport function createServicesCollection(config: ResolvedReservationPluginConfig): CollectionConfig {\n const rom = config.resourceOwnerMode\n const ownedServices = rom?.ownedServices ?? false\n const ownerField = rom?.ownerField ?? 'owner'\n\n // Owner field on Services (only when ownedServices: true)\n const ownerFieldDef: Field | null =\n rom && ownedServices\n ? {\n name: ownerField,\n type: 'relationship',\n admin: {\n position: 'sidebar',\n },\n hooks: {\n beforeChange: [\n ({ operation, req, value }) => {\n if (operation === 'create' && req.user) {return req.user.id}\n return value\n },\n ],\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldOwner'),\n relationTo:
|
|
1
|
+
{"version":3,"sources":["../../src/collections/Services.ts"],"sourcesContent":["import type { CollectionConfig, CollectionSlug, Field } from 'payload'\n\nimport type { PluginT } from '../translations/index.js'\nimport type { ResolvedReservationPluginConfig } from '../types.js'\n\nimport { composeAccess, makeServiceOwnerAccess } from '../utilities/ownerAccess.js'\n\nexport function createServicesCollection(config: ResolvedReservationPluginConfig): CollectionConfig {\n const rom = config.resourceOwnerMode\n const ownedServices = rom?.ownedServices ?? false\n const ownerField = rom?.ownerField ?? 'owner'\n // Owner relationship points where owners/staff live — same resolution as\n // Resources; previously hardcoded to customers, breaking separate user setups.\n const ownerCollection =\n rom?.ownerCollection ?? config.staffProvisioning?.userCollection ?? config.slugs.customers\n\n // Owner field on Services (only when ownedServices: true)\n const ownerFieldDef: Field | null =\n rom && ownedServices\n ? {\n name: ownerField,\n type: 'relationship',\n admin: {\n position: 'sidebar',\n },\n hooks: {\n beforeChange: [\n ({ operation, req, value }) => {\n if (operation === 'create' && req.user) {return req.user.id}\n return value\n },\n ],\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldOwner'),\n relationTo: ownerCollection as unknown as CollectionSlug,\n required: true,\n }\n : null\n\n // Owner-mode base rules with any app overrides composed on top (per operation)\n const access = composeAccess(\n rom && ownedServices ? makeServiceOwnerAccess(rom, ownerField) : {},\n config.access.services,\n )\n\n return {\n slug: config.slugs.services,\n access,\n admin: {\n group: config.adminGroup,\n useAsTitle: 'name',\n },\n fields: [\n {\n name: 'name',\n type: 'text',\n label: ({ t }) => (t as PluginT)('reservation:fieldName'),\n ...(config.localized ? { localized: true } : {}),\n maxLength: 200,\n required: true,\n },\n ...(config.hasMediaCollection\n ? [\n {\n name: 'image',\n type: 'upload' as const,\n label: ({ t }: { t: unknown }) => (t as PluginT)('reservation:fieldImage'),\n relationTo: config.slugs.media as unknown as CollectionSlug,\n },\n ]\n : []),\n {\n name: 'description',\n type: 'textarea',\n label: ({ t }) => (t as PluginT)('reservation:fieldDescription'),\n ...(config.localized ? { localized: true } : {}),\n },\n {\n name: 'duration',\n type: 'number',\n label: ({ t }) => (t as PluginT)('reservation:fieldDurationMinutes'),\n min: 1,\n required: true,\n },\n {\n name: 'durationType',\n type: 'select',\n defaultValue: 'fixed',\n label: ({ t }) => (t as PluginT)('reservation:fieldDurationType'),\n options: [\n {\n label: ({ t }) => (t as PluginT)('reservation:durationFixed'),\n value: 'fixed',\n },\n {\n label: ({ t }) => (t as PluginT)('reservation:durationFlexible'),\n value: 'flexible',\n },\n {\n label: ({ t }) => (t as PluginT)('reservation:durationFullDay'),\n value: 'full-day',\n },\n ],\n required: true,\n },\n {\n name: 'price',\n type: 'number',\n admin: {\n step: 0.01,\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldPrice'),\n min: 0,\n },\n {\n // max < 24h keeps the conflict-detection coarse pre-filter\n // (COARSE_MARGIN_MS = 24h) provably sufficient.\n name: 'bufferTimeBefore',\n type: 'number',\n defaultValue: 0,\n label: ({ t }) => (t as PluginT)('reservation:fieldBufferTimeBefore'),\n max: 1439,\n min: 0,\n },\n {\n name: 'bufferTimeAfter',\n type: 'number',\n defaultValue: 0,\n label: ({ t }) => (t as PluginT)('reservation:fieldBufferTimeAfter'),\n max: 1439,\n min: 0,\n },\n {\n name: 'requiredResources',\n type: 'relationship',\n admin: {\n description: ({ t }) => (t as PluginT)('reservation:fieldRequiredResourcesDesc'),\n },\n hasMany: true,\n label: ({ t }) => (t as PluginT)('reservation:fieldRequiredResources'),\n relationTo: config.slugs.resources as unknown as CollectionSlug,\n },\n {\n name: 'allowGuestBooking',\n type: 'select',\n admin: {\n description: ({ t }) => (t as PluginT)('reservation:fieldAllowGuestBookingDesc'),\n },\n defaultValue: 'inherit',\n label: ({ t }) => (t as PluginT)('reservation:fieldAllowGuestBooking'),\n options: [\n {\n label: ({ t }) => (t as PluginT)('reservation:guestBookingInherit'),\n value: 'inherit',\n },\n {\n label: ({ t }) => (t as PluginT)('reservation:guestBookingEnabled'),\n value: 'enabled',\n },\n {\n label: ({ t }) => (t as PluginT)('reservation:guestBookingDisabled'),\n value: 'disabled',\n },\n ],\n },\n {\n name: 'active',\n type: 'checkbox',\n admin: {\n position: 'sidebar',\n },\n defaultValue: true,\n label: ({ t }) => (t as PluginT)('reservation:fieldActive'),\n },\n ...(ownerFieldDef ? [ownerFieldDef] : []),\n ],\n labels: {\n plural: ({ t }) => (t as PluginT)('reservation:collectionServices'),\n singular: ({ t }) => (t as PluginT)('reservation:collectionServices'),\n },\n }\n}\n"],"names":["composeAccess","makeServiceOwnerAccess","createServicesCollection","config","rom","resourceOwnerMode","ownedServices","ownerField","ownerCollection","staffProvisioning","userCollection","slugs","customers","ownerFieldDef","name","type","admin","position","hooks","beforeChange","operation","req","value","user","id","label","t","relationTo","required","access","services","slug","group","adminGroup","useAsTitle","fields","localized","maxLength","hasMediaCollection","media","min","defaultValue","options","step","max","description","hasMany","resources","labels","plural","singular"],"mappings":"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"}
|