payload-reserve 1.0.2 → 1.1.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 (81) hide show
  1. package/README.md +61 -1141
  2. package/dist/collections/Customers.d.ts +3 -0
  3. package/dist/collections/Customers.js +58 -0
  4. package/dist/collections/Customers.js.map +1 -0
  5. package/dist/collections/Reservations.js +129 -35
  6. package/dist/collections/Reservations.js.map +1 -1
  7. package/dist/collections/Resources.js +70 -2
  8. package/dist/collections/Resources.js.map +1 -1
  9. package/dist/collections/Schedules.js +4 -1
  10. package/dist/collections/Schedules.js.map +1 -1
  11. package/dist/collections/Services.js +59 -2
  12. package/dist/collections/Services.js.map +1 -1
  13. package/dist/components/AvailabilityOverview/AvailabilityOverview.module.css +31 -0
  14. package/dist/components/AvailabilityOverview/index.js +59 -8
  15. package/dist/components/AvailabilityOverview/index.js.map +1 -1
  16. package/dist/components/CalendarView/CalendarView.module.css +171 -0
  17. package/dist/components/CalendarView/index.js +547 -38
  18. package/dist/components/CalendarView/index.js.map +1 -1
  19. package/dist/components/CustomerField/index.js +24 -10
  20. package/dist/components/CustomerField/index.js.map +1 -1
  21. package/dist/components/DashboardWidget/DashboardWidgetServer.js +21 -11
  22. package/dist/components/DashboardWidget/DashboardWidgetServer.js.map +1 -1
  23. package/dist/defaults.d.ts +1 -2
  24. package/dist/defaults.js +22 -4
  25. package/dist/defaults.js.map +1 -1
  26. package/dist/endpoints/cancelBooking.d.ts +3 -0
  27. package/dist/endpoints/cancelBooking.js +37 -0
  28. package/dist/endpoints/cancelBooking.js.map +1 -0
  29. package/dist/endpoints/checkAvailability.d.ts +3 -0
  30. package/dist/endpoints/checkAvailability.js +37 -0
  31. package/dist/endpoints/checkAvailability.js.map +1 -0
  32. package/dist/endpoints/createBooking.d.ts +3 -0
  33. package/dist/endpoints/createBooking.js +32 -0
  34. package/dist/endpoints/createBooking.js.map +1 -0
  35. package/dist/endpoints/customerSearch.js +76 -58
  36. package/dist/endpoints/customerSearch.js.map +1 -1
  37. package/dist/endpoints/getSlots.d.ts +3 -0
  38. package/dist/endpoints/getSlots.js +51 -0
  39. package/dist/endpoints/getSlots.js.map +1 -0
  40. package/dist/hooks/index.d.ts +2 -0
  41. package/dist/hooks/index.js +2 -0
  42. package/dist/hooks/index.js.map +1 -1
  43. package/dist/hooks/reservations/calculateEndTime.js +75 -9
  44. package/dist/hooks/reservations/calculateEndTime.js.map +1 -1
  45. package/dist/hooks/reservations/checkIdempotency.d.ts +3 -0
  46. package/dist/hooks/reservations/checkIdempotency.js +32 -0
  47. package/dist/hooks/reservations/checkIdempotency.js.map +1 -0
  48. package/dist/hooks/reservations/onStatusChange.d.ts +3 -0
  49. package/dist/hooks/reservations/onStatusChange.js +41 -0
  50. package/dist/hooks/reservations/onStatusChange.js.map +1 -0
  51. package/dist/hooks/reservations/validateCancellation.js +1 -1
  52. package/dist/hooks/reservations/validateCancellation.js.map +1 -1
  53. package/dist/hooks/reservations/validateConflicts.js +34 -56
  54. package/dist/hooks/reservations/validateConflicts.js.map +1 -1
  55. package/dist/hooks/reservations/validateStatusTransition.d.ts +2 -1
  56. package/dist/hooks/reservations/validateStatusTransition.js +29 -6
  57. package/dist/hooks/reservations/validateStatusTransition.js.map +1 -1
  58. package/dist/index.d.ts +5 -1
  59. package/dist/index.js +3 -0
  60. package/dist/index.js.map +1 -1
  61. package/dist/plugin.js +49 -54
  62. package/dist/plugin.js.map +1 -1
  63. package/dist/services/AvailabilityService.d.ts +57 -0
  64. package/dist/services/AvailabilityService.js +232 -0
  65. package/dist/services/AvailabilityService.js.map +1 -0
  66. package/dist/services/index.d.ts +1 -0
  67. package/dist/services/index.js +3 -0
  68. package/dist/services/index.js.map +1 -0
  69. package/dist/translations/en.json +33 -1
  70. package/dist/translations/index.d.ts +5 -0
  71. package/dist/translations/index.js.map +1 -1
  72. package/dist/types.d.ts +84 -6
  73. package/dist/types.js +29 -9
  74. package/dist/types.js.map +1 -1
  75. package/dist/utilities/ownerAccess.d.ts +24 -0
  76. package/dist/utilities/ownerAccess.js +128 -0
  77. package/dist/utilities/ownerAccess.js.map +1 -0
  78. package/dist/utilities/resolveReservationItems.d.ts +18 -0
  79. package/dist/utilities/resolveReservationItems.js +45 -0
  80. package/dist/utilities/resolveReservationItems.js.map +1 -0
  81. package/package.json +12 -9
