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,3 @@
1
+ import type { CollectionAfterChangeHook } from 'payload';
2
+ import type { ResolvedReservationPluginConfig } from '../../types.js';
3
+ export declare const onStatusChange: (config: ResolvedReservationPluginConfig) => CollectionAfterChangeHook;
@@ -0,0 +1,41 @@
1
+ export const onStatusChange = (config)=>async ({ context, doc, previousDoc, req })=>{
2
+ if (context?.skipReservationHooks) {
3
+ return doc;
4
+ }
5
+ if (!previousDoc || previousDoc.status === doc.status) {
6
+ return doc;
7
+ }
8
+ const prev = previousDoc.status;
9
+ const next = doc.status;
10
+ // Call generic afterStatusChange plugin hooks
11
+ if (config.hooks?.afterStatusChange) {
12
+ for (const hook of config.hooks.afterStatusChange){
13
+ await hook({
14
+ doc: doc,
15
+ newStatus: next,
16
+ previousStatus: prev,
17
+ req
18
+ });
19
+ }
20
+ }
21
+ // Call specific hooks based on transition
22
+ if (next === 'confirmed' && config.hooks?.afterBookingConfirm) {
23
+ for (const hook of config.hooks.afterBookingConfirm){
24
+ await hook({
25
+ doc: doc,
26
+ req
27
+ });
28
+ }
29
+ }
30
+ if (next === 'cancelled' && config.hooks?.afterBookingCancel) {
31
+ for (const hook of config.hooks.afterBookingCancel){
32
+ await hook({
33
+ doc: doc,
34
+ req
35
+ });
36
+ }
37
+ }
38
+ return doc;
39
+ };
40
+
41
+ //# sourceMappingURL=onStatusChange.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/hooks/reservations/onStatusChange.ts"],"sourcesContent":["import type { CollectionAfterChangeHook } from 'payload'\n\nimport type { ResolvedReservationPluginConfig } from '../../types.js'\n\nexport const onStatusChange =\n (config: ResolvedReservationPluginConfig): CollectionAfterChangeHook =>\n async ({ context, doc, previousDoc, req }) => {\n if (context?.skipReservationHooks) {return doc}\n if (!previousDoc || previousDoc.status === doc.status) {return doc}\n\n const prev = previousDoc.status as string\n const next = doc.status as string\n\n // Call generic afterStatusChange plugin hooks\n if (config.hooks?.afterStatusChange) {\n for (const hook of config.hooks.afterStatusChange) {\n await hook({ doc: doc as Record<string, unknown>, newStatus: next, previousStatus: prev, req })\n }\n }\n\n // Call specific hooks based on transition\n if (next === 'confirmed' && config.hooks?.afterBookingConfirm) {\n for (const hook of config.hooks.afterBookingConfirm) {\n await hook({ doc: doc as Record<string, unknown>, req })\n }\n }\n if (next === 'cancelled' && config.hooks?.afterBookingCancel) {\n for (const hook of config.hooks.afterBookingCancel) {\n await hook({ doc: doc as Record<string, unknown>, req })\n }\n }\n\n return doc\n }\n"],"names":["onStatusChange","config","context","doc","previousDoc","req","skipReservationHooks","status","prev","next","hooks","afterStatusChange","hook","newStatus","previousStatus","afterBookingConfirm","afterBookingCancel"],"mappings":"AAIA,OAAO,MAAMA,iBACX,CAACC,SACD,OAAO,EAAEC,OAAO,EAAEC,GAAG,EAAEC,WAAW,EAAEC,GAAG,EAAE;QACvC,IAAIH,SAASI,sBAAsB;YAAC,OAAOH;QAAG;QAC9C,IAAI,CAACC,eAAeA,YAAYG,MAAM,KAAKJ,IAAII,MAAM,EAAE;YAAC,OAAOJ;QAAG;QAElE,MAAMK,OAAOJ,YAAYG,MAAM;QAC/B,MAAME,OAAON,IAAII,MAAM;QAEvB,8CAA8C;QAC9C,IAAIN,OAAOS,KAAK,EAAEC,mBAAmB;YACnC,KAAK,MAAMC,QAAQX,OAAOS,KAAK,CAACC,iBAAiB,CAAE;gBACjD,MAAMC,KAAK;oBAAET,KAAKA;oBAAgCU,WAAWJ;oBAAMK,gBAAgBN;oBAAMH;gBAAI;YAC/F;QACF;QAEA,0CAA0C;QAC1C,IAAII,SAAS,eAAeR,OAAOS,KAAK,EAAEK,qBAAqB;YAC7D,KAAK,MAAMH,QAAQX,OAAOS,KAAK,CAACK,mBAAmB,CAAE;gBACnD,MAAMH,KAAK;oBAAET,KAAKA;oBAAgCE;gBAAI;YACxD;QACF;QACA,IAAII,SAAS,eAAeR,OAAOS,KAAK,EAAEM,oBAAoB;YAC5D,KAAK,MAAMJ,QAAQX,OAAOS,KAAK,CAACM,kBAAkB,CAAE;gBAClD,MAAMJ,KAAK;oBAAET,KAAKA;oBAAgCE;gBAAI;YACxD;QACF;QAEA,OAAOF;IACT,EAAC"}
@@ -19,7 +19,7 @@ export const validateCancellation = (config)=>({ context, data, operation, origi
19
19
  }
20
20
  const startDate = new Date(startTime);
21
21
  const hours = hoursUntil(startDate);
22
- if (hours < config.cancellationNoticePeriod) {
22
+ if (hours > 0 && hours < config.cancellationNoticePeriod) {
23
23
  throw new ValidationError({
24
24
  errors: [
25
25
  {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/hooks/reservations/validateCancellation.ts"],"sourcesContent":["import type { CollectionBeforeChangeHook } from 'payload'\n\nimport { ValidationError } from 'payload'\n\nimport type { PluginT } from '../../translations/index.js'\nimport type { ResolvedReservationPluginConfig } from '../../types.js'\n\nimport { hoursUntil } from '../../utilities/slotUtils.js'\n\nexport const validateCancellation =\n (config: ResolvedReservationPluginConfig): CollectionBeforeChangeHook =>\n ({ context, data, operation, originalDoc, req }) => {\n if (context?.skipReservationHooks) {return data}\n\n if (operation !== 'update') {return data}\n\n const newStatus = data?.status\n const previousStatus = originalDoc?.status\n\n // Only check when transitioning to cancelled\n if (newStatus !== 'cancelled' || previousStatus === 'cancelled') {return data}\n\n const startTime = data?.startTime ?? originalDoc?.startTime\n if (!startTime) {return data}\n\n const startDate = new Date(startTime)\n const hours = hoursUntil(startDate)\n\n if (hours < config.cancellationNoticePeriod) {\n throw new ValidationError({\n errors: [\n {\n message: (req.t as PluginT)('reservation:errorCancellationNotice', {\n hours: String(Math.round(hours)),\n period: String(config.cancellationNoticePeriod),\n }),\n path: 'status',\n },\n ],\n })\n }\n\n return data\n }\n"],"names":["ValidationError","hoursUntil","validateCancellation","config","context","data","operation","originalDoc","req","skipReservationHooks","newStatus","status","previousStatus","startTime","startDate","Date","hours","cancellationNoticePeriod","errors","message","t","String","Math","round","period","path"],"mappings":"AAEA,SAASA,eAAe,QAAQ,UAAS;AAKzC,SAASC,UAAU,QAAQ,+BAA8B;AAEzD,OAAO,MAAMC,uBACX,CAACC,SACD,CAAC,EAAEC,OAAO,EAAEC,IAAI,EAAEC,SAAS,EAAEC,WAAW,EAAEC,GAAG,EAAE;QAC7C,IAAIJ,SAASK,sBAAsB;YAAC,OAAOJ;QAAI;QAE/C,IAAIC,cAAc,UAAU;YAAC,OAAOD;QAAI;QAExC,MAAMK,YAAYL,MAAMM;QACxB,MAAMC,iBAAiBL,aAAaI;QAEpC,6CAA6C;QAC7C,IAAID,cAAc,eAAeE,mBAAmB,aAAa;YAAC,OAAOP;QAAI;QAE7E,MAAMQ,YAAYR,MAAMQ,aAAaN,aAAaM;QAClD,IAAI,CAACA,WAAW;YAAC,OAAOR;QAAI;QAE5B,MAAMS,YAAY,IAAIC,KAAKF;QAC3B,MAAMG,QAAQf,WAAWa;QAEzB,IAAIE,QAAQb,OAAOc,wBAAwB,EAAE;YAC3C,MAAM,IAAIjB,gBAAgB;gBACxBkB,QAAQ;oBACN;wBACEC,SAAS,AAACX,IAAIY,CAAC,CAAa,uCAAuC;4BACjEJ,OAAOK,OAAOC,KAAKC,KAAK,CAACP;4BACzBQ,QAAQH,OAAOlB,OAAOc,wBAAwB;wBAChD;wBACAQ,MAAM;oBACR;iBACD;YACH;QACF;QAEA,OAAOpB;IACT,EAAC"}
1
+ {"version":3,"sources":["../../../src/hooks/reservations/validateCancellation.ts"],"sourcesContent":["import type { CollectionBeforeChangeHook } from 'payload'\n\nimport { ValidationError } from 'payload'\n\nimport type { PluginT } from '../../translations/index.js'\nimport type { ResolvedReservationPluginConfig } from '../../types.js'\n\nimport { hoursUntil } from '../../utilities/slotUtils.js'\n\nexport const validateCancellation =\n (config: ResolvedReservationPluginConfig): CollectionBeforeChangeHook =>\n ({ context, data, operation, originalDoc, req }) => {\n if (context?.skipReservationHooks) {return data}\n\n if (operation !== 'update') {return data}\n\n const newStatus = data?.status\n const previousStatus = originalDoc?.status\n\n // Only check when transitioning to cancelled\n if (newStatus !== 'cancelled' || previousStatus === 'cancelled') {return data}\n\n const startTime = data?.startTime ?? originalDoc?.startTime\n if (!startTime) {return data}\n\n const startDate = new Date(startTime)\n const hours = hoursUntil(startDate)\n\n if (hours > 0 && hours < config.cancellationNoticePeriod) {\n throw new ValidationError({\n errors: [\n {\n message: (req.t as PluginT)('reservation:errorCancellationNotice', {\n hours: String(Math.round(hours)),\n period: String(config.cancellationNoticePeriod),\n }),\n path: 'status',\n },\n ],\n })\n }\n\n return data\n }\n"],"names":["ValidationError","hoursUntil","validateCancellation","config","context","data","operation","originalDoc","req","skipReservationHooks","newStatus","status","previousStatus","startTime","startDate","Date","hours","cancellationNoticePeriod","errors","message","t","String","Math","round","period","path"],"mappings":"AAEA,SAASA,eAAe,QAAQ,UAAS;AAKzC,SAASC,UAAU,QAAQ,+BAA8B;AAEzD,OAAO,MAAMC,uBACX,CAACC,SACD,CAAC,EAAEC,OAAO,EAAEC,IAAI,EAAEC,SAAS,EAAEC,WAAW,EAAEC,GAAG,EAAE;QAC7C,IAAIJ,SAASK,sBAAsB;YAAC,OAAOJ;QAAI;QAE/C,IAAIC,cAAc,UAAU;YAAC,OAAOD;QAAI;QAExC,MAAMK,YAAYL,MAAMM;QACxB,MAAMC,iBAAiBL,aAAaI;QAEpC,6CAA6C;QAC7C,IAAID,cAAc,eAAeE,mBAAmB,aAAa;YAAC,OAAOP;QAAI;QAE7E,MAAMQ,YAAYR,MAAMQ,aAAaN,aAAaM;QAClD,IAAI,CAACA,WAAW;YAAC,OAAOR;QAAI;QAE5B,MAAMS,YAAY,IAAIC,KAAKF;QAC3B,MAAMG,QAAQf,WAAWa;QAEzB,IAAIE,QAAQ,KAAKA,QAAQb,OAAOc,wBAAwB,EAAE;YACxD,MAAM,IAAIjB,gBAAgB;gBACxBkB,QAAQ;oBACN;wBACEC,SAAS,AAACX,IAAIY,CAAC,CAAa,uCAAuC;4BACjEJ,OAAOK,OAAOC,KAAKC,KAAK,CAACP;4BACzBQ,QAAQH,OAAOlB,OAAOc,wBAAwB;wBAChD;wBACAQ,MAAM;oBACR;iBACD;YACH;QACF;QAEA,OAAOpB;IACT,EAAC"}
@@ -1,17 +1,21 @@
1
1
  import { ValidationError } from 'payload';
2
- import { computeBlockedWindow } from '../../utilities/slotUtils.js';
2
+ import { checkAvailability } from '../../services/AvailabilityService.js';
3
+ import { resolveReservationItems } from '../../utilities/resolveReservationItems.js';
3
4
  export const validateConflicts = (config)=>async ({ context, data, operation, originalDoc, req })=>{
4
5
  if (context?.skipReservationHooks) {
5
6
  return data;
6
7
  }
7
- if (!data?.startTime || !data?.endTime || !data?.resource) {
8
+ const items = resolveReservationItems(data);
9
+ if (items.length === 0) {
8
10
  return data;
9
11
  }
10
- const serviceId = typeof data.service === 'object' ? data.service.id : data.service;
12
+ // Fetch buffer times from the primary service
13
+ const serviceId = typeof data?.service === 'object' ? data.service.id : data?.service;
11
14
  let bufferBefore = config.defaultBufferTime;
12
15
  let bufferAfter = config.defaultBufferTime;
13
16
  if (serviceId) {
14
17
  try {
18
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
19
  const service = await req.payload.findByID({
16
20
  id: serviceId,
17
21
  collection: config.slugs.services,
@@ -25,60 +29,34 @@ export const validateConflicts = (config)=>async ({ context, data, operation, or
25
29
  // Use defaults if service lookup fails
26
30
  }
27
31
  }
28
- const startTime = new Date(data.startTime);
29
- const endTime = new Date(data.endTime);
30
- const { effectiveEnd, effectiveStart } = computeBlockedWindow(startTime, endTime, bufferBefore, bufferAfter);
31
- const resourceId = typeof data.resource === 'object' ? data.resource.id : data.resource;
32
- const where = {
33
- and: [
34
- {
35
- resource: {
36
- equals: resourceId
37
- }
38
- },
39
- {
40
- status: {
41
- not_in: [
42
- 'cancelled',
43
- 'no-show'
44
- ]
45
- }
46
- },
47
- {
48
- startTime: {
49
- less_than: effectiveEnd.toISOString()
50
- }
51
- },
52
- {
53
- endTime: {
54
- greater_than: effectiveStart.toISOString()
55
- }
56
- }
57
- ]
58
- };
59
- // Exclude self on update
60
- if (operation === 'update' && originalDoc?.id) {
61
- ;
62
- where.and.push({
63
- id: {
64
- not_equals: originalDoc.id
65
- }
66
- });
67
- }
68
- const { totalDocs } = await req.payload.count({
69
- collection: config.slugs.reservations,
70
- req,
71
- where
72
- });
73
- if (totalDocs > 0) {
74
- throw new ValidationError({
75
- errors: [
76
- {
77
- message: req.t('reservation:errorConflict'),
78
- path: 'startTime'
79
- }
80
- ]
32
+ for (const item of items){
33
+ if (!item.endTime) {
34
+ continue;
35
+ }
36
+ const result = await checkAvailability({
37
+ blockingStatuses: config.statusMachine.blockingStatuses,
38
+ bufferAfter,
39
+ bufferBefore,
40
+ endTime: new Date(item.endTime),
41
+ excludeReservationId: operation === 'update' ? originalDoc?.id : undefined,
42
+ guestCount: item.guestCount,
43
+ payload: req.payload,
44
+ req,
45
+ reservationSlug: config.slugs.reservations,
46
+ resourceId: item.resource,
47
+ resourceSlug: config.slugs.resources,
48
+ startTime: new Date(item.startTime)
81
49
  });
50
+ if (!result.available) {
51
+ throw new ValidationError({
52
+ errors: [
53
+ {
54
+ message: result.reason ?? req.t('reservation:errorConflict'),
55
+ path: 'startTime'
56
+ }
57
+ ]
58
+ });
59
+ }
82
60
  }
83
61
  return data;
84
62
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/hooks/reservations/validateConflicts.ts"],"sourcesContent":["import type { CollectionBeforeChangeHook, Where } from 'payload'\n\nimport { ValidationError } from 'payload'\n\nimport type { PluginT } from '../../translations/index.js'\nimport type { ResolvedReservationPluginConfig } from '../../types.js'\n\nimport { computeBlockedWindow } from '../../utilities/slotUtils.js'\n\nexport const validateConflicts =\n (config: ResolvedReservationPluginConfig): CollectionBeforeChangeHook =>\n async ({ context, data, operation, originalDoc, req }) => {\n if (context?.skipReservationHooks) {return data}\n\n if (!data?.startTime || !data?.endTime || !data?.resource) {return data}\n\n const serviceId = typeof data.service === 'object' ? data.service.id : data.service\n\n let bufferBefore = config.defaultBufferTime\n let bufferAfter = config.defaultBufferTime\n\n if (serviceId) {\n try {\n const service = await req.payload.findByID({\n id: serviceId,\n collection: config.slugs.services as 'services',\n req,\n })\n if (service) {\n bufferBefore = (service.bufferTimeBefore as number) ?? config.defaultBufferTime\n bufferAfter = (service.bufferTimeAfter as number) ?? config.defaultBufferTime\n }\n } catch {\n // Use defaults if service lookup fails\n }\n }\n\n const startTime = new Date(data.startTime)\n const endTime = new Date(data.endTime)\n const { effectiveEnd, effectiveStart } = computeBlockedWindow(\n startTime,\n endTime,\n bufferBefore,\n bufferAfter,\n )\n\n const resourceId = typeof data.resource === 'object' ? data.resource.id : data.resource\n\n const where: Where = {\n and: [\n { resource: { equals: resourceId } },\n {\n status: {\n not_in: ['cancelled', 'no-show'],\n },\n },\n { startTime: { less_than: effectiveEnd.toISOString() } },\n { endTime: { greater_than: effectiveStart.toISOString() } },\n ],\n }\n\n // Exclude self on update\n if (operation === 'update' && originalDoc?.id) {\n ;(where.and as Where[]).push({ id: { not_equals: originalDoc.id } })\n }\n\n const { totalDocs } = await req.payload.count({\n collection: config.slugs.reservations as 'reservations',\n req,\n where,\n })\n\n if (totalDocs > 0) {\n throw new ValidationError({\n errors: [\n {\n message: (req.t as PluginT)('reservation:errorConflict'),\n path: 'startTime',\n },\n ],\n })\n }\n\n return data\n }\n"],"names":["ValidationError","computeBlockedWindow","validateConflicts","config","context","data","operation","originalDoc","req","skipReservationHooks","startTime","endTime","resource","serviceId","service","id","bufferBefore","defaultBufferTime","bufferAfter","payload","findByID","collection","slugs","services","bufferTimeBefore","bufferTimeAfter","Date","effectiveEnd","effectiveStart","resourceId","where","and","equals","status","not_in","less_than","toISOString","greater_than","push","not_equals","totalDocs","count","reservations","errors","message","t","path"],"mappings":"AAEA,SAASA,eAAe,QAAQ,UAAS;AAKzC,SAASC,oBAAoB,QAAQ,+BAA8B;AAEnE,OAAO,MAAMC,oBACX,CAACC,SACD,OAAO,EAAEC,OAAO,EAAEC,IAAI,EAAEC,SAAS,EAAEC,WAAW,EAAEC,GAAG,EAAE;QACnD,IAAIJ,SAASK,sBAAsB;YAAC,OAAOJ;QAAI;QAE/C,IAAI,CAACA,MAAMK,aAAa,CAACL,MAAMM,WAAW,CAACN,MAAMO,UAAU;YAAC,OAAOP;QAAI;QAEvE,MAAMQ,YAAY,OAAOR,KAAKS,OAAO,KAAK,WAAWT,KAAKS,OAAO,CAACC,EAAE,GAAGV,KAAKS,OAAO;QAEnF,IAAIE,eAAeb,OAAOc,iBAAiB;QAC3C,IAAIC,cAAcf,OAAOc,iBAAiB;QAE1C,IAAIJ,WAAW;YACb,IAAI;gBACF,MAAMC,UAAU,MAAMN,IAAIW,OAAO,CAACC,QAAQ,CAAC;oBACzCL,IAAIF;oBACJQ,YAAYlB,OAAOmB,KAAK,CAACC,QAAQ;oBACjCf;gBACF;gBACA,IAAIM,SAAS;oBACXE,eAAe,AAACF,QAAQU,gBAAgB,IAAerB,OAAOc,iBAAiB;oBAC/EC,cAAc,AAACJ,QAAQW,eAAe,IAAetB,OAAOc,iBAAiB;gBAC/E;YACF,EAAE,OAAM;YACN,uCAAuC;YACzC;QACF;QAEA,MAAMP,YAAY,IAAIgB,KAAKrB,KAAKK,SAAS;QACzC,MAAMC,UAAU,IAAIe,KAAKrB,KAAKM,OAAO;QACrC,MAAM,EAAEgB,YAAY,EAAEC,cAAc,EAAE,GAAG3B,qBACvCS,WACAC,SACAK,cACAE;QAGF,MAAMW,aAAa,OAAOxB,KAAKO,QAAQ,KAAK,WAAWP,KAAKO,QAAQ,CAACG,EAAE,GAAGV,KAAKO,QAAQ;QAEvF,MAAMkB,QAAe;YACnBC,KAAK;gBACH;oBAAEnB,UAAU;wBAAEoB,QAAQH;oBAAW;gBAAE;gBACnC;oBACEI,QAAQ;wBACNC,QAAQ;4BAAC;4BAAa;yBAAU;oBAClC;gBACF;gBACA;oBAAExB,WAAW;wBAAEyB,WAAWR,aAAaS,WAAW;oBAAG;gBAAE;gBACvD;oBAAEzB,SAAS;wBAAE0B,cAAcT,eAAeQ,WAAW;oBAAG;gBAAE;aAC3D;QACH;QAEA,yBAAyB;QACzB,IAAI9B,cAAc,YAAYC,aAAaQ,IAAI;;YAC3Ce,MAAMC,GAAG,CAAaO,IAAI,CAAC;gBAAEvB,IAAI;oBAAEwB,YAAYhC,YAAYQ,EAAE;gBAAC;YAAE;QACpE;QAEA,MAAM,EAAEyB,SAAS,EAAE,GAAG,MAAMhC,IAAIW,OAAO,CAACsB,KAAK,CAAC;YAC5CpB,YAAYlB,OAAOmB,KAAK,CAACoB,YAAY;YACrClC;YACAsB;QACF;QAEA,IAAIU,YAAY,GAAG;YACjB,MAAM,IAAIxC,gBAAgB;gBACxB2C,QAAQ;oBACN;wBACEC,SAAS,AAACpC,IAAIqC,CAAC,CAAa;wBAC5BC,MAAM;oBACR;iBACD;YACH;QACF;QAEA,OAAOzC;IACT,EAAC"}
1
+ {"version":3,"sources":["../../../src/hooks/reservations/validateConflicts.ts"],"sourcesContent":["import type { CollectionBeforeChangeHook } from 'payload'\n\nimport { ValidationError } from 'payload'\n\nimport type { PluginT } from '../../translations/index.js'\nimport type { ResolvedReservationPluginConfig } from '../../types.js'\n\nimport { checkAvailability } from '../../services/AvailabilityService.js'\nimport { resolveReservationItems } from '../../utilities/resolveReservationItems.js'\n\nexport const validateConflicts =\n (config: ResolvedReservationPluginConfig): CollectionBeforeChangeHook =>\n async ({ context, data, operation, originalDoc, req }) => {\n if (context?.skipReservationHooks) {return data}\n\n const items = resolveReservationItems(data as Record<string, unknown>)\n\n if (items.length === 0) {return data}\n\n // Fetch buffer times from the primary service\n const serviceId = typeof data?.service === 'object' ? data.service.id : data?.service\n let bufferBefore = config.defaultBufferTime\n let bufferAfter = config.defaultBufferTime\n\n if (serviceId) {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const service = await (req.payload.findByID as any)({\n id: serviceId,\n collection: config.slugs.services,\n req,\n })\n if (service) {\n bufferBefore = (service.bufferTimeBefore as number) ?? config.defaultBufferTime\n bufferAfter = (service.bufferTimeAfter as number) ?? config.defaultBufferTime\n }\n } catch {\n // Use defaults if service lookup fails\n }\n }\n\n for (const item of items) {\n if (!item.endTime) {continue}\n\n const result = await checkAvailability({\n blockingStatuses: config.statusMachine.blockingStatuses,\n bufferAfter,\n bufferBefore,\n endTime: new Date(item.endTime),\n excludeReservationId: operation === 'update' ? originalDoc?.id : undefined,\n guestCount: item.guestCount,\n payload: req.payload,\n req,\n reservationSlug: config.slugs.reservations,\n resourceId: item.resource,\n resourceSlug: config.slugs.resources,\n startTime: new Date(item.startTime),\n })\n\n if (!result.available) {\n throw new ValidationError({\n errors: [\n {\n message: result.reason ?? (req.t as PluginT)('reservation:errorConflict'),\n path: 'startTime',\n },\n ],\n })\n }\n }\n\n return data\n }\n"],"names":["ValidationError","checkAvailability","resolveReservationItems","validateConflicts","config","context","data","operation","originalDoc","req","skipReservationHooks","items","length","serviceId","service","id","bufferBefore","defaultBufferTime","bufferAfter","payload","findByID","collection","slugs","services","bufferTimeBefore","bufferTimeAfter","item","endTime","result","blockingStatuses","statusMachine","Date","excludeReservationId","undefined","guestCount","reservationSlug","reservations","resourceId","resource","resourceSlug","resources","startTime","available","errors","message","reason","t","path"],"mappings":"AAEA,SAASA,eAAe,QAAQ,UAAS;AAKzC,SAASC,iBAAiB,QAAQ,wCAAuC;AACzE,SAASC,uBAAuB,QAAQ,6CAA4C;AAEpF,OAAO,MAAMC,oBACX,CAACC,SACD,OAAO,EAAEC,OAAO,EAAEC,IAAI,EAAEC,SAAS,EAAEC,WAAW,EAAEC,GAAG,EAAE;QACnD,IAAIJ,SAASK,sBAAsB;YAAC,OAAOJ;QAAI;QAE/C,MAAMK,QAAQT,wBAAwBI;QAEtC,IAAIK,MAAMC,MAAM,KAAK,GAAG;YAAC,OAAON;QAAI;QAEpC,8CAA8C;QAC9C,MAAMO,YAAY,OAAOP,MAAMQ,YAAY,WAAWR,KAAKQ,OAAO,CAACC,EAAE,GAAGT,MAAMQ;QAC9E,IAAIE,eAAeZ,OAAOa,iBAAiB;QAC3C,IAAIC,cAAcd,OAAOa,iBAAiB;QAE1C,IAAIJ,WAAW;YACb,IAAI;gBACF,8DAA8D;gBAC9D,MAAMC,UAAU,MAAM,AAACL,IAAIU,OAAO,CAACC,QAAQ,CAAS;oBAClDL,IAAIF;oBACJQ,YAAYjB,OAAOkB,KAAK,CAACC,QAAQ;oBACjCd;gBACF;gBACA,IAAIK,SAAS;oBACXE,eAAe,AAACF,QAAQU,gBAAgB,IAAepB,OAAOa,iBAAiB;oBAC/EC,cAAc,AAACJ,QAAQW,eAAe,IAAerB,OAAOa,iBAAiB;gBAC/E;YACF,EAAE,OAAM;YACN,uCAAuC;YACzC;QACF;QAEA,KAAK,MAAMS,QAAQf,MAAO;YACxB,IAAI,CAACe,KAAKC,OAAO,EAAE;gBAAC;YAAQ;YAE5B,MAAMC,SAAS,MAAM3B,kBAAkB;gBACrC4B,kBAAkBzB,OAAO0B,aAAa,CAACD,gBAAgB;gBACvDX;gBACAF;gBACAW,SAAS,IAAII,KAAKL,KAAKC,OAAO;gBAC9BK,sBAAsBzB,cAAc,WAAWC,aAAaO,KAAKkB;gBACjEC,YAAYR,KAAKQ,UAAU;gBAC3Bf,SAASV,IAAIU,OAAO;gBACpBV;gBACA0B,iBAAiB/B,OAAOkB,KAAK,CAACc,YAAY;gBAC1CC,YAAYX,KAAKY,QAAQ;gBACzBC,cAAcnC,OAAOkB,KAAK,CAACkB,SAAS;gBACpCC,WAAW,IAAIV,KAAKL,KAAKe,SAAS;YACpC;YAEA,IAAI,CAACb,OAAOc,SAAS,EAAE;gBACrB,MAAM,IAAI1C,gBAAgB;oBACxB2C,QAAQ;wBACN;4BACEC,SAAShB,OAAOiB,MAAM,IAAI,AAACpC,IAAIqC,CAAC,CAAa;4BAC7CC,MAAM;wBACR;qBACD;gBACH;YACF;QACF;QAEA,OAAOzC;IACT,EAAC"}
@@ -1,2 +1,3 @@
1
1
  import type { CollectionBeforeChangeHook } from 'payload';
2
- export declare const validateStatusTransition: () => CollectionBeforeChangeHook;
2
+ import type { ResolvedReservationPluginConfig } from '../../types.js';
3
+ export declare const validateStatusTransition: (config: ResolvedReservationPluginConfig) => CollectionBeforeChangeHook;
@@ -1,17 +1,19 @@
1
1
  import { ValidationError } from 'payload';
2
- import { VALID_STATUS_TRANSITIONS } from '../../types.js';
3
- export const validateStatusTransition = ()=>({ context, data, operation, originalDoc, req })=>{
2
+ import { validateTransition } from '../../services/AvailabilityService.js';
3
+ export const validateStatusTransition = (config)=>async ({ context, data, operation, originalDoc, req })=>{
4
4
  if (context?.skipReservationHooks) {
5
5
  return data;
6
6
  }
7
7
  const newStatus = data?.status;
8
+ const { statusMachine } = config;
8
9
  if (operation === 'create') {
9
10
  const isAdmin = Boolean(req.user);
11
+ const defaultStatus = statusMachine.defaultStatus;
10
12
  const allowedOnCreate = isAdmin ? [
11
- 'pending',
13
+ defaultStatus,
12
14
  'confirmed'
13
15
  ] : [
14
- 'pending'
16
+ defaultStatus
15
17
  ];
16
18
  if (newStatus && !allowedOnCreate.includes(newStatus)) {
17
19
  const allowed = allowedOnCreate.map((s)=>`"${s}"`).join(' or ');
@@ -26,14 +28,15 @@ export const validateStatusTransition = ()=>({ context, data, operation, origina
26
28
  ]
27
29
  });
28
30
  }
31
+ // Call beforeBookingCreate hooks (handled by plugin hooks wrapper)
29
32
  return data;
30
33
  }
31
34
  // On update
32
35
  if (operation === 'update' && newStatus) {
33
36
  const previousStatus = originalDoc?.status;
34
37
  if (previousStatus && previousStatus !== newStatus) {
35
- const allowed = VALID_STATUS_TRANSITIONS[previousStatus];
36
- if (!allowed || !allowed.includes(newStatus)) {
38
+ const result = validateTransition(previousStatus, newStatus, statusMachine);
39
+ if (!result.valid) {
37
40
  throw new ValidationError({
38
41
  errors: [
39
42
  {
@@ -46,6 +49,26 @@ export const validateStatusTransition = ()=>({ context, data, operation, origina
46
49
  ]
47
50
  });
48
51
  }
52
+ // Call beforeBookingConfirm plugin hooks
53
+ if (newStatus === 'confirmed' && config.hooks?.beforeBookingConfirm) {
54
+ for (const hook of config.hooks.beforeBookingConfirm){
55
+ await hook({
56
+ doc: originalDoc,
57
+ newStatus,
58
+ req
59
+ });
60
+ }
61
+ }
62
+ // Call beforeBookingCancel plugin hooks
63
+ if (newStatus === 'cancelled' && config.hooks?.beforeBookingCancel) {
64
+ for (const hook of config.hooks.beforeBookingCancel){
65
+ await hook({
66
+ doc: originalDoc,
67
+ reason: data?.cancellationReason,
68
+ req
69
+ });
70
+ }
71
+ }
49
72
  }
50
73
  }
51
74
  return data;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/hooks/reservations/validateStatusTransition.ts"],"sourcesContent":["import type { CollectionBeforeChangeHook } from 'payload'\n\nimport { ValidationError } from 'payload'\n\nimport type { PluginT } from '../../translations/index.js'\nimport type { ReservationStatus } from '../../types.js'\n\nimport { VALID_STATUS_TRANSITIONS } from '../../types.js'\n\nexport const validateStatusTransition = (): CollectionBeforeChangeHook =>\n ({ context, data, operation, originalDoc, req }) => {\n if (context?.skipReservationHooks) {return data}\n\n const newStatus = data?.status as ReservationStatus | undefined\n\n if (operation === 'create') {\n const isAdmin = Boolean(req.user)\n const allowedOnCreate: ReservationStatus[] = isAdmin\n ? ['pending', 'confirmed']\n : ['pending']\n\n if (newStatus && !allowedOnCreate.includes(newStatus)) {\n const allowed = allowedOnCreate.map((s) => `\"${s}\"`).join(' or ')\n throw new ValidationError({\n errors: [\n {\n message: (req.t as PluginT)('reservation:errorInvalidCreateStatus', { allowed }),\n path: 'status',\n },\n ],\n })\n }\n return data\n }\n\n // On update\n if (operation === 'update' && newStatus) {\n const previousStatus = originalDoc?.status as ReservationStatus | undefined\n\n if (previousStatus && previousStatus !== newStatus) {\n const allowed = VALID_STATUS_TRANSITIONS[previousStatus]\n if (!allowed || !allowed.includes(newStatus)) {\n throw new ValidationError({\n errors: [\n {\n message: (req.t as PluginT)('reservation:errorInvalidTransition', {\n from: previousStatus,\n to: newStatus,\n }),\n path: 'status',\n },\n ],\n })\n }\n }\n }\n\n return data\n }\n"],"names":["ValidationError","VALID_STATUS_TRANSITIONS","validateStatusTransition","context","data","operation","originalDoc","req","skipReservationHooks","newStatus","status","isAdmin","Boolean","user","allowedOnCreate","includes","allowed","map","s","join","errors","message","t","path","previousStatus","from","to"],"mappings":"AAEA,SAASA,eAAe,QAAQ,UAAS;AAKzC,SAASC,wBAAwB,QAAQ,iBAAgB;AAEzD,OAAO,MAAMC,2BAA2B,IACtC,CAAC,EAAEC,OAAO,EAAEC,IAAI,EAAEC,SAAS,EAAEC,WAAW,EAAEC,GAAG,EAAE;QAC7C,IAAIJ,SAASK,sBAAsB;YAAC,OAAOJ;QAAI;QAE/C,MAAMK,YAAYL,MAAMM;QAExB,IAAIL,cAAc,UAAU;YAC1B,MAAMM,UAAUC,QAAQL,IAAIM,IAAI;YAChC,MAAMC,kBAAuCH,UACzC;gBAAC;gBAAW;aAAY,GACxB;gBAAC;aAAU;YAEf,IAAIF,aAAa,CAACK,gBAAgBC,QAAQ,CAACN,YAAY;gBACrD,MAAMO,UAAUF,gBAAgBG,GAAG,CAAC,CAACC,IAAM,CAAC,CAAC,EAAEA,EAAE,CAAC,CAAC,EAAEC,IAAI,CAAC;gBAC1D,MAAM,IAAInB,gBAAgB;oBACxBoB,QAAQ;wBACN;4BACEC,SAAS,AAACd,IAAIe,CAAC,CAAa,wCAAwC;gCAAEN;4BAAQ;4BAC9EO,MAAM;wBACR;qBACD;gBACH;YACF;YACA,OAAOnB;QACT;QAEA,YAAY;QACZ,IAAIC,cAAc,YAAYI,WAAW;YACvC,MAAMe,iBAAiBlB,aAAaI;YAEpC,IAAIc,kBAAkBA,mBAAmBf,WAAW;gBAClD,MAAMO,UAAUf,wBAAwB,CAACuB,eAAe;gBACxD,IAAI,CAACR,WAAW,CAACA,QAAQD,QAAQ,CAACN,YAAY;oBAC5C,MAAM,IAAIT,gBAAgB;wBACxBoB,QAAQ;4BACN;gCACEC,SAAS,AAACd,IAAIe,CAAC,CAAa,sCAAsC;oCAChEG,MAAMD;oCACNE,IAAIjB;gCACN;gCACAc,MAAM;4BACR;yBACD;oBACH;gBACF;YACF;QACF;QAEA,OAAOnB;IACT,EAAC"}
1
+ {"version":3,"sources":["../../../src/hooks/reservations/validateStatusTransition.ts"],"sourcesContent":["import type { CollectionBeforeChangeHook } from 'payload'\n\nimport { ValidationError } from 'payload'\n\nimport type { PluginT } from '../../translations/index.js'\nimport type { ResolvedReservationPluginConfig } from '../../types.js'\n\nimport { validateTransition } from '../../services/AvailabilityService.js'\n\nexport const validateStatusTransition =\n (config: ResolvedReservationPluginConfig): CollectionBeforeChangeHook =>\n async ({ context, data, operation, originalDoc, req }) => {\n if (context?.skipReservationHooks) {return data}\n\n const newStatus = data?.status as string | undefined\n const { statusMachine } = config\n\n if (operation === 'create') {\n const isAdmin = Boolean(req.user)\n const defaultStatus = statusMachine.defaultStatus\n const allowedOnCreate: string[] = isAdmin\n ? [defaultStatus, 'confirmed']\n : [defaultStatus]\n\n if (newStatus && !allowedOnCreate.includes(newStatus)) {\n const allowed = allowedOnCreate.map((s) => `\"${s}\"`).join(' or ')\n throw new ValidationError({\n errors: [\n {\n message: (req.t as PluginT)('reservation:errorInvalidCreateStatus', { allowed }),\n path: 'status',\n },\n ],\n })\n }\n\n // Call beforeBookingCreate hooks (handled by plugin hooks wrapper)\n return data\n }\n\n // On update\n if (operation === 'update' && newStatus) {\n const previousStatus = originalDoc?.status as string | undefined\n\n if (previousStatus && previousStatus !== newStatus) {\n const result = validateTransition(previousStatus, newStatus, statusMachine)\n\n if (!result.valid) {\n throw new ValidationError({\n errors: [\n {\n message: (req.t as PluginT)('reservation:errorInvalidTransition', {\n from: previousStatus,\n to: newStatus,\n }),\n path: 'status',\n },\n ],\n })\n }\n\n // Call beforeBookingConfirm plugin hooks\n if (newStatus === 'confirmed' && config.hooks?.beforeBookingConfirm) {\n for (const hook of config.hooks.beforeBookingConfirm) {\n await hook({\n doc: originalDoc as Record<string, unknown>,\n newStatus,\n req,\n })\n }\n }\n\n // Call beforeBookingCancel plugin hooks\n if (newStatus === 'cancelled' && config.hooks?.beforeBookingCancel) {\n for (const hook of config.hooks.beforeBookingCancel) {\n await hook({\n doc: originalDoc as Record<string, unknown>,\n reason: data?.cancellationReason as string | undefined,\n req,\n })\n }\n }\n }\n }\n\n return data\n }\n"],"names":["ValidationError","validateTransition","validateStatusTransition","config","context","data","operation","originalDoc","req","skipReservationHooks","newStatus","status","statusMachine","isAdmin","Boolean","user","defaultStatus","allowedOnCreate","includes","allowed","map","s","join","errors","message","t","path","previousStatus","result","valid","from","to","hooks","beforeBookingConfirm","hook","doc","beforeBookingCancel","reason","cancellationReason"],"mappings":"AAEA,SAASA,eAAe,QAAQ,UAAS;AAKzC,SAASC,kBAAkB,QAAQ,wCAAuC;AAE1E,OAAO,MAAMC,2BACX,CAACC,SACD,OAAO,EAAEC,OAAO,EAAEC,IAAI,EAAEC,SAAS,EAAEC,WAAW,EAAEC,GAAG,EAAE;QACnD,IAAIJ,SAASK,sBAAsB;YAAC,OAAOJ;QAAI;QAE/C,MAAMK,YAAYL,MAAMM;QACxB,MAAM,EAAEC,aAAa,EAAE,GAAGT;QAE1B,IAAIG,cAAc,UAAU;YAC1B,MAAMO,UAAUC,QAAQN,IAAIO,IAAI;YAChC,MAAMC,gBAAgBJ,cAAcI,aAAa;YACjD,MAAMC,kBAA4BJ,UAC9B;gBAACG;gBAAe;aAAY,GAC5B;gBAACA;aAAc;YAEnB,IAAIN,aAAa,CAACO,gBAAgBC,QAAQ,CAACR,YAAY;gBACrD,MAAMS,UAAUF,gBAAgBG,GAAG,CAAC,CAACC,IAAM,CAAC,CAAC,EAAEA,EAAE,CAAC,CAAC,EAAEC,IAAI,CAAC;gBAC1D,MAAM,IAAItB,gBAAgB;oBACxBuB,QAAQ;wBACN;4BACEC,SAAS,AAAChB,IAAIiB,CAAC,CAAa,wCAAwC;gCAAEN;4BAAQ;4BAC9EO,MAAM;wBACR;qBACD;gBACH;YACF;YAEA,mEAAmE;YACnE,OAAOrB;QACT;QAEA,YAAY;QACZ,IAAIC,cAAc,YAAYI,WAAW;YACvC,MAAMiB,iBAAiBpB,aAAaI;YAEpC,IAAIgB,kBAAkBA,mBAAmBjB,WAAW;gBAClD,MAAMkB,SAAS3B,mBAAmB0B,gBAAgBjB,WAAWE;gBAE7D,IAAI,CAACgB,OAAOC,KAAK,EAAE;oBACjB,MAAM,IAAI7B,gBAAgB;wBACxBuB,QAAQ;4BACN;gCACEC,SAAS,AAAChB,IAAIiB,CAAC,CAAa,sCAAsC;oCAChEK,MAAMH;oCACNI,IAAIrB;gCACN;gCACAgB,MAAM;4BACR;yBACD;oBACH;gBACF;gBAEA,yCAAyC;gBACzC,IAAIhB,cAAc,eAAeP,OAAO6B,KAAK,EAAEC,sBAAsB;oBACnE,KAAK,MAAMC,QAAQ/B,OAAO6B,KAAK,CAACC,oBAAoB,CAAE;wBACpD,MAAMC,KAAK;4BACTC,KAAK5B;4BACLG;4BACAF;wBACF;oBACF;gBACF;gBAEA,wCAAwC;gBACxC,IAAIE,cAAc,eAAeP,OAAO6B,KAAK,EAAEI,qBAAqB;oBAClE,KAAK,MAAMF,QAAQ/B,OAAO6B,KAAK,CAACI,mBAAmB,CAAE;wBACnD,MAAMF,KAAK;4BACTC,KAAK5B;4BACL8B,QAAQhC,MAAMiC;4BACd9B;wBACF;oBACF;gBACF;YACF;QACF;QAEA,OAAOH;IACT,EAAC"}
package/dist/index.d.ts CHANGED
@@ -1,2 +1,6 @@
1
1
  export { payloadReserve } from './plugin.js';
2
- export type { ReservationPluginConfig, ResolvedReservationPluginConfig } from './types.js';
2
+ export { buildOverlapQuery, checkAvailability, computeEndTime, getAvailableSlots, isBlockingStatus, validateTransition, } from './services/index.js';
3
+ export type { CapacityMode, DurationType, ReservationPluginConfig, ReservationPluginHooks, ResolvedReservationPluginConfig, StatusMachineConfig, } from './types.js';
4
+ export { DEFAULT_STATUS_MACHINE, VALID_STATUS_TRANSITIONS } from './types.js';
5
+ export type { ResolvedItem } from './utilities/resolveReservationItems.js';
6
+ export { resolveReservationItems } from './utilities/resolveReservationItems.js';
package/dist/index.js CHANGED
@@ -1,3 +1,6 @@
1
1
  export { payloadReserve } from './plugin.js';
2
+ export { buildOverlapQuery, checkAvailability, computeEndTime, getAvailableSlots, isBlockingStatus, validateTransition } from './services/index.js';
3
+ export { DEFAULT_STATUS_MACHINE, VALID_STATUS_TRANSITIONS } from './types.js';
4
+ export { resolveReservationItems } from './utilities/resolveReservationItems.js';
2
5
 
3
6
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { payloadReserve } from './plugin.js'\nexport type { ReservationPluginConfig, ResolvedReservationPluginConfig } from './types.js'\n"],"names":["payloadReserve"],"mappings":"AAAA,SAASA,cAAc,QAAQ,cAAa"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { payloadReserve } from './plugin.js'\nexport {\n buildOverlapQuery,\n checkAvailability,\n computeEndTime,\n getAvailableSlots,\n isBlockingStatus,\n validateTransition,\n} from './services/index.js'\nexport type {\n CapacityMode,\n DurationType,\n ReservationPluginConfig,\n ReservationPluginHooks,\n ResolvedReservationPluginConfig,\n StatusMachineConfig,\n} from './types.js'\nexport { DEFAULT_STATUS_MACHINE, VALID_STATUS_TRANSITIONS } from './types.js'\nexport type { ResolvedItem } from './utilities/resolveReservationItems.js'\nexport { resolveReservationItems } from './utilities/resolveReservationItems.js'\n"],"names":["payloadReserve","buildOverlapQuery","checkAvailability","computeEndTime","getAvailableSlots","isBlockingStatus","validateTransition","DEFAULT_STATUS_MACHINE","VALID_STATUS_TRANSITIONS","resolveReservationItems"],"mappings":"AAAA,SAASA,cAAc,QAAQ,cAAa;AAC5C,SACEC,iBAAiB,EACjBC,iBAAiB,EACjBC,cAAc,EACdC,iBAAiB,EACjBC,gBAAgB,EAChBC,kBAAkB,QACb,sBAAqB;AAS5B,SAASC,sBAAsB,EAAEC,wBAAwB,QAAQ,aAAY;AAE7E,SAASC,uBAAuB,QAAQ,yCAAwC"}
package/dist/plugin.js CHANGED
@@ -1,12 +1,16 @@
1
1
  import { deepMergeSimple } from 'payload/shared';
2
+ import { createCustomersCollection } from './collections/Customers.js';
2
3
  import { createReservationsCollection } from './collections/Reservations.js';
3
4
  import { createResourcesCollection } from './collections/Resources.js';
4
5
  import { createSchedulesCollection } from './collections/Schedules.js';
5
6
  import { createServicesCollection } from './collections/Services.js';
6
7
  import { resolveConfig } from './defaults.js';
8
+ import { createCancelBookingEndpoint } from './endpoints/cancelBooking.js';
9
+ import { createCheckAvailabilityEndpoint } from './endpoints/checkAvailability.js';
10
+ import { createBookingEndpoint } from './endpoints/createBooking.js';
7
11
  import { createCustomerSearchEndpoint } from './endpoints/customerSearch.js';
12
+ import { createGetSlotsEndpoint } from './endpoints/getSlots.js';
8
13
  import { translations } from './translations/index.js';
9
- /** Check whether a top-level field with the given name already exists */ const hasField = (fields, name)=>fields.some((f)=>'name' in f && f.name === name);
10
14
  export const payloadReserve = (pluginOptions = {})=>(config)=>{
11
15
  const resolved = resolveConfig(pluginOptions);
12
16
  // Detect localization from the Payload config
@@ -19,58 +23,51 @@ export const payloadReserve = (pluginOptions = {})=>(config)=>{
19
23
  if (resolved.disabled) {
20
24
  return config;
21
25
  }
22
- // Add the 4 plugin collections
23
- config.collections.push(createServicesCollection(resolved), createResourcesCollection(resolved), createSchedulesCollection(resolved), createReservationsCollection(resolved));
24
- // Extend the existing user collection with customer fields
25
- const userCol = config.collections.find((c)=>c.slug === resolved.userCollection);
26
- if (userCol) {
27
- const fieldsToAdd = [
28
- {
29
- name: 'name',
30
- type: 'text',
31
- maxLength: 200
32
- },
33
- {
34
- name: 'phone',
35
- type: 'text',
36
- maxLength: 50
37
- },
38
- {
39
- name: 'notes',
40
- type: 'textarea'
41
- },
42
- {
43
- name: 'bookings',
44
- type: 'join',
45
- collection: resolved.slugs.reservations,
46
- on: 'customer'
47
- }
48
- ];
49
- for (const field of fieldsToAdd){
50
- if (!hasField(userCol.fields, field.name)) {
51
- userCol.fields.push(field);
52
- }
53
- }
54
- // Enable multi-field search on the user collection
55
- if (!userCol.admin) {
56
- userCol.admin = {};
57
- }
58
- if (!userCol.admin.listSearchableFields) {
59
- userCol.admin.listSearchableFields = [
60
- 'name',
61
- 'phone',
62
- 'email'
26
+ if (resolved.userCollection) {
27
+ // Extend the existing auth collection with customer fields
28
+ const targetCollection = config.collections.find((col)=>col.slug === resolved.userCollection);
29
+ if (targetCollection) {
30
+ // Collect existing field names for deduplication check
31
+ const existingFieldNames = new Set(targetCollection.fields.map((field)=>'name' in field ? field.name : undefined).filter(Boolean));
32
+ // Fields to inject if not already present
33
+ const fieldsToAdd = [
34
+ {
35
+ name: 'phone',
36
+ type: 'text',
37
+ maxLength: 50
38
+ },
39
+ {
40
+ name: 'notes',
41
+ type: 'textarea'
42
+ },
43
+ {
44
+ name: 'bookings',
45
+ type: 'join',
46
+ collection: resolved.slugs.reservations,
47
+ on: 'customer'
48
+ }
63
49
  ];
50
+ for (const field of fieldsToAdd){
51
+ const fieldName = 'name' in field ? field.name : undefined;
52
+ if (fieldName && !existingFieldNames.has(fieldName)) {
53
+ targetCollection.fields.push(field);
54
+ }
55
+ }
64
56
  }
57
+ // Point the customers slug at the user collection so other parts of the
58
+ // plugin (endpoints, hooks) reference the correct collection
59
+ resolved.slugs.customers = resolved.userCollection;
60
+ // Push only the 4 domain collections (no standalone Customers)
61
+ config.collections.push(createServicesCollection(resolved), createResourcesCollection(resolved), createSchedulesCollection(resolved), createReservationsCollection(resolved));
65
62
  } else {
66
- // eslint-disable-next-line no-console
67
- console.warn(`[payload-reserve] Could not find collection "${resolved.userCollection}" to extend with customer fields. ` + 'Make sure your Payload config defines this collection before the reservation plugin runs.');
63
+ // Default behaviour: push all 5 collections including standalone Customers
64
+ config.collections.push(createServicesCollection(resolved), createResourcesCollection(resolved), createSchedulesCollection(resolved), createReservationsCollection(resolved), createCustomersCollection(resolved));
68
65
  }
69
66
  // Register custom endpoints
70
67
  if (!config.endpoints) {
71
68
  config.endpoints = [];
72
69
  }
73
- config.endpoints.push(createCustomerSearchEndpoint(resolved));
70
+ config.endpoints.push(createCancelBookingEndpoint(resolved), createCheckAvailabilityEndpoint(resolved), createBookingEndpoint(resolved), createCustomerSearchEndpoint(resolved), createGetSlotsEndpoint(resolved));
74
71
  // Set up admin configuration
75
72
  if (!config.admin) {
76
73
  config.admin = {};
@@ -78,15 +75,14 @@ export const payloadReserve = (pluginOptions = {})=>(config)=>{
78
75
  if (!config.admin.components) {
79
76
  config.admin.components = {};
80
77
  }
81
- // Store slugs in admin custom for component access
78
+ // Store slugs and status machine in admin custom for component access
82
79
  if (!config.admin.custom) {
83
80
  config.admin.custom = {};
84
81
  }
85
82
  config.admin.custom.reservationSlugs = {
86
- ...resolved.slugs,
87
- userCollection: resolved.userCollection
83
+ ...resolved.slugs
88
84
  };
89
- config.admin.custom.reservationCustomerRole = resolved.customerRole;
85
+ config.admin.custom.reservationStatusMachine = resolved.statusMachine;
90
86
  // Add dashboard widget
91
87
  if (!config.admin.dashboard) {
92
88
  config.admin.dashboard = {
@@ -113,11 +109,10 @@ export const payloadReserve = (pluginOptions = {})=>(config)=>{
113
109
  path: '/reservation-availability'
114
110
  };
115
111
  // Merge plugin translations (user translations take precedence)
116
- if (!config.i18n) {
117
- config.i18n = {};
118
- }
119
- ;
120
- config.i18n.translations = deepMergeSimple(translations, config.i18n.translations ?? {});
112
+ config.i18n = {
113
+ ...config.i18n ?? {},
114
+ translations: deepMergeSimple(translations, config.i18n?.translations ?? {})
115
+ };
121
116
  return config;
122
117
  };
123
118
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/plugin.ts"],"sourcesContent":["import type { Config, Field } from 'payload'\n\nimport { deepMergeSimple } from 'payload/shared'\n\nimport type { ReservationPluginConfig } from './types.js'\n\nimport { createReservationsCollection } from './collections/Reservations.js'\nimport { createResourcesCollection } from './collections/Resources.js'\nimport { createSchedulesCollection } from './collections/Schedules.js'\nimport { createServicesCollection } from './collections/Services.js'\nimport { resolveConfig } from './defaults.js'\nimport { createCustomerSearchEndpoint } from './endpoints/customerSearch.js'\nimport { translations } from './translations/index.js'\n\n/** Check whether a top-level field with the given name already exists */\nconst hasField = (fields: Field[], name: string): boolean =>\n fields.some((f) => 'name' in f && f.name === name)\n\nexport const payloadReserve =\n (pluginOptions: ReservationPluginConfig = {}) =>\n (config: Config): Config => {\n const resolved = resolveConfig(pluginOptions)\n\n // Detect localization from the Payload config\n if (config.localization) {\n resolved.localized = true\n }\n\n if (!config.collections) {\n config.collections = []\n }\n\n if (resolved.disabled) {\n return config\n }\n\n // Add the 4 plugin collections\n config.collections.push(\n createServicesCollection(resolved),\n createResourcesCollection(resolved),\n createSchedulesCollection(resolved),\n createReservationsCollection(resolved),\n )\n\n // Extend the existing user collection with customer fields\n const userCol = config.collections.find((c) => c.slug === resolved.userCollection)\n if (userCol) {\n const fieldsToAdd: Field[] = [\n { name: 'name', type: 'text', maxLength: 200 },\n { name: 'phone', type: 'text', maxLength: 50 },\n { name: 'notes', type: 'textarea' },\n {\n name: 'bookings',\n type: 'join',\n collection: resolved.slugs.reservations,\n on: 'customer',\n },\n ]\n\n for (const field of fieldsToAdd) {\n if (!hasField(userCol.fields, (field as { name: string }).name)) {\n userCol.fields.push(field)\n }\n }\n\n // Enable multi-field search on the user collection\n if (!userCol.admin) {userCol.admin = {}}\n if (!userCol.admin.listSearchableFields) {\n userCol.admin.listSearchableFields = ['name', 'phone', 'email']\n }\n } else {\n // eslint-disable-next-line no-console\n console.warn(\n `[payload-reserve] Could not find collection \"${resolved.userCollection}\" to extend with customer fields. ` +\n 'Make sure your Payload config defines this collection before the reservation plugin runs.',\n )\n }\n\n // Register custom endpoints\n if (!config.endpoints) {config.endpoints = []}\n config.endpoints.push(createCustomerSearchEndpoint(resolved))\n\n // Set up admin configuration\n if (!config.admin) {config.admin = {}}\n if (!config.admin.components) {config.admin.components = {}}\n\n // Store slugs in admin custom for component access\n if (!config.admin.custom) {config.admin.custom = {}}\n config.admin.custom.reservationSlugs = {\n ...resolved.slugs,\n userCollection: resolved.userCollection,\n }\n config.admin.custom.reservationCustomerRole = resolved.customerRole\n\n // Add dashboard widget\n if (!config.admin.dashboard) {\n config.admin.dashboard = { widgets: [] }\n }\n if (!config.admin.dashboard.widgets) {\n config.admin.dashboard.widgets = []\n }\n config.admin.dashboard.widgets.push({\n slug: 'reservation-todays-reservations',\n ComponentPath: 'payload-reserve/rsc#DashboardWidgetServer',\n label: 'Today\\'s Reservations',\n maxWidth: 'large',\n minWidth: 'medium',\n })\n\n // Add availability overview as custom admin view\n if (!config.admin.components.views) {\n config.admin.components.views = {}\n }\n ;(config.admin.components.views as Record<string, unknown>)['reservation-availability'] = {\n Component: 'payload-reserve/client#AvailabilityOverview',\n path: '/reservation-availability',\n }\n\n // Merge plugin translations (user translations take precedence)\n if (!config.i18n) {config.i18n = {}}\n ;(config.i18n as Record<string, unknown>).translations = deepMergeSimple(\n translations,\n (config.i18n as Record<string, unknown>).translations ?? {},\n )\n\n return config\n }\n"],"names":["deepMergeSimple","createReservationsCollection","createResourcesCollection","createSchedulesCollection","createServicesCollection","resolveConfig","createCustomerSearchEndpoint","translations","hasField","fields","name","some","f","payloadReserve","pluginOptions","config","resolved","localization","localized","collections","disabled","push","userCol","find","c","slug","userCollection","fieldsToAdd","type","maxLength","collection","slugs","reservations","on","field","admin","listSearchableFields","console","warn","endpoints","components","custom","reservationSlugs","reservationCustomerRole","customerRole","dashboard","widgets","ComponentPath","label","maxWidth","minWidth","views","Component","path","i18n"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/plugin.ts"],"sourcesContent":["import type { CollectionSlug, Config, Field } from 'payload'\n\nimport { deepMergeSimple } from 'payload/shared'\n\nimport type { ReservationPluginConfig } from './types.js'\n\nimport { createCustomersCollection } from './collections/Customers.js'\nimport { createReservationsCollection } from './collections/Reservations.js'\nimport { createResourcesCollection } from './collections/Resources.js'\nimport { createSchedulesCollection } from './collections/Schedules.js'\nimport { createServicesCollection } from './collections/Services.js'\nimport { resolveConfig } from './defaults.js'\nimport { createCancelBookingEndpoint } from './endpoints/cancelBooking.js'\nimport { createCheckAvailabilityEndpoint } from './endpoints/checkAvailability.js'\nimport { createBookingEndpoint } from './endpoints/createBooking.js'\nimport { createCustomerSearchEndpoint } from './endpoints/customerSearch.js'\nimport { createGetSlotsEndpoint } from './endpoints/getSlots.js'\nimport { translations } from './translations/index.js'\n\nexport const payloadReserve =\n (pluginOptions: ReservationPluginConfig = {}) =>\n (config: Config): Config => {\n const resolved = resolveConfig(pluginOptions)\n\n // Detect localization from the Payload config\n if (config.localization) {\n resolved.localized = true\n }\n\n if (!config.collections) {\n config.collections = []\n }\n\n if (resolved.disabled) {\n return config\n }\n\n if (resolved.userCollection) {\n // Extend the existing auth collection with customer fields\n const targetCollection = config.collections.find(\n (col) => col.slug === resolved.userCollection,\n )\n\n if (targetCollection) {\n // Collect existing field names for deduplication check\n const existingFieldNames = new Set(\n targetCollection.fields\n .map((field) => ('name' in field ? field.name : undefined))\n .filter(Boolean),\n )\n\n // Fields to inject if not already present\n const fieldsToAdd: Field[] = [\n {\n name: 'phone',\n type: 'text',\n maxLength: 50,\n },\n {\n name: 'notes',\n type: 'textarea',\n },\n {\n name: 'bookings',\n type: 'join',\n collection: resolved.slugs.reservations as unknown as CollectionSlug,\n on: 'customer',\n },\n ]\n\n for (const field of fieldsToAdd) {\n const fieldName = 'name' in field ? field.name : undefined\n if (fieldName && !existingFieldNames.has(fieldName)) {\n targetCollection.fields.push(field)\n }\n }\n }\n\n // Point the customers slug at the user collection so other parts of the\n // plugin (endpoints, hooks) reference the correct collection\n resolved.slugs.customers = resolved.userCollection\n\n // Push only the 4 domain collections (no standalone Customers)\n config.collections.push(\n createServicesCollection(resolved),\n createResourcesCollection(resolved),\n createSchedulesCollection(resolved),\n createReservationsCollection(resolved),\n )\n } else {\n // Default behaviour: push all 5 collections including standalone Customers\n config.collections.push(\n createServicesCollection(resolved),\n createResourcesCollection(resolved),\n createSchedulesCollection(resolved),\n createReservationsCollection(resolved),\n createCustomersCollection(resolved),\n )\n }\n\n // Register custom endpoints\n if (!config.endpoints) {config.endpoints = []}\n config.endpoints.push(\n createCancelBookingEndpoint(resolved),\n createCheckAvailabilityEndpoint(resolved),\n createBookingEndpoint(resolved),\n createCustomerSearchEndpoint(resolved),\n createGetSlotsEndpoint(resolved),\n )\n\n // Set up admin configuration\n if (!config.admin) {config.admin = {}}\n if (!config.admin.components) {config.admin.components = {}}\n\n // Store slugs and status machine in admin custom for component access\n if (!config.admin.custom) {config.admin.custom = {}}\n config.admin.custom.reservationSlugs = {\n ...resolved.slugs,\n }\n config.admin.custom.reservationStatusMachine = resolved.statusMachine\n\n // Add dashboard widget\n if (!config.admin.dashboard) {\n config.admin.dashboard = { widgets: [] }\n }\n if (!config.admin.dashboard.widgets) {\n config.admin.dashboard.widgets = []\n }\n config.admin.dashboard.widgets.push({\n slug: 'reservation-todays-reservations',\n ComponentPath: 'payload-reserve/rsc#DashboardWidgetServer',\n label: 'Today\\'s Reservations',\n maxWidth: 'large',\n minWidth: 'medium',\n })\n\n // Add availability overview as custom admin view\n if (!config.admin.components.views) {\n config.admin.components.views = {}\n }\n ;(config.admin.components.views as Record<string, unknown>)['reservation-availability'] = {\n Component: 'payload-reserve/client#AvailabilityOverview',\n path: '/reservation-availability',\n }\n\n // Merge plugin translations (user translations take precedence)\n config.i18n = {\n ...(config.i18n ?? {}),\n translations: deepMergeSimple(\n translations,\n (config.i18n?.translations as Record<string, Record<string, unknown>>) ?? {},\n ),\n }\n\n return config\n }\n"],"names":["deepMergeSimple","createCustomersCollection","createReservationsCollection","createResourcesCollection","createSchedulesCollection","createServicesCollection","resolveConfig","createCancelBookingEndpoint","createCheckAvailabilityEndpoint","createBookingEndpoint","createCustomerSearchEndpoint","createGetSlotsEndpoint","translations","payloadReserve","pluginOptions","config","resolved","localization","localized","collections","disabled","userCollection","targetCollection","find","col","slug","existingFieldNames","Set","fields","map","field","name","undefined","filter","Boolean","fieldsToAdd","type","maxLength","collection","slugs","reservations","on","fieldName","has","push","customers","endpoints","admin","components","custom","reservationSlugs","reservationStatusMachine","statusMachine","dashboard","widgets","ComponentPath","label","maxWidth","minWidth","views","Component","path","i18n"],"mappings":"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"}