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.
- package/README.md +61 -1141
- package/dist/collections/Customers.d.ts +3 -0
- package/dist/collections/Customers.js +58 -0
- package/dist/collections/Customers.js.map +1 -0
- package/dist/collections/Reservations.js +129 -35
- package/dist/collections/Reservations.js.map +1 -1
- package/dist/collections/Resources.js +70 -2
- package/dist/collections/Resources.js.map +1 -1
- package/dist/collections/Schedules.js +4 -1
- package/dist/collections/Schedules.js.map +1 -1
- package/dist/collections/Services.js +59 -2
- package/dist/collections/Services.js.map +1 -1
- package/dist/components/AvailabilityOverview/AvailabilityOverview.module.css +31 -0
- package/dist/components/AvailabilityOverview/index.js +59 -8
- package/dist/components/AvailabilityOverview/index.js.map +1 -1
- package/dist/components/CalendarView/CalendarView.module.css +171 -0
- package/dist/components/CalendarView/index.js +547 -38
- package/dist/components/CalendarView/index.js.map +1 -1
- package/dist/components/CustomerField/index.js +24 -10
- package/dist/components/CustomerField/index.js.map +1 -1
- package/dist/components/DashboardWidget/DashboardWidgetServer.js +21 -11
- package/dist/components/DashboardWidget/DashboardWidgetServer.js.map +1 -1
- package/dist/defaults.d.ts +1 -2
- package/dist/defaults.js +22 -4
- package/dist/defaults.js.map +1 -1
- package/dist/endpoints/cancelBooking.d.ts +3 -0
- package/dist/endpoints/cancelBooking.js +37 -0
- package/dist/endpoints/cancelBooking.js.map +1 -0
- package/dist/endpoints/checkAvailability.d.ts +3 -0
- package/dist/endpoints/checkAvailability.js +37 -0
- package/dist/endpoints/checkAvailability.js.map +1 -0
- package/dist/endpoints/createBooking.d.ts +3 -0
- package/dist/endpoints/createBooking.js +32 -0
- package/dist/endpoints/createBooking.js.map +1 -0
- package/dist/endpoints/customerSearch.js +76 -58
- package/dist/endpoints/customerSearch.js.map +1 -1
- package/dist/endpoints/getSlots.d.ts +3 -0
- package/dist/endpoints/getSlots.js +51 -0
- package/dist/endpoints/getSlots.js.map +1 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/reservations/calculateEndTime.js +75 -9
- package/dist/hooks/reservations/calculateEndTime.js.map +1 -1
- package/dist/hooks/reservations/checkIdempotency.d.ts +3 -0
- package/dist/hooks/reservations/checkIdempotency.js +32 -0
- package/dist/hooks/reservations/checkIdempotency.js.map +1 -0
- package/dist/hooks/reservations/onStatusChange.d.ts +3 -0
- package/dist/hooks/reservations/onStatusChange.js +41 -0
- package/dist/hooks/reservations/onStatusChange.js.map +1 -0
- package/dist/hooks/reservations/validateCancellation.js +1 -1
- package/dist/hooks/reservations/validateCancellation.js.map +1 -1
- package/dist/hooks/reservations/validateConflicts.js +34 -56
- package/dist/hooks/reservations/validateConflicts.js.map +1 -1
- package/dist/hooks/reservations/validateStatusTransition.d.ts +2 -1
- package/dist/hooks/reservations/validateStatusTransition.js +29 -6
- package/dist/hooks/reservations/validateStatusTransition.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/plugin.js +49 -54
- package/dist/plugin.js.map +1 -1
- package/dist/services/AvailabilityService.d.ts +57 -0
- package/dist/services/AvailabilityService.js +232 -0
- package/dist/services/AvailabilityService.js.map +1 -0
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js +3 -0
- package/dist/services/index.js.map +1 -0
- package/dist/translations/en.json +33 -1
- package/dist/translations/index.d.ts +5 -0
- package/dist/translations/index.js.map +1 -1
- package/dist/types.d.ts +84 -6
- package/dist/types.js +29 -9
- package/dist/types.js.map +1 -1
- package/dist/utilities/ownerAccess.d.ts +24 -0
- package/dist/utilities/ownerAccess.js +128 -0
- package/dist/utilities/ownerAccess.js.map +1 -0
- package/dist/utilities/resolveReservationItems.d.ts +18 -0
- package/dist/utilities/resolveReservationItems.js +45 -0
- package/dist/utilities/resolveReservationItems.js.map +1 -0
- package/package.json +12 -9
|
@@ -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;
|
|
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 {
|
|
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
|
-
|
|
8
|
+
const items = resolveReservationItems(data);
|
|
9
|
+
if (items.length === 0) {
|
|
8
10
|
return data;
|
|
9
11
|
}
|
|
10
|
-
|
|
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
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
13
|
+
defaultStatus,
|
|
12
14
|
'confirmed'
|
|
13
15
|
] : [
|
|
14
|
-
|
|
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
|
|
36
|
-
if (!
|
|
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 {
|
|
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
|
|
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 {
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
//
|
|
67
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
package/dist/plugin.js.map
CHANGED
|
@@ -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 {
|
|
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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"}
|