@@ -0,0 +1,57 @@
1
+ import type { Payload, PayloadRequest, Where } from 'payload';
2
+ import type { DurationType, StatusMachineConfig } from '../types.js';
3
+ export declare function computeEndTime(params: {
4
+ durationType: DurationType;
5
+ endTime?: Date;
6
+ serviceDuration: number;
7
+ startTime: Date;
8
+ }): {
9
+ durationMinutes: number;
10
+ endTime: Date;
11
+ };
12
+ export declare function buildOverlapQuery(params: {
13
+ blockingStatuses: string[];
14
+ effectiveEnd: Date;
15
+ effectiveStart: Date;
16
+ excludeReservationId?: string;
17
+ resourceId: string;
18
+ }): Where;
19
+ export declare function isBlockingStatus(status: string, statusMachine: StatusMachineConfig): boolean;
20
+ export declare function validateTransition(fromStatus: string, toStatus: string, statusMachine: StatusMachineConfig): {
21
+ reason?: string;
22
+ valid: boolean;
23
+ };
24
+ export declare function checkAvailability(params: {
25
+ blockingStatuses: string[];
26
+ bufferAfter: number;
27
+ bufferBefore: number;
28
+ endTime: Date;
29
+ excludeReservationId?: string;
30
+ guestCount: number;
31
+ payload: Payload;
32
+ req: PayloadRequest;
33
+ reservationSlug: string;
34
+ resourceId: string;
35
+ resourceSlug: string;
36
+ startTime: Date;
37
+ }): Promise<{
38
+ available: boolean;
39
+ currentCount: number;
40
+ reason?: string;
41
+ totalCapacity: number;
42
+ }>;
43
+ export declare function getAvailableSlots(params: {
44
+ blockingStatuses: string[];
45
+ date: Date;
46
+ payload: Payload;
47
+ req: PayloadRequest;
48
+ reservationSlug: string;
49
+ resourceId: string;
50
+ resourceSlug: string;
51
+ scheduleSlug: string;
52
+ serviceId: string;
53
+ serviceSlug: string;
54
+ }): Promise<Array<{
55
+ end: Date;
56
+ start: Date;
57
+ }>>;
@@ -0,0 +1,232 @@
1
+ import { resolveScheduleForDate } from '../utilities/scheduleUtils.js';
2
+ import { addMinutes, computeBlockedWindow } from '../utilities/slotUtils.js';
3
+ // --- Pure functions (no DB) ---
4
+ export function computeEndTime(params) {
5
+ const { durationType, serviceDuration, startTime } = params;
6
+ if (durationType === 'full-day') {
7
+ const end = new Date(startTime);
8
+ end.setHours(23, 59, 59, 999);
9
+ const durationMinutes = Math.round((end.getTime() - startTime.getTime()) / 60_000);
10
+ return {
11
+ durationMinutes,
12
+ endTime: end
13
+ };
14
+ }
15
+ if (durationType === 'flexible' && params.endTime) {
16
+ const durationMinutes = Math.round((params.endTime.getTime() - startTime.getTime()) / 60_000);
17
+ return {
18
+ durationMinutes,
19
+ endTime: params.endTime
20
+ };
21
+ }
22
+ // fixed duration (default)
23
+ const endTime = addMinutes(startTime, serviceDuration);
24
+ return {
25
+ durationMinutes: serviceDuration,
26
+ endTime
27
+ };
28
+ }
29
+ export function buildOverlapQuery(params) {
30
+ const { blockingStatuses, effectiveEnd, effectiveStart, excludeReservationId, resourceId } = params;
31
+ const conditions = [
32
+ {
33
+ resource: {
34
+ equals: resourceId
35
+ }
36
+ },
37
+ {
38
+ status: {
39
+ in: blockingStatuses
40
+ }
41
+ },
42
+ {
43
+ startTime: {
44
+ less_than: effectiveEnd.toISOString()
45
+ }
46
+ },
47
+ {
48
+ endTime: {
49
+ greater_than: effectiveStart.toISOString()
50
+ }
51
+ }
52
+ ];
53
+ if (excludeReservationId) {
54
+ conditions.push({
55
+ id: {
56
+ not_equals: excludeReservationId
57
+ }
58
+ });
59
+ }
60
+ return {
61
+ and: conditions
62
+ };
63
+ }
64
+ export function isBlockingStatus(status, statusMachine) {
65
+ return statusMachine.blockingStatuses.includes(status);
66
+ }
67
+ export function validateTransition(fromStatus, toStatus, statusMachine) {
68
+ const allowed = statusMachine.transitions[fromStatus];
69
+ if (!allowed) {
70
+ return {
71
+ reason: `Unknown status: ${fromStatus}`,
72
+ valid: false
73
+ };
74
+ }
75
+ if (!allowed.includes(toStatus)) {
76
+ return {
77
+ reason: `Cannot transition from "${fromStatus}" to "${toStatus}"`,
78
+ valid: false
79
+ };
80
+ }
81
+ return {
82
+ valid: true
83
+ };
84
+ }
85
+ // --- DB functions (use Payload Local API only) ---
86
+ export async function checkAvailability(params) {
87
+ const { blockingStatuses, bufferAfter, bufferBefore, endTime, excludeReservationId, guestCount, payload, req, reservationSlug, resourceId, resourceSlug, startTime } = params;
88
+ // Fetch resource for quantity and capacity mode
89
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
90
+ const resource = await payload.findByID({
91
+ id: resourceId,
92
+ collection: resourceSlug,
93
+ depth: 0,
94
+ req
95
+ });
96
+ const quantity = resource.quantity ?? 1;
97
+ const capacityMode = resource.capacityMode ?? 'per-reservation';
98
+ // Compute effective window with buffers
99
+ const { effectiveEnd, effectiveStart } = computeBlockedWindow(startTime, endTime, bufferBefore, bufferAfter);
100
+ // Build overlap query
101
+ const where = buildOverlapQuery({
102
+ blockingStatuses,
103
+ effectiveEnd,
104
+ effectiveStart,
105
+ excludeReservationId,
106
+ resourceId
107
+ });
108
+ if (capacityMode === 'per-guest') {
109
+ // Must fetch docs to sum guestCount
110
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
111
+ const { docs } = await payload.find({
112
+ collection: reservationSlug,
113
+ depth: 0,
114
+ limit: 0,
115
+ req,
116
+ select: {
117
+ guestCount: true
118
+ },
119
+ where
120
+ });
121
+ const currentGuests = docs.reduce((sum, doc)=>sum + (doc.guestCount ?? 1), 0);
122
+ return {
123
+ available: currentGuests + guestCount <= quantity,
124
+ currentCount: currentGuests,
125
+ reason: currentGuests + guestCount > quantity ? 'Guest capacity exceeded' : undefined,
126
+ totalCapacity: quantity
127
+ };
128
+ }
129
+ // per-reservation mode: count is sufficient
130
+ // TODO: batch queries — linear per-item cost acceptable for 2-5 items
131
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
132
+ const { totalDocs } = await payload.count({
133
+ collection: reservationSlug,
134
+ req,
135
+ where
136
+ });
137
+ return {
138
+ available: totalDocs + 1 <= quantity,
139
+ currentCount: totalDocs,
140
+ reason: totalDocs + 1 > quantity ? 'All units are booked for this time' : undefined,
141
+ totalCapacity: quantity
142
+ };
143
+ }
144
+ export async function getAvailableSlots(params) {
145
+ const { blockingStatuses, date, payload, req, reservationSlug, resourceId, resourceSlug, scheduleSlug, serviceId, serviceSlug } = params;
146
+ // 1. Fetch service for duration + buffer times
147
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
148
+ const service = await payload.findByID({
149
+ id: serviceId,
150
+ collection: serviceSlug,
151
+ depth: 0,
152
+ req
153
+ });
154
+ const duration = service.duration ?? 60;
155
+ const bufferBefore = service.bufferTimeBefore ?? 0;
156
+ const bufferAfter = service.bufferTimeAfter ?? 0;
157
+ const durationType = service.durationType ?? 'fixed';
158
+ // 2. Fetch resource's schedules for the date
159
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
160
+ const { docs: schedules } = await payload.find({
161
+ collection: scheduleSlug,
162
+ depth: 0,
163
+ limit: 100,
164
+ req,
165
+ where: {
166
+ and: [
167
+ {
168
+ resource: {
169
+ equals: resourceId
170
+ }
171
+ },
172
+ {
173
+ active: {
174
+ equals: true
175
+ }
176
+ }
177
+ ]
178
+ }
179
+ });
180
+ // 3. Resolve schedules to time ranges for the date
181
+ const timeRanges = [];
182
+ for (const schedule of schedules){
183
+ const ranges = resolveScheduleForDate(schedule, date);
184
+ timeRanges.push(...ranges);
185
+ }
186
+ if (timeRanges.length === 0) {
187
+ return [];
188
+ }
189
+ // 4. Generate candidate slots from schedule ranges
190
+ const { endTime: slotEndOffset } = computeEndTime({
191
+ durationType,
192
+ serviceDuration: duration,
193
+ startTime: new Date(0)
194
+ });
195
+ const slotDuration = Math.round(slotEndOffset.getTime() / 60_000);
196
+ const effectiveDuration = durationType === 'fixed' ? duration : slotDuration;
197
+ const availableSlots = [];
198
+ for (const range of timeRanges){
199
+ let candidateStart = new Date(range.start);
200
+ while(true){
201
+ const candidateEnd = addMinutes(candidateStart, effectiveDuration);
202
+ if (candidateEnd > range.end) {
203
+ break;
204
+ }
205
+ // 5. Check availability for each candidate slot
206
+ const result = await checkAvailability({
207
+ blockingStatuses,
208
+ bufferAfter,
209
+ bufferBefore,
210
+ endTime: candidateEnd,
211
+ guestCount: 1,
212
+ payload,
213
+ req,
214
+ reservationSlug,
215
+ resourceId,
216
+ resourceSlug,
217
+ startTime: candidateStart
218
+ });
219
+ if (result.available) {
220
+ availableSlots.push({
221
+ end: candidateEnd,
222
+ start: new Date(candidateStart)
223
+ });
224
+ }
225
+ // Move to next slot (service duration as step)
226
+ candidateStart = addMinutes(candidateStart, effectiveDuration);
227
+ }
228
+ }
229
+ return availableSlots;
230
+ }
231
+
232
+ //# sourceMappingURL=AvailabilityService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/services/AvailabilityService.ts"],"sourcesContent":["import type { Payload, PayloadRequest, Where } from 'payload'\n\nimport type { CapacityMode, DurationType, StatusMachineConfig } from '../types.js'\n\nimport { resolveScheduleForDate } from '../utilities/scheduleUtils.js'\nimport { addMinutes, computeBlockedWindow } from '../utilities/slotUtils.js'\n\n// --- Pure functions (no DB) ---\n\nexport function computeEndTime(params: {\n durationType: DurationType\n endTime?: Date\n serviceDuration: number\n startTime: Date\n}): { durationMinutes: number; endTime: Date } {\n const { durationType, serviceDuration, startTime } = params\n\n if (durationType === 'full-day') {\n const end = new Date(startTime)\n end.setHours(23, 59, 59, 999)\n const durationMinutes = Math.round((end.getTime() - startTime.getTime()) / 60_000)\n return { durationMinutes, endTime: end }\n }\n\n if (durationType === 'flexible' && params.endTime) {\n const durationMinutes = Math.round(\n (params.endTime.getTime() - startTime.getTime()) / 60_000,\n )\n return { durationMinutes, endTime: params.endTime }\n }\n\n // fixed duration (default)\n const endTime = addMinutes(startTime, serviceDuration)\n return { durationMinutes: serviceDuration, endTime }\n}\n\nexport function buildOverlapQuery(params: {\n blockingStatuses: string[]\n effectiveEnd: Date\n effectiveStart: Date\n excludeReservationId?: string\n resourceId: string\n}): Where {\n const { blockingStatuses, effectiveEnd, effectiveStart, excludeReservationId, resourceId } =\n params\n\n const conditions: Where[] = [\n { resource: { equals: resourceId } },\n { status: { in: blockingStatuses } },\n { startTime: { less_than: effectiveEnd.toISOString() } },\n { endTime: { greater_than: effectiveStart.toISOString() } },\n ]\n\n if (excludeReservationId) {\n conditions.push({ id: { not_equals: excludeReservationId } })\n }\n\n return { and: conditions }\n}\n\nexport function isBlockingStatus(\n status: string,\n statusMachine: StatusMachineConfig,\n): boolean {\n return statusMachine.blockingStatuses.includes(status)\n}\n\nexport function validateTransition(\n fromStatus: string,\n toStatus: string,\n statusMachine: StatusMachineConfig,\n): { reason?: string; valid: boolean } {\n const allowed = statusMachine.transitions[fromStatus]\n if (!allowed) {\n return { reason: `Unknown status: ${fromStatus}`, valid: false }\n }\n if (!allowed.includes(toStatus)) {\n return {\n reason: `Cannot transition from \"${fromStatus}\" to \"${toStatus}\"`,\n valid: false,\n }\n }\n return { valid: true }\n}\n\n// --- DB functions (use Payload Local API only) ---\n\nexport async function checkAvailability(params: {\n blockingStatuses: string[]\n bufferAfter: number\n bufferBefore: number\n endTime: Date\n excludeReservationId?: string\n guestCount: number\n payload: Payload\n req: PayloadRequest\n reservationSlug: string\n resourceId: string\n resourceSlug: string\n startTime: Date\n}): Promise<{\n available: boolean\n currentCount: number\n reason?: string\n totalCapacity: number\n}> {\n const {\n blockingStatuses,\n bufferAfter,\n bufferBefore,\n endTime,\n excludeReservationId,\n guestCount,\n payload,\n req,\n reservationSlug,\n resourceId,\n resourceSlug,\n startTime,\n } = params\n\n // Fetch resource for quantity and capacity mode\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const resource = await (payload.findByID as any)({\n id: resourceId,\n collection: resourceSlug,\n depth: 0,\n req,\n })\n const quantity = (resource.quantity as number) ?? 1\n const capacityMode = ((resource.capacityMode as string) ?? 'per-reservation') as CapacityMode\n\n // Compute effective window with buffers\n const { effectiveEnd, effectiveStart } = computeBlockedWindow(\n startTime,\n endTime,\n bufferBefore,\n bufferAfter,\n )\n\n // Build overlap query\n const where = buildOverlapQuery({\n blockingStatuses,\n effectiveEnd,\n effectiveStart,\n excludeReservationId,\n resourceId,\n })\n\n if (capacityMode === 'per-guest') {\n // Must fetch docs to sum guestCount\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const { docs } = await (payload.find as any)({\n collection: reservationSlug,\n depth: 0,\n limit: 0,\n req,\n select: { guestCount: true },\n where,\n })\n const currentGuests = docs.reduce(\n (sum: number, doc: Record<string, unknown>) => sum + ((doc.guestCount as number) ?? 1),\n 0,\n )\n return {\n available: currentGuests + guestCount <= quantity,\n currentCount: currentGuests,\n reason:\n currentGuests + guestCount > quantity ? 'Guest capacity exceeded' : undefined,\n totalCapacity: quantity,\n }\n }\n\n // per-reservation mode: count is sufficient\n // TODO: batch queries — linear per-item cost acceptable for 2-5 items\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const { totalDocs } = await (payload.count as any)({\n collection: reservationSlug,\n req,\n where,\n })\n return {\n available: totalDocs + 1 <= quantity,\n currentCount: totalDocs,\n reason: totalDocs + 1 > quantity ? 'All units are booked for this time' : undefined,\n totalCapacity: quantity,\n }\n}\n\nexport async function getAvailableSlots(params: {\n blockingStatuses: string[]\n date: Date\n payload: Payload\n req: PayloadRequest\n reservationSlug: string\n resourceId: string\n resourceSlug: string\n scheduleSlug: string\n serviceId: string\n serviceSlug: string\n}): Promise<Array<{ end: Date; start: Date }>> {\n const {\n blockingStatuses,\n date,\n payload,\n req,\n reservationSlug,\n resourceId,\n resourceSlug,\n scheduleSlug,\n serviceId,\n serviceSlug,\n } = params\n\n // 1. Fetch service for duration + buffer times\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const service = await (payload.findByID as any)({\n id: serviceId,\n collection: serviceSlug,\n depth: 0,\n req,\n })\n const duration = (service.duration as number) ?? 60\n const bufferBefore = (service.bufferTimeBefore as number) ?? 0\n const bufferAfter = (service.bufferTimeAfter as number) ?? 0\n const durationType = ((service.durationType as string) ?? 'fixed') as DurationType\n\n // 2. Fetch resource's schedules for the date\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const { docs: schedules } = await (payload.find as any)({\n collection: scheduleSlug,\n depth: 0,\n limit: 100,\n req,\n where: {\n and: [{ resource: { equals: resourceId } }, { active: { equals: true } }],\n },\n })\n\n // 3. Resolve schedules to time ranges for the date\n const timeRanges: Array<{ end: Date; start: Date }> = []\n for (const schedule of schedules) {\n const ranges = resolveScheduleForDate(\n schedule as unknown as Parameters<typeof resolveScheduleForDate>[0],\n date,\n )\n timeRanges.push(...ranges)\n }\n\n if (timeRanges.length === 0) {\n return []\n }\n\n // 4. Generate candidate slots from schedule ranges\n const { endTime: slotEndOffset } = computeEndTime({\n durationType,\n serviceDuration: duration,\n startTime: new Date(0),\n })\n const slotDuration = Math.round(slotEndOffset.getTime() / 60_000)\n const effectiveDuration = durationType === 'fixed' ? duration : slotDuration\n\n const availableSlots: Array<{ end: Date; start: Date }> = []\n\n for (const range of timeRanges) {\n let candidateStart = new Date(range.start)\n\n while (true) {\n const candidateEnd = addMinutes(candidateStart, effectiveDuration)\n if (candidateEnd > range.end) {break}\n\n // 5. Check availability for each candidate slot\n const result = await checkAvailability({\n blockingStatuses,\n bufferAfter,\n bufferBefore,\n endTime: candidateEnd,\n guestCount: 1,\n payload,\n req,\n reservationSlug,\n resourceId,\n resourceSlug,\n startTime: candidateStart,\n })\n\n if (result.available) {\n availableSlots.push({ end: candidateEnd, start: new Date(candidateStart) })\n }\n\n // Move to next slot (service duration as step)\n candidateStart = addMinutes(candidateStart, effectiveDuration)\n }\n }\n\n return availableSlots\n}\n"],"names":["resolveScheduleForDate","addMinutes","computeBlockedWindow","computeEndTime","params","durationType","serviceDuration","startTime","end","Date","setHours","durationMinutes","Math","round","getTime","endTime","buildOverlapQuery","blockingStatuses","effectiveEnd","effectiveStart","excludeReservationId","resourceId","conditions","resource","equals","status","in","less_than","toISOString","greater_than","push","id","not_equals","and","isBlockingStatus","statusMachine","includes","validateTransition","fromStatus","toStatus","allowed","transitions","reason","valid","checkAvailability","bufferAfter","bufferBefore","guestCount","payload","req","reservationSlug","resourceSlug","findByID","collection","depth","quantity","capacityMode","where","docs","find","limit","select","currentGuests","reduce","sum","doc","available","currentCount","undefined","totalCapacity","totalDocs","count","getAvailableSlots","date","scheduleSlug","serviceId","serviceSlug","service","duration","bufferTimeBefore","bufferTimeAfter","schedules","active","timeRanges","schedule","ranges","length","slotEndOffset","slotDuration","effectiveDuration","availableSlots","range","candidateStart","start","candidateEnd","result"],"mappings":"AAIA,SAASA,sBAAsB,QAAQ,gCAA+B;AACtE,SAASC,UAAU,EAAEC,oBAAoB,QAAQ,4BAA2B;AAE5E,iCAAiC;AAEjC,OAAO,SAASC,eAAeC,MAK9B;IACC,MAAM,EAAEC,YAAY,EAAEC,eAAe,EAAEC,SAAS,EAAE,GAAGH;IAErD,IAAIC,iBAAiB,YAAY;QAC/B,MAAMG,MAAM,IAAIC,KAAKF;QACrBC,IAAIE,QAAQ,CAAC,IAAI,IAAI,IAAI;QACzB,MAAMC,kBAAkBC,KAAKC,KAAK,CAAC,AAACL,CAAAA,IAAIM,OAAO,KAAKP,UAAUO,OAAO,EAAC,IAAK;QAC3E,OAAO;YAAEH;YAAiBI,SAASP;QAAI;IACzC;IAEA,IAAIH,iBAAiB,cAAcD,OAAOW,OAAO,EAAE;QACjD,MAAMJ,kBAAkBC,KAAKC,KAAK,CAChC,AAACT,CAAAA,OAAOW,OAAO,CAACD,OAAO,KAAKP,UAAUO,OAAO,EAAC,IAAK;QAErD,OAAO;YAAEH;YAAiBI,SAASX,OAAOW,OAAO;QAAC;IACpD;IAEA,2BAA2B;IAC3B,MAAMA,UAAUd,WAAWM,WAAWD;IACtC,OAAO;QAAEK,iBAAiBL;QAAiBS;IAAQ;AACrD;AAEA,OAAO,SAASC,kBAAkBZ,MAMjC;IACC,MAAM,EAAEa,gBAAgB,EAAEC,YAAY,EAAEC,cAAc,EAAEC,oBAAoB,EAAEC,UAAU,EAAE,GACxFjB;IAEF,MAAMkB,aAAsB;QAC1B;YAAEC,UAAU;gBAAEC,QAAQH;YAAW;QAAE;QACnC;YAAEI,QAAQ;gBAAEC,IAAIT;YAAiB;QAAE;QACnC;YAAEV,WAAW;gBAAEoB,WAAWT,aAAaU,WAAW;YAAG;QAAE;QACvD;YAAEb,SAAS;gBAAEc,cAAcV,eAAeS,WAAW;YAAG;QAAE;KAC3D;IAED,IAAIR,sBAAsB;QACxBE,WAAWQ,IAAI,CAAC;YAAEC,IAAI;gBAAEC,YAAYZ;YAAqB;QAAE;IAC7D;IAEA,OAAO;QAAEa,KAAKX;IAAW;AAC3B;AAEA,OAAO,SAASY,iBACdT,MAAc,EACdU,aAAkC;IAElC,OAAOA,cAAclB,gBAAgB,CAACmB,QAAQ,CAACX;AACjD;AAEA,OAAO,SAASY,mBACdC,UAAkB,EAClBC,QAAgB,EAChBJ,aAAkC;IAElC,MAAMK,UAAUL,cAAcM,WAAW,CAACH,WAAW;IACrD,IAAI,CAACE,SAAS;QACZ,OAAO;YAAEE,QAAQ,CAAC,gBAAgB,EAAEJ,YAAY;YAAEK,OAAO;QAAM;IACjE;IACA,IAAI,CAACH,QAAQJ,QAAQ,CAACG,WAAW;QAC/B,OAAO;YACLG,QAAQ,CAAC,wBAAwB,EAAEJ,WAAW,MAAM,EAAEC,SAAS,CAAC,CAAC;YACjEI,OAAO;QACT;IACF;IACA,OAAO;QAAEA,OAAO;IAAK;AACvB;AAEA,oDAAoD;AAEpD,OAAO,eAAeC,kBAAkBxC,MAavC;IAMC,MAAM,EACJa,gBAAgB,EAChB4B,WAAW,EACXC,YAAY,EACZ/B,OAAO,EACPK,oBAAoB,EACpB2B,UAAU,EACVC,OAAO,EACPC,GAAG,EACHC,eAAe,EACf7B,UAAU,EACV8B,YAAY,EACZ5C,SAAS,EACV,GAAGH;IAEJ,gDAAgD;IAChD,8DAA8D;IAC9D,MAAMmB,WAAW,MAAM,AAACyB,QAAQI,QAAQ,CAAS;QAC/CrB,IAAIV;QACJgC,YAAYF;QACZG,OAAO;QACPL;IACF;IACA,MAAMM,WAAW,AAAChC,SAASgC,QAAQ,IAAe;IAClD,MAAMC,eAAgB,AAACjC,SAASiC,YAAY,IAAe;IAE3D,wCAAwC;IACxC,MAAM,EAAEtC,YAAY,EAAEC,cAAc,EAAE,GAAGjB,qBACvCK,WACAQ,SACA+B,cACAD;IAGF,sBAAsB;IACtB,MAAMY,QAAQzC,kBAAkB;QAC9BC;QACAC;QACAC;QACAC;QACAC;IACF;IAEA,IAAImC,iBAAiB,aAAa;QAChC,oCAAoC;QACpC,8DAA8D;QAC9D,MAAM,EAAEE,IAAI,EAAE,GAAG,MAAM,AAACV,QAAQW,IAAI,CAAS;YAC3CN,YAAYH;YACZI,OAAO;YACPM,OAAO;YACPX;YACAY,QAAQ;gBAAEd,YAAY;YAAK;YAC3BU;QACF;QACA,MAAMK,gBAAgBJ,KAAKK,MAAM,CAC/B,CAACC,KAAaC,MAAiCD,MAAO,CAAA,AAACC,IAAIlB,UAAU,IAAe,CAAA,GACpF;QAEF,OAAO;YACLmB,WAAWJ,gBAAgBf,cAAcQ;YACzCY,cAAcL;YACdpB,QACEoB,gBAAgBf,aAAaQ,WAAW,4BAA4Ba;YACtEC,eAAed;QACjB;IACF;IAEA,4CAA4C;IAC5C,sEAAsE;IACtE,8DAA8D;IAC9D,MAAM,EAAEe,SAAS,EAAE,GAAG,MAAM,AAACtB,QAAQuB,KAAK,CAAS;QACjDlB,YAAYH;QACZD;QACAQ;IACF;IACA,OAAO;QACLS,WAAWI,YAAY,KAAKf;QAC5BY,cAAcG;QACd5B,QAAQ4B,YAAY,IAAIf,WAAW,uCAAuCa;QAC1EC,eAAed;IACjB;AACF;AAEA,OAAO,eAAeiB,kBAAkBpE,MAWvC;IACC,MAAM,EACJa,gBAAgB,EAChBwD,IAAI,EACJzB,OAAO,EACPC,GAAG,EACHC,eAAe,EACf7B,UAAU,EACV8B,YAAY,EACZuB,YAAY,EACZC,SAAS,EACTC,WAAW,EACZ,GAAGxE;IAEJ,+CAA+C;IAC/C,8DAA8D;IAC9D,MAAMyE,UAAU,MAAM,AAAC7B,QAAQI,QAAQ,CAAS;QAC9CrB,IAAI4C;QACJtB,YAAYuB;QACZtB,OAAO;QACPL;IACF;IACA,MAAM6B,WAAW,AAACD,QAAQC,QAAQ,IAAe;IACjD,MAAMhC,eAAe,AAAC+B,QAAQE,gBAAgB,IAAe;IAC7D,MAAMlC,cAAc,AAACgC,QAAQG,eAAe,IAAe;IAC3D,MAAM3E,eAAgB,AAACwE,QAAQxE,YAAY,IAAe;IAE1D,6CAA6C;IAC7C,8DAA8D;IAC9D,MAAM,EAAEqD,MAAMuB,SAAS,EAAE,GAAG,MAAM,AAACjC,QAAQW,IAAI,CAAS;QACtDN,YAAYqB;QACZpB,OAAO;QACPM,OAAO;QACPX;QACAQ,OAAO;YACLxB,KAAK;gBAAC;oBAAEV,UAAU;wBAAEC,QAAQH;oBAAW;gBAAE;gBAAG;oBAAE6D,QAAQ;wBAAE1D,QAAQ;oBAAK;gBAAE;aAAE;QAC3E;IACF;IAEA,mDAAmD;IACnD,MAAM2D,aAAgD,EAAE;IACxD,KAAK,MAAMC,YAAYH,UAAW;QAChC,MAAMI,SAASrF,uBACboF,UACAX;QAEFU,WAAWrD,IAAI,IAAIuD;IACrB;IAEA,IAAIF,WAAWG,MAAM,KAAK,GAAG;QAC3B,OAAO,EAAE;IACX;IAEA,mDAAmD;IACnD,MAAM,EAAEvE,SAASwE,aAAa,EAAE,GAAGpF,eAAe;QAChDE;QACAC,iBAAiBwE;QACjBvE,WAAW,IAAIE,KAAK;IACtB;IACA,MAAM+E,eAAe5E,KAAKC,KAAK,CAAC0E,cAAczE,OAAO,KAAK;IAC1D,MAAM2E,oBAAoBpF,iBAAiB,UAAUyE,WAAWU;IAEhE,MAAME,iBAAoD,EAAE;IAE5D,KAAK,MAAMC,SAASR,WAAY;QAC9B,IAAIS,iBAAiB,IAAInF,KAAKkF,MAAME,KAAK;QAEzC,MAAO,KAAM;YACX,MAAMC,eAAe7F,WAAW2F,gBAAgBH;YAChD,IAAIK,eAAeH,MAAMnF,GAAG,EAAE;gBAAC;YAAK;YAEpC,gDAAgD;YAChD,MAAMuF,SAAS,MAAMnD,kBAAkB;gBACrC3B;gBACA4B;gBACAC;gBACA/B,SAAS+E;gBACT/C,YAAY;gBACZC;gBACAC;gBACAC;gBACA7B;gBACA8B;gBACA5C,WAAWqF;YACb;YAEA,IAAIG,OAAO7B,SAAS,EAAE;gBACpBwB,eAAe5D,IAAI,CAAC;oBAAEtB,KAAKsF;oBAAcD,OAAO,IAAIpF,KAAKmF;gBAAgB;YAC3E;YAEA,+CAA+C;YAC/CA,iBAAiB3F,WAAW2F,gBAAgBH;QAC9C;IACF;IAEA,OAAOC;AACT"}
@@ -0,0 +1 @@
1
+ export { buildOverlapQuery, checkAvailability, computeEndTime, getAvailableSlots, isBlockingStatus, validateTransition, } from './AvailabilityService.js';
@@ -0,0 +1,3 @@
1
+ export { buildOverlapQuery, checkAvailability, computeEndTime, getAvailableSlots, isBlockingStatus, validateTransition } from './AvailabilityService.js';
2
+
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/services/index.ts"],"sourcesContent":["export {\n buildOverlapQuery,\n checkAvailability,\n computeEndTime,\n getAvailableSlots,\n isBlockingStatus,\n validateTransition,\n} from './AvailabilityService.js'\n"],"names":["buildOverlapQuery","checkAvailability","computeEndTime","getAvailableSlots","isBlockingStatus","validateTransition"],"mappings":"AAAA,SACEA,iBAAiB,EACjBC,iBAAiB,EACjBC,cAAc,EACdC,iBAAiB,EACjBC,gBAAgB,EAChBC,kBAAkB,QACb,2BAA0B"}
@@ -3,6 +3,10 @@
3
3
  "collectionResources": "Resources",
