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.
Files changed (95) hide show
  1. package/README.md +40 -3
  2. package/dist/collections/Reservations.js +19 -7
  3. package/dist/collections/Reservations.js.map +1 -1
  4. package/dist/collections/Resources.js +11 -8
  5. package/dist/collections/Resources.js.map +1 -1
  6. package/dist/collections/Schedules.js +12 -6
  7. package/dist/collections/Schedules.js.map +1 -1
  8. package/dist/collections/Services.js +19 -10
  9. package/dist/collections/Services.js.map +1 -1
  10. package/dist/components/AvailabilityOverview/index.js +70 -26
  11. package/dist/components/AvailabilityOverview/index.js.map +1 -1
  12. package/dist/components/CalendarView/CalendarView.module.css +9 -0
  13. package/dist/components/CalendarView/LaneTimelineView.d.ts +4 -1
  14. package/dist/components/CalendarView/LaneTimelineView.js +17 -12
  15. package/dist/components/CalendarView/LaneTimelineView.js.map +1 -1
  16. package/dist/components/CalendarView/index.js +154 -53
  17. package/dist/components/CalendarView/index.js.map +1 -1
  18. package/dist/components/CustomerField/index.js +8 -3
  19. package/dist/components/CustomerField/index.js.map +1 -1
  20. package/dist/components/DashboardWidget/DashboardWidgetServer.js +97 -21
  21. package/dist/components/DashboardWidget/DashboardWidgetServer.js.map +1 -1
  22. package/dist/defaults.js +46 -8
  23. package/dist/defaults.js.map +1 -1
  24. package/dist/endpoints/cancelBooking.js +1 -1
  25. package/dist/endpoints/cancelBooking.js.map +1 -1
  26. package/dist/endpoints/checkAvailability.js +56 -7
  27. package/dist/endpoints/checkAvailability.js.map +1 -1
  28. package/dist/endpoints/createBooking.js +19 -10
  29. package/dist/endpoints/createBooking.js.map +1 -1
  30. package/dist/endpoints/customerSearch.js +5 -2
  31. package/dist/endpoints/customerSearch.js.map +1 -1
  32. package/dist/endpoints/getSlots.js +56 -7
  33. package/dist/endpoints/getSlots.js.map +1 -1
  34. package/dist/endpoints/resourceAvailability.d.ts +2 -1
  35. package/dist/endpoints/resourceAvailability.js +85 -25
  36. package/dist/endpoints/resourceAvailability.js.map +1 -1
  37. package/dist/hooks/reservations/calculateEndTime.js +48 -20
  38. package/dist/hooks/reservations/calculateEndTime.js.map +1 -1
  39. package/dist/hooks/reservations/enforceCustomerOwnership.d.ts +11 -0
  40. package/dist/hooks/reservations/enforceCustomerOwnership.js +30 -0
  41. package/dist/hooks/reservations/enforceCustomerOwnership.js.map +1 -0
  42. package/dist/hooks/reservations/onStatusChange.js +10 -4
  43. package/dist/hooks/reservations/onStatusChange.js.map +1 -1
  44. package/dist/hooks/reservations/validateCancellation.js +3 -2
  45. package/dist/hooks/reservations/validateCancellation.js.map +1 -1
  46. package/dist/hooks/reservations/validateConflicts.js +23 -4
  47. package/dist/hooks/reservations/validateConflicts.js.map +1 -1
  48. package/dist/hooks/reservations/validateGuestBooking.js +3 -4
  49. package/dist/hooks/reservations/validateGuestBooking.js.map +1 -1
  50. package/dist/hooks/reservations/validateStatusTransition.js +2 -2
  51. package/dist/hooks/reservations/validateStatusTransition.js.map +1 -1
  52. package/dist/hooks/users/provisionStaffResource.js +5 -8
  53. package/dist/hooks/users/provisionStaffResource.js.map +1 -1
  54. package/dist/plugin.js +82 -13
  55. package/dist/plugin.js.map +1 -1
  56. package/dist/services/AvailabilityService.d.ts +54 -2
  57. package/dist/services/AvailabilityService.js +180 -46
  58. package/dist/services/AvailabilityService.js.map +1 -1
  59. package/dist/translations/ar.json +1 -0
  60. package/dist/translations/de.json +1 -0
  61. package/dist/translations/en.json +1 -0
  62. package/dist/translations/es.json +1 -0
  63. package/dist/translations/fa.json +1 -0
  64. package/dist/translations/fr.json +1 -0
  65. package/dist/translations/hi.json +1 -0
  66. package/dist/translations/id.json +1 -0
  67. package/dist/translations/pl.json +1 -0
  68. package/dist/translations/ru.json +1 -0
  69. package/dist/translations/tr.json +1 -0
  70. package/dist/translations/zh.json +1 -0
  71. package/dist/types.d.ts +50 -1
  72. package/dist/types.js +2 -0
  73. package/dist/types.js.map +1 -1
  74. package/dist/utilities/collectionOverrides.d.ts +14 -0
  75. package/dist/utilities/collectionOverrides.js +47 -0
  76. package/dist/utilities/collectionOverrides.js.map +1 -0
  77. package/dist/utilities/ownerAccess.d.ts +6 -0
  78. package/dist/utilities/ownerAccess.js +25 -12
  79. package/dist/utilities/ownerAccess.js.map +1 -1
  80. package/dist/utilities/reservationChanges.d.ts +17 -0
  81. package/dist/utilities/reservationChanges.js +88 -0
  82. package/dist/utilities/reservationChanges.js.map +1 -0
  83. package/dist/utilities/scheduleUtils.d.ts +14 -8
  84. package/dist/utilities/scheduleUtils.js +26 -19
  85. package/dist/utilities/scheduleUtils.js.map +1 -1
  86. package/dist/utilities/tenantFilter.d.ts +25 -0
  87. package/dist/utilities/tenantFilter.js +56 -0
  88. package/dist/utilities/tenantFilter.js.map +1 -0
  89. package/dist/utilities/timezoneUtils.d.ts +39 -0
  90. package/dist/utilities/timezoneUtils.js +134 -0
  91. package/dist/utilities/timezoneUtils.js.map +1 -0
  92. package/dist/utilities/useTenantFilter.d.ts +6 -0
  93. package/dist/utilities/useTenantFilter.js +28 -0
  94. package/dist/utilities/useTenantFilter.js.map +1 -0
  95. 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 terminal states