4
4
  "collectionSchedules": "Schedules",
5
5
  "collectionReservations": "Reservations",
6
+ "collectionCustomers": "Customers",
7
+ "collectionCustomer": "Customer",
8
+ "fieldFirstName": "First Name",
9
+ "fieldLastName": "Last Name",
6
10
  "fieldName": "Name",
7
11
  "fieldDescription": "Description",
8
12
  "fieldImage": "Image",
@@ -73,11 +77,14 @@
73
77
  "availabilityNoResources": "No active resources found.",
74
78
  "availabilityNotConfigured": "Reservation plugin not configured.",
75
79
  "availabilityBooked": "Booked",
80
+ "availabilityXofYBooked": "{{booked}}/{{total}} booked",
76
81
  "availabilityUnavailable": "Unavailable",
77
82
  "availabilityResource": "Resource",
78
83
  "dashboardTitle": "Today's Reservations",
79
84
  "dashboardTotal": "Total",
85
+ "dashboardActive": "Active",
80
86
  "dashboardUpcoming": "Upcoming",
87
+ "dashboardTerminal": "Closed",
81
88
  "dashboardCompleted": "Completed",
82
89
  "dashboardCancelled": "Cancelled",
83
90
  "dashboardNextAppointment": "Next Appointment",
@@ -87,5 +94,30 @@
87
94
  "fieldCustomerSearch": "Search customers...",
88
95
  "fieldCustomerNoResults": "No customers found",
89
96
  "fieldCustomerCreateNew": "Create new customer",
90
- "fieldCustomerClear": "Clear selection"
97
+ "fieldCustomerClear": "Clear selection",
98
+ "calendarPending": "Pending",
99
+ "pendingDateTime": "Date / Time",
100
+ "pendingActions": "Actions",
101
+ "pendingSelectAll": "Select all",
102
+ "pendingConfirmSelected": "Confirm Selected ({{count}})",
103
+ "pendingConfirming": "Confirming...",
104
+ "pendingConfirm": "Confirm",
105
+ "pendingCancel": "Cancel",
106
+ "pendingConfirmSuccess": "Reservation confirmed",
107
+ "pendingConfirmError": "Failed to confirm reservation",
108
+ "pendingCancelSuccess": "Reservation cancelled",
109
+ "pendingCancelError": "Failed to cancel reservation",
110
+ "pendingBulkConfirmSuccess": "{{succeeded}} confirmed, {{failed}} failed",
111
+ "pendingEmpty": "No pending reservations",
112
+ "fieldQuantity": "Quantity",
113
+ "fieldCapacityMode": "Capacity Mode",
114
+ "fieldTimezone": "Timezone",
115
+ "fieldDurationType": "Duration Type",
116
+ "capacityPerReservation": "Per Reservation",
117
+ "capacityPerGuest": "Per Guest",
118
+ "durationFixed": "Fixed",
119
+ "durationFlexible": "Flexible",
120
+ "durationFullDay": "Full Day",
121
+ "fieldItems": "Items",
122
+ "fieldGuestCount": "Guest Count"
91
123
  }