21
- - **Double-Booking Prevention** — Server-side conflict detection with per-item buffer times; respects capacity modes
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
- - **Extra Reservation Fields** — Inject custom fields into the Reservations collection via `extraReservationFields` without forking the plugin
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 = config.access.reservations ?? (rom ? makeReservationOwnerAccess(rom) : {});
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
- read: ({ req })=>Boolean(req.user) && req.user.collection !== config.slugs.customers
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 === 'cancelled'
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(config),
268
- validateCancellation(config)
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 = config.access.resources ?? (rom ? makeResourceOwnerAccess(rom) : {});
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
- name: 'image',
64
- type: 'upload',
65
- label: ({ t })=>t('reservation:fieldImage'),
66
- relationTo: config.slugs.media
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 config.access.resources ?? (rom ? makeResourceOwnerAccess(rom) : {})\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 {\n name: 'image',\n type: 'upload',\n label: ({ t }) => (t as PluginT)('reservation:fieldImage'),\n relationTo: config.slugs.media as unknown as CollectionSlug,\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 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":["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","media","hasMany","services","defaultValue","min","condition","data","quantity","options","resourceTypes","labels","plural","singular"],"mappings":"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"}
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 = config.access.schedules ?? (rom ? makeScheduleOwnerAccess(rom) : {});
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 && slot.endTime && slot.startTime >= slot.endTime) {
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 && slot.endTime && slot.startTime >= slot.endTime) {
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).toISOString().split('T')[0];
240
- const end = new Date(exc.endDate).toISOString().split('T')[0];
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":"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"}
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":"AAEA,SAASA,eAAe,QAAQ,UAAS;AAKzC,SAASC,aAAa,EAAEC,uBAAuB,QAAQ,8BAA6B;AACpF,SAASC,kBAAkB,QAAQ,gCAA+B;AAClE,SAASC,mBAAmB,QAAQ,gCAA+B;AAEnE,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,SACJZ,cAAcU,MAAMT,wBAAwBS,OAAO,CAAC,GAAGD,OAAOG,MAAM,CAACC,SAAS;IAEhF,OAAO;QACLC,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,SAAS3B,mBAAmBO,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,qEAAqE;oBACrE,qEAAqE;oBACrE,yDAAyD;oBACzD,MAAMC,OAAO;oBACb,MAAMC,UAAU,CAACC,OAAgBC,MAC/B,CAACD,SAAS,CAACC,OAAO,CAACH,KAAKpC,IAAI,CAACsC,UAAU,CAACF,KAAKpC,IAAI,CAACuC,QAAQD,QAAQC;oBACpE,MAAMC,QAAQ,AAACL,MAAMM,kBAAsE,EAAE;oBAC7F,KAAK,MAAMC,QAAQF,MAAO;wBACxB,IAAI,CAACH,QAAQK,KAAKC,SAAS,EAAED,KAAKE,OAAO,GAAG;4BAC1C,MAAM,IAAIpD,gBAAgB;gCACxBqD,QAAQ;oCAAC;wCAAEC,SAAS;wCAAmCC,MAAM;oCAAiB;iCAAE;4BAClF;wBACF;oBACF;oBACA,MAAMC,SAAS,AAACb,MAAMc,eAAmE,EAAE;oBAC3F,KAAK,MAAMP,QAAQM,OAAQ;wBACzB,IAAI,CAACX,QAAQK,KAAKC,SAAS,EAAED,KAAKE,OAAO,GAAG;4BAC1C,MAAM,IAAIpD,gBAAgB;gCACxBqD,QAAQ;oCAAC;wCAAEC,SAAS;wCAAmCC,MAAM;oCAAc;iCAAE;4BAC/E;wBACF;oBACF;oBACA,MAAMG,aAAa,AAACf,MAAMe,cAA6D,EAAE;oBACzF,KAAK,MAAMC,OAAOD,WAAY;wBAC5B,IAAIC,IAAItB,IAAI,IAAIsB,IAAIC,OAAO,EAAE;4BAC3B,MAAMd,QAAQ1C,oBAAoB,IAAIyD,KAAKF,IAAItB,IAAI,GAAG3B,OAAOoD,QAAQ;4BACrE,MAAMf,MAAM3C,oBAAoB,IAAIyD,KAAKF,IAAIC,OAAO,GAAGlD,OAAOoD,QAAQ;4BACtE,IAAIf,MAAMD,OAAO;gCACf,MAAM,IAAI9C,gBAAgB;oCACxBqD,QAAQ;wCAAC;4CAAEC,SAAS;4CAA8CC,MAAM;wCAAa;qCAAE;gCACzF;4BACF;wBACF;oBACF;oBACA,OAAOZ;gBACT;aACD;QACH;QACAoB,QAAQ;YACNC,QAAQ,CAAC,EAAEvC,CAAC,EAAE,GAAK,AAACA,EAAc;YAClCwC,UAAU,CAAC,EAAExC,CAAC,EAAE,GAAK,AAACA,EAAc;QACtC;IACF;AACF"}
@@ -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: config.slugs.customers,
27
+ relationTo: ownerCollection,
25
28
  required: true
26
29
  } : null;
27
- // Determine access: app override owner-mode auto-wired unrestricted
28
- const access = config.access.services ?? (rom && ownedServices ? makeServiceOwnerAccess(rom, ownerField) : {});
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
- name: 'image',
49
- type: 'upload',
50
- label: ({ t })=>t('reservation:fieldImage'),
51
- relationTo: config.slugs.media
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: config.slugs.customers 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 config.access.services ??\n (rom && ownedServices ? makeServiceOwnerAccess(rom, ownerField) : {})\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 {\n name: 'image',\n type: 'upload',\n label: ({ t }) => (t as PluginT)('reservation:fieldImage'),\n relationTo: config.slugs.media as unknown as CollectionSlug,\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 name: 'bufferTimeBefore',\n type: 'number',\n defaultValue: 0,\n label: ({ t }) => (t as PluginT)('reservation:fieldBufferTimeBefore'),\n min: 0,\n },\n {\n name: 'bufferTimeAfter',\n type: 'number',\n defaultValue: 0,\n label: ({ t }) => (t as PluginT)('reservation:fieldBufferTimeAfter'),\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":["makeServiceOwnerAccess","createServicesCollection","config","rom","resourceOwnerMode","ownedServices","ownerField","ownerFieldDef","name","type","admin","position","hooks","beforeChange","operation","req","value","user","id","label","t","relationTo","slugs","customers","required","access","services","slug","group","adminGroup","useAsTitle","fields","localized","maxLength","media","min","defaultValue","options","step","description","hasMany","resources","labels","plural","singular"],"mappings":"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"}
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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"}