@@ -1,3 +1,8 @@
1
+ import en from './en.json';
1
2
  export declare const translations: Record<string, Record<string, unknown>>;
3
+ type EnKeys = keyof typeof en;
4
+ /** Union of all valid plugin translation keys, e.g. `"reservation:fieldName"` */
5
+ export type PluginTranslationKeys = `reservation:${EnKeys}`;
2
6
  /** Helper type for plugin translation calls (bypasses DefaultTranslationKeys constraint) */
3
7
  export type PluginT = (key: string, vars?: Record<string, unknown>) => string;
8
+ export {};
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/translations/index.ts"],"sourcesContent":["import en from './en.json' with { type: 'json' }\n\nexport const translations: Record<string, Record<string, unknown>> = {\n en: { reservation: en },\n}\n\n/** Helper type for plugin translation calls (bypasses DefaultTranslationKeys constraint) */\nexport type PluginT = (key: string, vars?: Record<string, unknown>) => string\n"],"names":["en","translations","reservation"],"mappings":"AAAA,OAAOA,QAAQ,YAAiC;AAEhD,OAAO,MAAMC,eAAwD;IACnED,IAAI;QAAEE,aAAaF;IAAG;AACxB,EAAC"}
1
+ {"version":3,"sources":["../../src/translations/index.ts"],"sourcesContent":["import en from './en.json' with { type: 'json' }\n\nexport const translations: Record<string, Record<string, unknown>> = {\n en: { reservation: en },\n}\n\ntype EnKeys = keyof typeof en\n/** Union of all valid plugin translation keys, e.g. `\"reservation:fieldName\"` */\nexport type PluginTranslationKeys = `reservation:${EnKeys}`\n\n/** Helper type for plugin translation calls (bypasses DefaultTranslationKeys constraint) */\nexport type PluginT = (key: string, vars?: Record<string, unknown>) => string\n"],"names":["en","translations","reservation"],"mappings":"AAAA,OAAOA,QAAQ,YAAiC;AAEhD,OAAO,MAAMC,eAAwD;IACnED,IAAI;QAAEE,aAAaF;IAAG;AACxB,EAAC"}
package/dist/types.d.ts CHANGED
@@ -1,7 +1,72 @@
1
- import type { CollectionConfig } from 'payload';
1
+ import type { CollectionConfig, Field, PayloadRequest } from 'payload';
2
+ export type DurationType = 'fixed' | 'flexible' | 'full-day';
3
+ export type CapacityMode = 'per-guest' | 'per-reservation';
4
+ export type StatusMachineConfig = {
5
+ blockingStatuses: string[];
6
+ defaultStatus: string;
7
+ statuses: string[];
8
+ terminalStatuses: string[];
9
+ transitions: Record<string, string[]>;
10
+ };
11
+ export declare const DEFAULT_STATUS_MACHINE: StatusMachineConfig;
12
+ export type ReservationItemConfig = {
13
+ endTime?: string;
14
+ guestCount?: number;
15
+ resource: string;
16
+ service?: string;
17
+ startTime?: string;
18
+ };
19
+ export type ReservationPluginHooks = {
20
+ afterBookingCancel?: Array<(args: {
21
+ doc: Record<string, unknown>;
22
+ req: PayloadRequest;
23
+ }) => Promise<void> | void>;
24
+ afterBookingConfirm?: Array<(args: {
25
+ doc: Record<string, unknown>;
26
+ req: PayloadRequest;
27
+ }) => Promise<void> | void>;
28
+ afterBookingCreate?: Array<(args: {
29
+ doc: Record<string, unknown>;
30
+ req: PayloadRequest;
31
+ }) => Promise<void> | void>;
32
+ afterStatusChange?: Array<(args: {
33
+ doc: Record<string, unknown>;
34
+ newStatus: string;
35
+ previousStatus: string;
36
+ req: PayloadRequest;
37
+ }) => Promise<void> | void>;
38
+ beforeBookingCancel?: Array<(args: {
39
+ doc: Record<string, unknown>;
40
+ reason?: string;
41
+ req: PayloadRequest;
42
+ }) => Promise<void> | void>;
43
+ beforeBookingConfirm?: Array<(args: {
44
+ doc: Record<string, unknown>;
45
+ newStatus: string;
46
+ req: PayloadRequest;
47
+ }) => Promise<void> | void>;
48
+ beforeBookingCreate?: Array<(args: {
49
+ data: Record<string, unknown>;
50
+ req: PayloadRequest;
51
+ }) => Promise<Record<string, unknown>> | Record<string, unknown>>;
52
+ };
53
+ export type ResourceOwnerModeConfig = {
54
+ /** Roles that can see all records (default: check req.user.collection === adminCollection) */
55
+ adminRoles?: string[];
56
+ /** Whether Services also get an owner field (default: false — Services are platform-managed) */
57
+ ownedServices?: boolean;
58
+ /** Field name for the owner relationship on Resources (default: 'owner') */
59
+ ownerField?: string;
60
+ };
61
+ export type ResolvedResourceOwnerModeConfig = {
62
+ adminRoles: string[];
63
+ ownedServices: boolean;
64
+ ownerField: string;
65
+ };
2
66
  export type ReservationPluginConfig = {
3
67
  /** Override access control per collection */
4
68
  access?: {
69
+ customers?: CollectionConfig['access'];
5
70
  reservations?: CollectionConfig['access'];
6
71
  resources?: CollectionConfig['access'];
7
72
  schedules?: CollectionConfig['access'];
@@ -11,25 +76,33 @@ export type ReservationPluginConfig = {
11
76
  adminGroup?: string;
12
77
  /** Hours of notice required before cancellation */
13
78
  cancellationNoticePeriod?: number;
14
- /** Role to filter customers by in the reservation form. Set false to disable filtering. (default: 'customer') */
15
- customerRole?: false | string;
16
79
  /** Default buffer time in minutes between reservations */
17
80
  defaultBufferTime?: number;
18
81
  /** Disable the plugin entirely */
19
82
  disabled?: boolean;
83
+ /** Extra fields to append to the Reservations collection */
84
+ extraReservationFields?: Field[];
85
+ /** Plugin hooks for external integrations */
86
+ hooks?: ReservationPluginHooks;
87
+ /** Enable resource-owner multi-tenancy (opt-in) */
88
+ resourceOwnerMode?: ResourceOwnerModeConfig;
20
89
  /** Override collection slugs */
21
90
  slugs?: {
91
+ customers?: string;
22
92
  media?: string;
23
93
  reservations?: string;
24
94
  resources?: string;
25
95
  schedules?: string;
26
96
  services?: string;
27
97
  };
28
- /** Slug of the existing auth collection to extend with customer fields (default: 'users') */
98
+ /** Configurable status machine (defaults to current behavior) */
99
+ statusMachine?: Partial<StatusMachineConfig>;
100
+ /** Which existing auth collection to extend with customer fields */
29
101
  userCollection?: string;
30
102
  };
31
103
  export type ResolvedReservationPluginConfig = {
32
104
  access: {
105
+ customers?: CollectionConfig['access'];
33
106
  reservations?: CollectionConfig['access'];
34
107
  resources?: CollectionConfig['access'];
35
108
  schedules?: CollectionConfig['access'];
@@ -37,20 +110,25 @@ export type ResolvedReservationPluginConfig = {
37
110
  };
38
111
  adminGroup: string;
39
112
  cancellationNoticePeriod: number;
40
- customerRole: false | string;
41
113
  defaultBufferTime: number;
42
114
  disabled: boolean;
115
+ extraReservationFields: Field[];
116
+ hooks: ReservationPluginHooks;
43
117
  localized: boolean;
118
+ resourceOwnerMode: ResolvedResourceOwnerModeConfig | undefined;
44
119
  slugs: {
120
+ customers: string;
45
121
  media: string;
46
122
  reservations: string;
47
123
  resources: string;
48
124
  schedules: string;
49
125
  services: string;
50
126
  };
51
- userCollection: string;
127
+ statusMachine: StatusMachineConfig;
128
+ userCollection: string | undefined;
52
129
  };
53
130
  export type ReservationStatus = 'cancelled' | 'completed' | 'confirmed' | 'no-show' | 'pending';
54
131
  export type DayOfWeek = 'fri' | 'mon' | 'sat' | 'sun' | 'thu' | 'tue' | 'wed';
55
132
  export type ScheduleType = 'manual' | 'recurring';
133
+ /** @deprecated Use DEFAULT_STATUS_MACHINE.transitions instead */
56
134
  export declare const VALID_STATUS_TRANSITIONS: Record<ReservationStatus, ReservationStatus[]>;
package/dist/types.js CHANGED
@@ -1,16 +1,36 @@
1
- export const VALID_STATUS_TRANSITIONS = {
2
- cancelled: [],
3
- completed: [],
4
- confirmed: [
1
+ export const DEFAULT_STATUS_MACHINE = {
2
+ blockingStatuses: [
3
+ 'pending',
4
+ 'confirmed'
5
+ ],
6
+ defaultStatus: 'pending',
7
+ statuses: [
8
+ 'pending',
9
+ 'confirmed',
5
10
  'completed',
6
11
  'cancelled',
7
12
  'no-show'
8
13
  ],
9
- 'no-show': [],
10
- pending: [
11
- 'confirmed',
12
- 'cancelled'
13
- ]
14
+ terminalStatuses: [
15
+ 'completed',
16
+ 'cancelled',
17
+ 'no-show'
18
+ ],
19
+ transitions: {
20
+ cancelled: [],
21
+ completed: [],
22
+ confirmed: [
23
+ 'completed',
24
+ 'cancelled',
25
+ 'no-show'
26
+ ],
27
+ 'no-show': [],
28
+ pending: [
29
+ 'confirmed',
30
+ 'cancelled'
31
+ ]
32
+ }
14
33
  };
34
+ /** @deprecated Use DEFAULT_STATUS_MACHINE.transitions instead */ export const VALID_STATUS_TRANSITIONS = DEFAULT_STATUS_MACHINE.transitions;
15
35
 
16
36
  //# sourceMappingURL=types.js.map
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { CollectionConfig } from 'payload'\n\nexport type ReservationPluginConfig = {\n /** Override access control per collection */\n access?: {\n reservations?: CollectionConfig['access']\n resources?: CollectionConfig['access']\n schedules?: CollectionConfig['access']\n services?: CollectionConfig['access']\n }\n /** Admin group name for all reservation collections */\n adminGroup?: string\n /** Hours of notice required before cancellation */\n cancellationNoticePeriod?: number\n /** Role to filter customers by in the reservation form. Set false to disable filtering. (default: 'customer') */\n customerRole?: false | string\n /** Default buffer time in minutes between reservations */\n defaultBufferTime?: number\n /** Disable the plugin entirely */\n disabled?: boolean\n /** Override collection slugs */\n slugs?: {\n media?: string\n reservations?: string\n resources?: string\n schedules?: string\n services?: string\n }\n /** Slug of the existing auth collection to extend with customer fields (default: 'users') */\n userCollection?: string\n}\n\nexport type ResolvedReservationPluginConfig = {\n access: {\n reservations?: CollectionConfig['access']\n resources?: CollectionConfig['access']\n schedules?: CollectionConfig['access']\n services?: CollectionConfig['access']\n }\n adminGroup: string\n cancellationNoticePeriod: number\n customerRole: false | string\n defaultBufferTime: number\n disabled: boolean\n localized: boolean\n slugs: {\n media: string\n reservations: string\n resources: string\n schedules: string\n services: string\n }\n userCollection: string\n}\n\nexport type ReservationStatus = 'cancelled' | 'completed' | 'confirmed' | 'no-show' | 'pending'\n\nexport type DayOfWeek = 'fri' | 'mon' | 'sat' | 'sun' | 'thu' | 'tue' | 'wed'\n\nexport type ScheduleType = 'manual' | 'recurring'\n\nexport const VALID_STATUS_TRANSITIONS: Record<ReservationStatus, ReservationStatus[]> = {\n cancelled: [],\n completed: [],\n confirmed: ['completed', 'cancelled', 'no-show'],\n 'no-show': [],\n pending: ['confirmed', 'cancelled'],\n}\n"],"names":["VALID_STATUS_TRANSITIONS","cancelled","completed","confirmed","pending"],"mappings":"AA6DA,OAAO,MAAMA,2BAA2E;IACtFC,WAAW,EAAE;IACbC,WAAW,EAAE;IACbC,WAAW;QAAC;QAAa;QAAa;KAAU;IAChD,WAAW,EAAE;IACbC,SAAS;QAAC;QAAa;KAAY;AACrC,EAAC"}
1
+ {"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { CollectionConfig, Field, PayloadRequest } from 'payload'\n\n// --- Duration & Capacity models ---\n\nexport type DurationType = 'fixed' | 'flexible' | 'full-day'\n\nexport type CapacityMode = 'per-guest' | 'per-reservation'\n\n// --- Configurable status machine ---\n\nexport type StatusMachineConfig = {\n blockingStatuses: string[]\n defaultStatus: string\n statuses: string[]\n terminalStatuses: string[]\n transitions: Record<string, string[]>\n}\n\nexport const DEFAULT_STATUS_MACHINE: StatusMachineConfig = {\n blockingStatuses: ['pending', 'confirmed'],\n defaultStatus: 'pending',\n statuses: ['pending', 'confirmed', 'completed', 'cancelled', 'no-show'],\n terminalStatuses: ['completed', 'cancelled', 'no-show'],\n transitions: {\n cancelled: [],\n completed: [],\n confirmed: ['completed', 'cancelled', 'no-show'],\n 'no-show': [],\n pending: ['confirmed', 'cancelled'],\n },\n}\n\n// --- Reservation item (for multi-resource bookings, Phase 3) ---\n\nexport type ReservationItemConfig = {\n endTime?: string\n guestCount?: number\n resource: string\n service?: string\n startTime?: string\n}\n\n// --- Plugin hooks for external integrations ---\n\nexport type ReservationPluginHooks = {\n afterBookingCancel?: Array<\n (args: { doc: Record<string, unknown>; req: PayloadRequest }) => Promise<void> | void\n >\n afterBookingConfirm?: Array<\n (args: { doc: Record<string, unknown>; req: PayloadRequest }) => Promise<void> | void\n >\n afterBookingCreate?: Array<\n (args: { doc: Record<string, unknown>; req: PayloadRequest }) => Promise<void> | void\n >\n afterStatusChange?: Array<\n (args: {\n doc: Record<string, unknown>\n newStatus: string\n previousStatus: string\n req: PayloadRequest\n }) => Promise<void> | void\n >\n beforeBookingCancel?: Array<\n (args: {\n doc: Record<string, unknown>\n reason?: string\n req: PayloadRequest\n }) => Promise<void> | void\n >\n beforeBookingConfirm?: Array<\n (args: {\n doc: Record<string, unknown>\n newStatus: string\n req: PayloadRequest\n }) => Promise<void> | void\n >\n beforeBookingCreate?: Array<\n (args: {\n data: Record<string, unknown>\n req: PayloadRequest\n }) => Promise<Record<string, unknown>> | Record<string, unknown>\n >\n}\n\n// --- Resource owner mode ---\n\nexport type ResourceOwnerModeConfig = {\n /** Roles that can see all records (default: check req.user.collection === adminCollection) */\n adminRoles?: string[]\n /** Whether Services also get an owner field (default: false — Services are platform-managed) */\n ownedServices?: boolean\n /** Field name for the owner relationship on Resources (default: 'owner') */\n ownerField?: string\n}\n\nexport type ResolvedResourceOwnerModeConfig = {\n adminRoles: string[]\n ownedServices: boolean\n ownerField: string\n}\n\n// --- Plugin configuration ---\n\nexport type ReservationPluginConfig = {\n /** Override access control per collection */\n access?: {\n customers?: CollectionConfig['access']\n reservations?: CollectionConfig['access']\n resources?: CollectionConfig['access']\n schedules?: CollectionConfig['access']\n services?: CollectionConfig['access']\n }\n /** Admin group name for all reservation collections */\n adminGroup?: string\n /** Hours of notice required before cancellation */\n cancellationNoticePeriod?: number\n /** Default buffer time in minutes between reservations */\n defaultBufferTime?: number\n /** Disable the plugin entirely */\n disabled?: boolean\n /** Extra fields to append to the Reservations collection */\n extraReservationFields?: Field[]\n /** Plugin hooks for external integrations */\n hooks?: ReservationPluginHooks\n /** Enable resource-owner multi-tenancy (opt-in) */\n resourceOwnerMode?: ResourceOwnerModeConfig\n /** Override collection slugs */\n slugs?: {\n customers?: string\n media?: string\n reservations?: string\n resources?: string\n schedules?: string\n services?: string\n }\n /** Configurable status machine (defaults to current behavior) */\n statusMachine?: Partial<StatusMachineConfig>\n /** Which existing auth collection to extend with customer fields */\n userCollection?: string\n}\n\nexport type ResolvedReservationPluginConfig = {\n access: {\n customers?: CollectionConfig['access']\n reservations?: CollectionConfig['access']\n resources?: CollectionConfig['access']\n schedules?: CollectionConfig['access']\n services?: CollectionConfig['access']\n }\n adminGroup: string\n cancellationNoticePeriod: number\n defaultBufferTime: number\n disabled: boolean\n extraReservationFields: Field[]\n hooks: ReservationPluginHooks\n localized: boolean\n resourceOwnerMode: ResolvedResourceOwnerModeConfig | undefined\n slugs: {\n customers: string\n media: string\n reservations: string\n resources: string\n schedules: string\n services: string\n }\n statusMachine: StatusMachineConfig\n userCollection: string | undefined\n}\n\nexport type ReservationStatus = 'cancelled' | 'completed' | 'confirmed' | 'no-show' | 'pending'\n\nexport type DayOfWeek = 'fri' | 'mon' | 'sat' | 'sun' | 'thu' | 'tue' | 'wed'\n\nexport type ScheduleType = 'manual' | 'recurring'\n\n/** @deprecated Use DEFAULT_STATUS_MACHINE.transitions instead */\nexport const VALID_STATUS_TRANSITIONS: Record<ReservationStatus, ReservationStatus[]> =\n DEFAULT_STATUS_MACHINE.transitions as Record<ReservationStatus, ReservationStatus[]>\n"],"names":["DEFAULT_STATUS_MACHINE","blockingStatuses","defaultStatus","statuses","terminalStatuses","transitions","cancelled","completed","confirmed","pending","VALID_STATUS_TRANSITIONS"],"mappings":"AAkBA,OAAO,MAAMA,yBAA8C;IACzDC,kBAAkB;QAAC;QAAW;KAAY;IAC1CC,eAAe;IACfC,UAAU;QAAC;QAAW;QAAa;QAAa;QAAa;KAAU;IACvEC,kBAAkB;QAAC;QAAa;QAAa;KAAU;IACvDC,aAAa;QACXC,WAAW,EAAE;QACbC,WAAW,EAAE;QACbC,WAAW;YAAC;YAAa;YAAa;SAAU;QAChD,WAAW,EAAE;QACbC,SAAS;YAAC;YAAa;SAAY;IACrC;AACF,EAAC;AAiJD,+DAA+D,GAC/D,OAAO,MAAMC,2BACXV,uBAAuBK,WAAW,CAAkD"}
@@ -0,0 +1,24 @@
1
+ import type { CollectionConfig } from 'payload';
2
+ import type { ResolvedResourceOwnerModeConfig } from '../types.js';
3
+ type CollectionAccess = NonNullable<CollectionConfig['access']>;
4
+ /**
5
+ * Access factories for Resources collection.
6
+ * Owners may read/update/delete their own resources; anyone authenticated may create.
7
+ */
8
+ export declare function makeResourceOwnerAccess(rom: ResolvedResourceOwnerModeConfig): CollectionAccess;
9
+ /**
10
+ * Access factories for Schedules collection.
11
+ * A schedule's ownership is determined through its `resource.owner` relationship.
12
+ */
13
+ export declare function makeScheduleOwnerAccess(rom: ResolvedResourceOwnerModeConfig): CollectionAccess;
14
+ /**
15
+ * Access factories for Reservations collection.
16
+ * Resource owners can see reservations for their resources (read-only);
17
+ * mutations are admin-only to prevent owners from unilaterally cancelling guest bookings.
18
+ */
19
+ export declare function makeReservationOwnerAccess(rom: ResolvedResourceOwnerModeConfig): CollectionAccess;
20
+ /**
21
+ * Access factories for Services collection when `ownedServices: true`.
22
+ */
23
+ export declare function makeServiceOwnerAccess(rom: ResolvedResourceOwnerModeConfig, ownerField: string): CollectionAccess;
24
+ export {};