payload-reserve 1.0.2 → 1.0.3
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 +44 -1154
- 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 +124 -34
- package/dist/collections/Reservations.js.map +1 -1
- package/dist/collections/Resources.js +39 -0
- package/dist/collections/Resources.js.map +1 -1
- package/dist/collections/Schedules.js.map +1 -1
- package/dist/collections/Services.js +27 -0
- 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 +15 -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 +38 -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 +65 -6
- package/dist/types.js +29 -9
- package/dist/types.js.map +1 -1
- 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
package/dist/defaults.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/defaults.ts"],"sourcesContent":["import type { ReservationPluginConfig, ResolvedReservationPluginConfig } from './types.js'\n\nexport const DEFAULT_SLUGS = {\n media: 'media',\n reservations: 'reservations',\n resources: 'resources',\n schedules: 'schedules',\n services: 'services',\n} as const\n\nexport const DEFAULT_ADMIN_GROUP = 'Reservations'\nexport const DEFAULT_BUFFER_TIME = 0\nexport const DEFAULT_CANCELLATION_NOTICE_PERIOD = 24\
|
|
1
|
+
{"version":3,"sources":["../src/defaults.ts"],"sourcesContent":["import type { ReservationPluginConfig, ResolvedReservationPluginConfig } from './types.js'\n\nimport { DEFAULT_STATUS_MACHINE } from './types.js'\n\nexport const DEFAULT_SLUGS = {\n customers: 'customers',\n media: 'media',\n reservations: 'reservations',\n resources: 'resources',\n schedules: 'schedules',\n services: 'services',\n} as const\n\nexport const DEFAULT_ADMIN_GROUP = 'Reservations'\nexport const DEFAULT_BUFFER_TIME = 0\nexport const DEFAULT_CANCELLATION_NOTICE_PERIOD = 24\n\nexport function resolveConfig(\n pluginOptions: ReservationPluginConfig,\n): ResolvedReservationPluginConfig {\n const userStatusMachine = pluginOptions.statusMachine\n return {\n access: pluginOptions.access ?? {},\n adminGroup: pluginOptions.adminGroup ?? DEFAULT_ADMIN_GROUP,\n cancellationNoticePeriod:\n pluginOptions.cancellationNoticePeriod ?? DEFAULT_CANCELLATION_NOTICE_PERIOD,\n defaultBufferTime: pluginOptions.defaultBufferTime ?? DEFAULT_BUFFER_TIME,\n disabled: pluginOptions.disabled ?? false,\n hooks: pluginOptions.hooks ?? {},\n localized: false,\n slugs: {\n customers: pluginOptions.slugs?.customers ?? DEFAULT_SLUGS.customers,\n media: pluginOptions.slugs?.media ?? DEFAULT_SLUGS.media,\n reservations: pluginOptions.slugs?.reservations ?? DEFAULT_SLUGS.reservations,\n resources: pluginOptions.slugs?.resources ?? DEFAULT_SLUGS.resources,\n schedules: pluginOptions.slugs?.schedules ?? DEFAULT_SLUGS.schedules,\n services: pluginOptions.slugs?.services ?? DEFAULT_SLUGS.services,\n },\n statusMachine: userStatusMachine\n ? {\n blockingStatuses:\n userStatusMachine.blockingStatuses ?? DEFAULT_STATUS_MACHINE.blockingStatuses,\n defaultStatus: userStatusMachine.defaultStatus ?? DEFAULT_STATUS_MACHINE.defaultStatus,\n statuses: userStatusMachine.statuses ?? DEFAULT_STATUS_MACHINE.statuses,\n terminalStatuses:\n userStatusMachine.terminalStatuses ?? DEFAULT_STATUS_MACHINE.terminalStatuses,\n transitions: userStatusMachine.transitions ?? DEFAULT_STATUS_MACHINE.transitions,\n }\n : { ...DEFAULT_STATUS_MACHINE },\n userCollection: pluginOptions.userCollection ?? undefined,\n }\n}\n"],"names":["DEFAULT_STATUS_MACHINE","DEFAULT_SLUGS","customers","media","reservations","resources","schedules","services","DEFAULT_ADMIN_GROUP","DEFAULT_BUFFER_TIME","DEFAULT_CANCELLATION_NOTICE_PERIOD","resolveConfig","pluginOptions","userStatusMachine","statusMachine","access","adminGroup","cancellationNoticePeriod","defaultBufferTime","disabled","hooks","localized","slugs","blockingStatuses","defaultStatus","statuses","terminalStatuses","transitions","userCollection","undefined"],"mappings":"AAEA,SAASA,sBAAsB,QAAQ,aAAY;AAEnD,OAAO,MAAMC,gBAAgB;IAC3BC,WAAW;IACXC,OAAO;IACPC,cAAc;IACdC,WAAW;IACXC,WAAW;IACXC,UAAU;AACZ,EAAU;AAEV,OAAO,MAAMC,sBAAsB,eAAc;AACjD,OAAO,MAAMC,sBAAsB,EAAC;AACpC,OAAO,MAAMC,qCAAqC,GAAE;AAEpD,OAAO,SAASC,cACdC,aAAsC;IAEtC,MAAMC,oBAAoBD,cAAcE,aAAa;IACrD,OAAO;QACLC,QAAQH,cAAcG,MAAM,IAAI,CAAC;QACjCC,YAAYJ,cAAcI,UAAU,IAAIR;QACxCS,0BACEL,cAAcK,wBAAwB,IAAIP;QAC5CQ,mBAAmBN,cAAcM,iBAAiB,IAAIT;QACtDU,UAAUP,cAAcO,QAAQ,IAAI;QACpCC,OAAOR,cAAcQ,KAAK,IAAI,CAAC;QAC/BC,WAAW;QACXC,OAAO;YACLpB,WAAWU,cAAcU,KAAK,EAAEpB,aAAaD,cAAcC,SAAS;YACpEC,OAAOS,cAAcU,KAAK,EAAEnB,SAASF,cAAcE,KAAK;YACxDC,cAAcQ,cAAcU,KAAK,EAAElB,gBAAgBH,cAAcG,YAAY;YAC7EC,WAAWO,cAAcU,KAAK,EAAEjB,aAAaJ,cAAcI,SAAS;YACpEC,WAAWM,cAAcU,KAAK,EAAEhB,aAAaL,cAAcK,SAAS;YACpEC,UAAUK,cAAcU,KAAK,EAAEf,YAAYN,cAAcM,QAAQ;QACnE;QACAO,eAAeD,oBACX;YACEU,kBACEV,kBAAkBU,gBAAgB,IAAIvB,uBAAuBuB,gBAAgB;YAC/EC,eAAeX,kBAAkBW,aAAa,IAAIxB,uBAAuBwB,aAAa;YACtFC,UAAUZ,kBAAkBY,QAAQ,IAAIzB,uBAAuByB,QAAQ;YACvEC,kBACEb,kBAAkBa,gBAAgB,IAAI1B,uBAAuB0B,gBAAgB;YAC/EC,aAAad,kBAAkBc,WAAW,IAAI3B,uBAAuB2B,WAAW;QAClF,IACA;YAAE,GAAG3B,sBAAsB;QAAC;QAChC4B,gBAAgBhB,cAAcgB,cAAc,IAAIC;IAClD;AACF"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export function createCancelBookingEndpoint(config) {
|
|
2
|
+
return {
|
|
3
|
+
handler: async (req)=>{
|
|
4
|
+
if (!req.user) {
|
|
5
|
+
return Response.json({
|
|
6
|
+
message: 'Unauthorized'
|
|
7
|
+
}, {
|
|
8
|
+
status: 401
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
const body = await req.json?.();
|
|
12
|
+
const { reason, reservationId } = body ?? {};
|
|
13
|
+
if (!reservationId) {
|
|
14
|
+
return Response.json({
|
|
15
|
+
message: 'reservationId is required'
|
|
16
|
+
}, {
|
|
17
|
+
status: 400
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
21
|
+
const reservation = await req.payload.update({
|
|
22
|
+
id: reservationId,
|
|
23
|
+
collection: config.slugs.reservations,
|
|
24
|
+
data: {
|
|
25
|
+
cancellationReason: reason,
|
|
26
|
+
status: 'cancelled'
|
|
27
|
+
},
|
|
28
|
+
req
|
|
29
|
+
});
|
|
30
|
+
return Response.json(reservation);
|
|
31
|
+
},
|
|
32
|
+
method: 'post',
|
|
33
|
+
path: '/reserve/cancel'
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
//# sourceMappingURL=cancelBooking.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/endpoints/cancelBooking.ts"],"sourcesContent":["import type { Endpoint } from 'payload'\n\nimport type { ResolvedReservationPluginConfig } from '../types.js'\n\nexport function createCancelBookingEndpoint(config: ResolvedReservationPluginConfig): Endpoint {\n return {\n handler: async (req) => {\n if (!req.user) {\n return Response.json({ message: 'Unauthorized' }, { status: 401 })\n }\n\n const body = await req.json?.()\n const { reason, reservationId } = (body ?? {}) as {\n reason?: string\n reservationId?: string\n }\n\n if (!reservationId) {\n return Response.json({ message: 'reservationId is required' }, { status: 400 })\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const reservation = await (req.payload.update as any)({\n id: reservationId,\n collection: config.slugs.reservations,\n data: {\n cancellationReason: reason,\n status: 'cancelled',\n },\n req,\n })\n\n return Response.json(reservation)\n },\n method: 'post',\n path: '/reserve/cancel',\n }\n}\n"],"names":["createCancelBookingEndpoint","config","handler","req","user","Response","json","message","status","body","reason","reservationId","reservation","payload","update","id","collection","slugs","reservations","data","cancellationReason","method","path"],"mappings":"AAIA,OAAO,SAASA,4BAA4BC,MAAuC;IACjF,OAAO;QACLC,SAAS,OAAOC;YACd,IAAI,CAACA,IAAIC,IAAI,EAAE;gBACb,OAAOC,SAASC,IAAI,CAAC;oBAAEC,SAAS;gBAAe,GAAG;oBAAEC,QAAQ;gBAAI;YAClE;YAEA,MAAMC,OAAO,MAAMN,IAAIG,IAAI;YAC3B,MAAM,EAAEI,MAAM,EAAEC,aAAa,EAAE,GAAIF,QAAQ,CAAC;YAK5C,IAAI,CAACE,eAAe;gBAClB,OAAON,SAASC,IAAI,CAAC;oBAAEC,SAAS;gBAA4B,GAAG;oBAAEC,QAAQ;gBAAI;YAC/E;YAEA,8DAA8D;YAC9D,MAAMI,cAAc,MAAM,AAACT,IAAIU,OAAO,CAACC,MAAM,CAAS;gBACpDC,IAAIJ;gBACJK,YAAYf,OAAOgB,KAAK,CAACC,YAAY;gBACrCC,MAAM;oBACJC,oBAAoBV;oBACpBF,QAAQ;gBACV;gBACAL;YACF;YAEA,OAAOE,SAASC,IAAI,CAACM;QACvB;QACAS,QAAQ;QACRC,MAAM;IACR;AACF"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { getAvailableSlots } from '../services/AvailabilityService.js';
|
|
2
|
+
export function createCheckAvailabilityEndpoint(config) {
|
|
3
|
+
return {
|
|
4
|
+
handler: async (req)=>{
|
|
5
|
+
const url = new URL(req.url);
|
|
6
|
+
const date = url.searchParams.get('date');
|
|
7
|
+
const resource = url.searchParams.get('resource');
|
|
8
|
+
const service = url.searchParams.get('service');
|
|
9
|
+
if (!date || !resource || !service) {
|
|
10
|
+
return Response.json({
|
|
11
|
+
message: 'Missing required query params: resource, date, service'
|
|
12
|
+
}, {
|
|
13
|
+
status: 400
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
const slots = await getAvailableSlots({
|
|
17
|
+
blockingStatuses: config.statusMachine.blockingStatuses,
|
|
18
|
+
date: new Date(date),
|
|
19
|
+
payload: req.payload,
|
|
20
|
+
req,
|
|
21
|
+
reservationSlug: config.slugs.reservations,
|
|
22
|
+
resourceId: resource,
|
|
23
|
+
resourceSlug: config.slugs.resources,
|
|
24
|
+
scheduleSlug: config.slugs.schedules,
|
|
25
|
+
serviceId: service,
|
|
26
|
+
serviceSlug: config.slugs.services
|
|
27
|
+
});
|
|
28
|
+
return Response.json({
|
|
29
|
+
slots
|
|
30
|
+
});
|
|
31
|
+
},
|
|
32
|
+
method: 'get',
|
|
33
|
+
path: '/reserve/availability'
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
//# sourceMappingURL=checkAvailability.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/endpoints/checkAvailability.ts"],"sourcesContent":["import type { Endpoint } from 'payload'\n\nimport type { ResolvedReservationPluginConfig } from '../types.js'\n\nimport { getAvailableSlots } from '../services/AvailabilityService.js'\n\nexport function createCheckAvailabilityEndpoint(\n config: ResolvedReservationPluginConfig,\n): Endpoint {\n return {\n handler: async (req) => {\n const url = new URL(req.url!)\n const date = url.searchParams.get('date')\n const resource = url.searchParams.get('resource')\n const service = url.searchParams.get('service')\n\n if (!date || !resource || !service) {\n return Response.json(\n { message: 'Missing required query params: resource, date, service' },\n { status: 400 },\n )\n }\n\n const slots = await getAvailableSlots({\n blockingStatuses: config.statusMachine.blockingStatuses,\n date: new Date(date),\n payload: req.payload,\n req,\n reservationSlug: config.slugs.reservations,\n resourceId: resource,\n resourceSlug: config.slugs.resources,\n scheduleSlug: config.slugs.schedules,\n serviceId: service,\n serviceSlug: config.slugs.services,\n })\n\n return Response.json({ slots })\n },\n method: 'get',\n path: '/reserve/availability',\n }\n}\n"],"names":["getAvailableSlots","createCheckAvailabilityEndpoint","config","handler","req","url","URL","date","searchParams","get","resource","service","Response","json","message","status","slots","blockingStatuses","statusMachine","Date","payload","reservationSlug","slugs","reservations","resourceId","resourceSlug","resources","scheduleSlug","schedules","serviceId","serviceSlug","services","method","path"],"mappings":"AAIA,SAASA,iBAAiB,QAAQ,qCAAoC;AAEtE,OAAO,SAASC,gCACdC,MAAuC;IAEvC,OAAO;QACLC,SAAS,OAAOC;YACd,MAAMC,MAAM,IAAIC,IAAIF,IAAIC,GAAG;YAC3B,MAAME,OAAOF,IAAIG,YAAY,CAACC,GAAG,CAAC;YAClC,MAAMC,WAAWL,IAAIG,YAAY,CAACC,GAAG,CAAC;YACtC,MAAME,UAAUN,IAAIG,YAAY,CAACC,GAAG,CAAC;YAErC,IAAI,CAACF,QAAQ,CAACG,YAAY,CAACC,SAAS;gBAClC,OAAOC,SAASC,IAAI,CAClB;oBAAEC,SAAS;gBAAyD,GACpE;oBAAEC,QAAQ;gBAAI;YAElB;YAEA,MAAMC,QAAQ,MAAMhB,kBAAkB;gBACpCiB,kBAAkBf,OAAOgB,aAAa,CAACD,gBAAgB;gBACvDV,MAAM,IAAIY,KAAKZ;gBACfa,SAAShB,IAAIgB,OAAO;gBACpBhB;gBACAiB,iBAAiBnB,OAAOoB,KAAK,CAACC,YAAY;gBAC1CC,YAAYd;gBACZe,cAAcvB,OAAOoB,KAAK,CAACI,SAAS;gBACpCC,cAAczB,OAAOoB,KAAK,CAACM,SAAS;gBACpCC,WAAWlB;gBACXmB,aAAa5B,OAAOoB,KAAK,CAACS,QAAQ;YACpC;YAEA,OAAOnB,SAASC,IAAI,CAAC;gBAAEG;YAAM;QAC/B;QACAgB,QAAQ;QACRC,MAAM;IACR;AACF"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export function createBookingEndpoint(config) {
|
|
2
|
+
return {
|
|
3
|
+
handler: async (req)=>{
|
|
4
|
+
const data = await req.json?.();
|
|
5
|
+
// Call beforeBookingCreate plugin hooks before creating the reservation
|
|
6
|
+
let bookingData = data;
|
|
7
|
+
if (config.hooks?.beforeBookingCreate) {
|
|
8
|
+
for (const hook of config.hooks.beforeBookingCreate){
|
|
9
|
+
bookingData = await hook({
|
|
10
|
+
data: bookingData,
|
|
11
|
+
req
|
|
12
|
+
}) ?? bookingData;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
// Create via Payload Local API — collection hooks handle conflict detection,
|
|
16
|
+
// endTime calculation, and status transitions
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
18
|
+
const reservation = await req.payload.create({
|
|
19
|
+
collection: config.slugs.reservations,
|
|
20
|
+
data: bookingData,
|
|
21
|
+
req
|
|
22
|
+
});
|
|
23
|
+
return Response.json(reservation, {
|
|
24
|
+
status: 201
|
|
25
|
+
});
|
|
26
|
+
},
|
|
27
|
+
method: 'post',
|
|
28
|
+
path: '/reserve/book'
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
//# sourceMappingURL=createBooking.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/endpoints/createBooking.ts"],"sourcesContent":["import type { Endpoint } from 'payload'\n\nimport type { ResolvedReservationPluginConfig } from '../types.js'\n\nexport function createBookingEndpoint(config: ResolvedReservationPluginConfig): Endpoint {\n return {\n handler: async (req) => {\n const data = (await req.json?.()) as Record<string, unknown>\n\n // Call beforeBookingCreate plugin hooks before creating the reservation\n let bookingData = data\n if (config.hooks?.beforeBookingCreate) {\n for (const hook of config.hooks.beforeBookingCreate) {\n bookingData = (await hook({ data: bookingData, req })) ?? bookingData\n }\n }\n\n // Create via Payload Local API — collection hooks handle conflict detection,\n // endTime calculation, and status transitions\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const reservation = await (req.payload.create as any)({\n collection: config.slugs.reservations,\n data: bookingData,\n req,\n })\n\n return Response.json(reservation, { status: 201 })\n },\n method: 'post',\n path: '/reserve/book',\n }\n}\n"],"names":["createBookingEndpoint","config","handler","req","data","json","bookingData","hooks","beforeBookingCreate","hook","reservation","payload","create","collection","slugs","reservations","Response","status","method","path"],"mappings":"AAIA,OAAO,SAASA,sBAAsBC,MAAuC;IAC3E,OAAO;QACLC,SAAS,OAAOC;YACd,MAAMC,OAAQ,MAAMD,IAAIE,IAAI;YAE5B,wEAAwE;YACxE,IAAIC,cAAcF;YAClB,IAAIH,OAAOM,KAAK,EAAEC,qBAAqB;gBACrC,KAAK,MAAMC,QAAQR,OAAOM,KAAK,CAACC,mBAAmB,CAAE;oBACnDF,cAAc,AAAC,MAAMG,KAAK;wBAAEL,MAAME;wBAAaH;oBAAI,MAAOG;gBAC5D;YACF;YAEA,6EAA6E;YAC7E,8CAA8C;YAC9C,8DAA8D;YAC9D,MAAMI,cAAc,MAAM,AAACP,IAAIQ,OAAO,CAACC,MAAM,CAAS;gBACpDC,YAAYZ,OAAOa,KAAK,CAACC,YAAY;gBACrCX,MAAME;gBACNH;YACF;YAEA,OAAOa,SAASX,IAAI,CAACK,aAAa;gBAAEO,QAAQ;YAAI;QAClD;QACAC,QAAQ;QACRC,MAAM;IACR;AACF"}
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inspect a collection's field list and return the set of top-level named
|
|
3
|
+
* fields as a plain Set<string>. Unnamed fields (rows, groups without a name,
|
|
4
|
+
* etc.) are skipped.
|
|
5
|
+
*/ function getNamedFields(fields) {
|
|
6
|
+
const names = new Set();
|
|
7
|
+
for (const field of fields){
|
|
8
|
+
if ('name' in field) {
|
|
9
|
+
names.add(field.name);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return names;
|
|
13
|
+
}
|
|
1
14
|
export function createCustomerSearchEndpoint(config) {
|
|
2
15
|
return {
|
|
3
16
|
handler: async (req)=>{
|
|
@@ -12,76 +25,81 @@ export function createCustomerSearchEndpoint(config) {
|
|
|
12
25
|
const search = url.searchParams.get('search') ?? '';
|
|
13
26
|
const limit = Math.min(Number(url.searchParams.get('limit') ?? '10'), 50);
|
|
14
27
|
const page = Math.max(Number(url.searchParams.get('page') ?? '1'), 1);
|
|
28
|
+
// Detect which fields exist on the target collection at runtime
|
|
29
|
+
const collectionConfig = req.payload.collections[config.slugs.customers]?.config;
|
|
30
|
+
const availableFields = collectionConfig ? getNamedFields(collectionConfig.fields) : new Set();
|
|
31
|
+
const hasName = availableFields.has('name');
|
|
32
|
+
const hasFirstName = availableFields.has('firstName');
|
|
33
|
+
const hasLastName = availableFields.has('lastName');
|
|
34
|
+
const hasPhone = availableFields.has('phone');
|
|
15
35
|
let where = {};
|
|
16
|
-
if (search
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
name: {
|
|
23
|
-
contains: search
|
|
24
|
-
}
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
phone: {
|
|
28
|
-
contains: search
|
|
29
|
-
}
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
email: {
|
|
33
|
-
contains: search
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
]
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
role: {
|
|
40
|
-
equals: config.customerRole
|
|
41
|
-
}
|
|
36
|
+
if (search) {
|
|
37
|
+
const orClauses = [];
|
|
38
|
+
if (hasName) {
|
|
39
|
+
orClauses.push({
|
|
40
|
+
name: {
|
|
41
|
+
contains: search
|
|
42
42
|
}
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
name: {
|
|
50
|
-
contains: search
|
|
51
|
-
}
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
phone: {
|
|
55
|
-
contains: search
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
email: {
|
|
60
|
-
contains: search
|
|
61
|
-
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
if (hasFirstName) {
|
|
46
|
+
orClauses.push({
|
|
47
|
+
firstName: {
|
|
48
|
+
contains: search
|
|
62
49
|
}
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
if (hasLastName) {
|
|
53
|
+
orClauses.push({
|
|
54
|
+
lastName: {
|
|
55
|
+
contains: search
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
// email is always present on auth collections
|
|
60
|
+
orClauses.push({
|
|
61
|
+
email: {
|
|
62
|
+
contains: search
|
|
69
63
|
}
|
|
64
|
+
});
|
|
65
|
+
if (hasPhone) {
|
|
66
|
+
orClauses.push({
|
|
67
|
+
phone: {
|
|
68
|
+
contains: search
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
where = {
|
|
73
|
+
or: orClauses
|
|
70
74
|
};
|
|
71
75
|
}
|
|
76
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
72
77
|
const result = await req.payload.find({
|
|
73
|
-
collection: config.
|
|
78
|
+
collection: config.slugs.customers,
|
|
74
79
|
limit,
|
|
75
80
|
page,
|
|
76
81
|
where
|
|
77
82
|
});
|
|
78
83
|
return Response.json({
|
|
79
|
-
docs: result.docs.map((doc)=>
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
email: doc
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
docs: result.docs.map((doc)=>{
|
|
85
|
+
const entry = {
|
|
86
|
+
id: doc['id'],
|
|
87
|
+
email: doc['email'] ?? ''
|
|
88
|
+
};
|
|
89
|
+
if (hasName) {
|
|
90
|
+
entry['name'] = doc['name'] ?? '';
|
|
91
|
+
}
|
|
92
|
+
if (hasFirstName) {
|
|
93
|
+
entry['firstName'] = doc['firstName'] ?? '';
|
|
94
|
+
}
|
|
95
|
+
if (hasLastName) {
|
|
96
|
+
entry['lastName'] = doc['lastName'] ?? '';
|
|
97
|
+
}
|
|
98
|
+
if (hasPhone) {
|
|
99
|
+
entry['phone'] = doc['phone'] ?? '';
|
|
100
|
+
}
|
|
101
|
+
return entry;
|
|
102
|
+
}),
|
|
85
103
|
hasNextPage: result.hasNextPage,
|
|
86
104
|
totalDocs: result.totalDocs
|
|
87
105
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/endpoints/customerSearch.ts"],"sourcesContent":["import type { Endpoint, Where } from 'payload'\n\nimport type { ResolvedReservationPluginConfig } from '../types.js'\n\nexport function createCustomerSearchEndpoint(\n config: ResolvedReservationPluginConfig,\n): Endpoint {\n return {\n handler: async (req) => {\n if (!req.user) {\n return Response.json({ message: 'Unauthorized' }, { status: 401 })\n }\n\n const url = new URL(req.url!)\n const search = url.searchParams.get('search') ?? ''\n const limit = Math.min(Number(url.searchParams.get('limit') ?? '10'), 50)\n const page = Math.max(Number(url.searchParams.get('page') ?? '1'), 1)\n\n
|
|
1
|
+
{"version":3,"sources":["../../src/endpoints/customerSearch.ts"],"sourcesContent":["import type { CollectionSlug, Endpoint, Field, Where } from 'payload'\n\nimport type { ResolvedReservationPluginConfig } from '../types.js'\n\n/**\n * Inspect a collection's field list and return the set of top-level named\n * fields as a plain Set<string>. Unnamed fields (rows, groups without a name,\n * etc.) are skipped.\n */\nfunction getNamedFields(fields: Field[]): Set<string> {\n const names = new Set<string>()\n for (const field of fields) {\n if ('name' in field) {\n names.add(field.name)\n }\n }\n return names\n}\n\nexport function createCustomerSearchEndpoint(\n config: ResolvedReservationPluginConfig,\n): Endpoint {\n return {\n handler: async (req) => {\n if (!req.user) {\n return Response.json({ message: 'Unauthorized' }, { status: 401 })\n }\n\n const url = new URL(req.url!)\n const search = url.searchParams.get('search') ?? ''\n const limit = Math.min(Number(url.searchParams.get('limit') ?? '10'), 50)\n const page = Math.max(Number(url.searchParams.get('page') ?? '1'), 1)\n\n // Detect which fields exist on the target collection at runtime\n const collectionConfig = req.payload.collections[config.slugs.customers as unknown as CollectionSlug]?.config\n const availableFields: Set<string> = collectionConfig\n ? getNamedFields(collectionConfig.fields)\n : new Set()\n\n const hasName = availableFields.has('name')\n const hasFirstName = availableFields.has('firstName')\n const hasLastName = availableFields.has('lastName')\n const hasPhone = availableFields.has('phone')\n\n let where: Where = {}\n\n if (search) {\n const orClauses: Where[] = []\n\n if (hasName) {\n orClauses.push({ name: { contains: search } })\n }\n if (hasFirstName) {\n orClauses.push({ firstName: { contains: search } })\n }\n if (hasLastName) {\n orClauses.push({ lastName: { contains: search } })\n }\n // email is always present on auth collections\n orClauses.push({ email: { contains: search } })\n if (hasPhone) {\n orClauses.push({ phone: { contains: search } })\n }\n\n where = { or: orClauses }\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const result = await (req.payload.find as any)({\n collection: config.slugs.customers,\n limit,\n page,\n where,\n })\n\n return Response.json({\n docs: (result.docs as Record<string, unknown>[]).map((doc) => {\n const entry: Record<string, unknown> = {\n id: doc['id'],\n email: doc['email'] ?? '',\n }\n\n if (hasName) {\n entry['name'] = doc['name'] ?? ''\n }\n if (hasFirstName) {\n entry['firstName'] = doc['firstName'] ?? ''\n }\n if (hasLastName) {\n entry['lastName'] = doc['lastName'] ?? ''\n }\n if (hasPhone) {\n entry['phone'] = doc['phone'] ?? ''\n }\n\n return entry\n }),\n hasNextPage: result.hasNextPage,\n totalDocs: result.totalDocs,\n })\n },\n method: 'get',\n path: '/reservation-customer-search',\n }\n}\n"],"names":["getNamedFields","fields","names","Set","field","add","name","createCustomerSearchEndpoint","config","handler","req","user","Response","json","message","status","url","URL","search","searchParams","get","limit","Math","min","Number","page","max","collectionConfig","payload","collections","slugs","customers","availableFields","hasName","has","hasFirstName","hasLastName","hasPhone","where","orClauses","push","contains","firstName","lastName","email","phone","or","result","find","collection","docs","map","doc","entry","id","hasNextPage","totalDocs","method","path"],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { getAvailableSlots } from '../services/AvailabilityService.js';
|
|
2
|
+
export function createGetSlotsEndpoint(config) {
|
|
3
|
+
return {
|
|
4
|
+
handler: async (req)=>{
|
|
5
|
+
const url = new URL(req.url);
|
|
6
|
+
const date = url.searchParams.get('date');
|
|
7
|
+
const resource = url.searchParams.get('resource');
|
|
8
|
+
const service = url.searchParams.get('service');
|
|
9
|
+
if (!date || !resource || !service) {
|
|
10
|
+
return Response.json({
|
|
11
|
+
error: 'Missing required query params: resource, date, service'
|
|
12
|
+
}, {
|
|
13
|
+
status: 400
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
const parsedDate = new Date(date);
|
|
17
|
+
if (isNaN(parsedDate.getTime())) {
|
|
18
|
+
return Response.json({
|
|
19
|
+
error: 'Invalid date format. Expected YYYY-MM-DD'
|
|
20
|
+
}, {
|
|
21
|
+
status: 400
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
const guestCount = Math.max(Number(url.searchParams.get('guestCount') ?? '1'), 1);
|
|
25
|
+
const slots = await getAvailableSlots({
|
|
26
|
+
blockingStatuses: config.statusMachine.blockingStatuses,
|
|
27
|
+
date: parsedDate,
|
|
28
|
+
payload: req.payload,
|
|
29
|
+
req,
|
|
30
|
+
reservationSlug: config.slugs.reservations,
|
|
31
|
+
resourceId: resource,
|
|
32
|
+
resourceSlug: config.slugs.resources,
|
|
33
|
+
scheduleSlug: config.slugs.schedules,
|
|
34
|
+
serviceId: service,
|
|
35
|
+
serviceSlug: config.slugs.services
|
|
36
|
+
});
|
|
37
|
+
return Response.json({
|
|
38
|
+
date,
|
|
39
|
+
guestCount,
|
|
40
|
+
slots: slots.map((s)=>({
|
|
41
|
+
end: s.end.toISOString(),
|
|
42
|
+
start: s.start.toISOString()
|
|
43
|
+
}))
|
|
44
|
+
});
|
|
45
|
+
},
|
|
46
|
+
method: 'get',
|
|
47
|
+
path: '/reserve/slots'
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
//# sourceMappingURL=getSlots.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/endpoints/getSlots.ts"],"sourcesContent":["import type { Endpoint } from 'payload'\n\nimport type { ResolvedReservationPluginConfig } from '../types.js'\n\nimport { getAvailableSlots } from '../services/AvailabilityService.js'\n\nexport function createGetSlotsEndpoint(config: ResolvedReservationPluginConfig): Endpoint {\n return {\n handler: async (req) => {\n const url = new URL(req.url!)\n const date = url.searchParams.get('date')\n const resource = url.searchParams.get('resource')\n const service = url.searchParams.get('service')\n\n if (!date || !resource || !service) {\n return Response.json(\n { error: 'Missing required query params: resource, date, service' },\n { status: 400 },\n )\n }\n\n const parsedDate = new Date(date)\n if (isNaN(parsedDate.getTime())) {\n return Response.json(\n { error: 'Invalid date format. Expected YYYY-MM-DD' },\n { status: 400 },\n )\n }\n\n const guestCount = Math.max(Number(url.searchParams.get('guestCount') ?? '1'), 1)\n\n const slots = await getAvailableSlots({\n blockingStatuses: config.statusMachine.blockingStatuses,\n date: parsedDate,\n payload: req.payload,\n req,\n reservationSlug: config.slugs.reservations,\n resourceId: resource,\n resourceSlug: config.slugs.resources,\n scheduleSlug: config.slugs.schedules,\n serviceId: service,\n serviceSlug: config.slugs.services,\n })\n\n return Response.json({\n date,\n guestCount,\n slots: slots.map((s) => ({ end: s.end.toISOString(), start: s.start.toISOString() })),\n })\n },\n method: 'get',\n path: '/reserve/slots',\n }\n}\n"],"names":["getAvailableSlots","createGetSlotsEndpoint","config","handler","req","url","URL","date","searchParams","get","resource","service","Response","json","error","status","parsedDate","Date","isNaN","getTime","guestCount","Math","max","Number","slots","blockingStatuses","statusMachine","payload","reservationSlug","slugs","reservations","resourceId","resourceSlug","resources","scheduleSlug","schedules","serviceId","serviceSlug","services","map","s","end","toISOString","start","method","path"],"mappings":"AAIA,SAASA,iBAAiB,QAAQ,qCAAoC;AAEtE,OAAO,SAASC,uBAAuBC,MAAuC;IAC5E,OAAO;QACLC,SAAS,OAAOC;YACd,MAAMC,MAAM,IAAIC,IAAIF,IAAIC,GAAG;YAC3B,MAAME,OAAOF,IAAIG,YAAY,CAACC,GAAG,CAAC;YAClC,MAAMC,WAAWL,IAAIG,YAAY,CAACC,GAAG,CAAC;YACtC,MAAME,UAAUN,IAAIG,YAAY,CAACC,GAAG,CAAC;YAErC,IAAI,CAACF,QAAQ,CAACG,YAAY,CAACC,SAAS;gBAClC,OAAOC,SAASC,IAAI,CAClB;oBAAEC,OAAO;gBAAyD,GAClE;oBAAEC,QAAQ;gBAAI;YAElB;YAEA,MAAMC,aAAa,IAAIC,KAAKV;YAC5B,IAAIW,MAAMF,WAAWG,OAAO,KAAK;gBAC/B,OAAOP,SAASC,IAAI,CAClB;oBAAEC,OAAO;gBAA2C,GACpD;oBAAEC,QAAQ;gBAAI;YAElB;YAEA,MAAMK,aAAaC,KAAKC,GAAG,CAACC,OAAOlB,IAAIG,YAAY,CAACC,GAAG,CAAC,iBAAiB,MAAM;YAE/E,MAAMe,QAAQ,MAAMxB,kBAAkB;gBACpCyB,kBAAkBvB,OAAOwB,aAAa,CAACD,gBAAgB;gBACvDlB,MAAMS;gBACNW,SAASvB,IAAIuB,OAAO;gBACpBvB;gBACAwB,iBAAiB1B,OAAO2B,KAAK,CAACC,YAAY;gBAC1CC,YAAYrB;gBACZsB,cAAc9B,OAAO2B,KAAK,CAACI,SAAS;gBACpCC,cAAchC,OAAO2B,KAAK,CAACM,SAAS;gBACpCC,WAAWzB;gBACX0B,aAAanC,OAAO2B,KAAK,CAACS,QAAQ;YACpC;YAEA,OAAO1B,SAASC,IAAI,CAAC;gBACnBN;gBACAa;gBACAI,OAAOA,MAAMe,GAAG,CAAC,CAACC,IAAO,CAAA;wBAAEC,KAAKD,EAAEC,GAAG,CAACC,WAAW;wBAAIC,OAAOH,EAAEG,KAAK,CAACD,WAAW;oBAAG,CAAA;YACpF;QACF;QACAE,QAAQ;QACRC,MAAM;IACR;AACF"}
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { calculateEndTime } from './reservations/calculateEndTime.js';
|
|
2
|
+
export { checkIdempotency } from './reservations/checkIdempotency.js';
|
|
3
|
+
export { onStatusChange } from './reservations/onStatusChange.js';
|
|
2
4
|
export { validateCancellation } from './reservations/validateCancellation.js';
|
|
3
5
|
export { validateConflicts } from './reservations/validateConflicts.js';
|
|
4
6
|
export { validateStatusTransition } from './reservations/validateStatusTransition.js';
|
package/dist/hooks/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { calculateEndTime } from './reservations/calculateEndTime.js';
|
|
2
|
+
export { checkIdempotency } from './reservations/checkIdempotency.js';
|
|
3
|
+
export { onStatusChange } from './reservations/onStatusChange.js';
|
|
2
4
|
export { validateCancellation } from './reservations/validateCancellation.js';
|
|
3
5
|
export { validateConflicts } from './reservations/validateConflicts.js';
|
|
4
6
|
export { validateStatusTransition } from './reservations/validateStatusTransition.js';
|
package/dist/hooks/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/hooks/index.ts"],"sourcesContent":["export { calculateEndTime } from './reservations/calculateEndTime.js'\nexport { validateCancellation } from './reservations/validateCancellation.js'\nexport { validateConflicts } from './reservations/validateConflicts.js'\nexport { validateStatusTransition } from './reservations/validateStatusTransition.js'\n"],"names":["calculateEndTime","validateCancellation","validateConflicts","validateStatusTransition"],"mappings":"AAAA,SAASA,gBAAgB,QAAQ,qCAAoC;AACrE,SAASC,oBAAoB,QAAQ,yCAAwC;AAC7E,SAASC,iBAAiB,QAAQ,sCAAqC;AACvE,SAASC,wBAAwB,QAAQ,6CAA4C"}
|
|
1
|
+
{"version":3,"sources":["../../src/hooks/index.ts"],"sourcesContent":["export { calculateEndTime } from './reservations/calculateEndTime.js'\nexport { checkIdempotency } from './reservations/checkIdempotency.js'\nexport { onStatusChange } from './reservations/onStatusChange.js'\nexport { validateCancellation } from './reservations/validateCancellation.js'\nexport { validateConflicts } from './reservations/validateConflicts.js'\nexport { validateStatusTransition } from './reservations/validateStatusTransition.js'\n"],"names":["calculateEndTime","checkIdempotency","onStatusChange","validateCancellation","validateConflicts","validateStatusTransition"],"mappings":"AAAA,SAASA,gBAAgB,QAAQ,qCAAoC;AACrE,SAASC,gBAAgB,QAAQ,qCAAoC;AACrE,SAASC,cAAc,QAAQ,mCAAkC;AACjE,SAASC,oBAAoB,QAAQ,yCAAwC;AAC7E,SAASC,iBAAiB,QAAQ,sCAAqC;AACvE,SAASC,wBAAwB,QAAQ,6CAA4C"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ValidationError } from 'payload';
|
|
2
|
+
import { computeEndTime } from '../../services/AvailabilityService.js';
|
|
3
|
+
import { resolveReservationItems } from '../../utilities/resolveReservationItems.js';
|
|
2
4
|
export const calculateEndTime = (config)=>async ({ context, data, req })=>{
|
|
3
5
|
if (context?.skipReservationHooks) {
|
|
4
6
|
return data;
|
|
@@ -6,15 +8,79 @@ export const calculateEndTime = (config)=>async ({ context, data, req })=>{
|
|
|
6
8
|
if (!data?.startTime || !data?.service) {
|
|
7
9
|
return data;
|
|
8
10
|
}
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
const items = resolveReservationItems(data);
|
|
12
|
+
if (items.length <= 1) {
|
|
13
|
+
// Single-resource: compute top-level endTime
|
|
14
|
+
const serviceId = typeof data.service === 'object' ? data.service.id : data.service;
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
|
+
const service = await req.payload.findByID({
|
|
17
|
+
id: serviceId,
|
|
18
|
+
collection: config.slugs.services,
|
|
19
|
+
req
|
|
20
|
+
});
|
|
21
|
+
if (!service?.duration && service?.durationType !== 'full-day') {
|
|
22
|
+
return data;
|
|
23
|
+
}
|
|
24
|
+
const durationType = service.durationType ?? 'fixed';
|
|
16
25
|
const startDate = new Date(data.startTime);
|
|
17
|
-
|
|
26
|
+
if (durationType === 'flexible') {
|
|
27
|
+
if (!data.endTime) {
|
|
28
|
+
throw new ValidationError({
|
|
29
|
+
errors: [
|
|
30
|
+
{
|
|
31
|
+
message: 'endTime is required for flexible duration services',
|
|
32
|
+
path: 'endTime'
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
// Validate customer-provided endTime (computeEndTime returns it back)
|
|
38
|
+
computeEndTime({
|
|
39
|
+
durationType: 'flexible',
|
|
40
|
+
endTime: new Date(data.endTime),
|
|
41
|
+
serviceDuration: service.duration,
|
|
42
|
+
startTime: startDate
|
|
43
|
+
});
|
|
44
|
+
} else {
|
|
45
|
+
const result = computeEndTime({
|
|
46
|
+
durationType,
|
|
47
|
+
serviceDuration: service.duration ?? 0,
|
|
48
|
+
startTime: startDate
|
|
49
|
+
});
|
|
50
|
+
data.endTime = result.endTime.toISOString();
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
// Multi-resource: compute endTime per item
|
|
54
|
+
for (const item of data.items){
|
|
55
|
+
if (!item.startTime) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const itemServiceId = typeof item.service === 'object' ? item.service.id : item.service ?? (typeof data.service === 'object' ? data.service.id : data.service);
|
|
59
|
+
if (!itemServiceId) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
63
|
+
const service = await req.payload.findByID({
|
|
64
|
+
id: itemServiceId,
|
|
65
|
+
collection: config.slugs.services,
|
|
66
|
+
req
|
|
67
|
+
});
|
|
68
|
+
if (!service?.duration && service?.durationType !== 'full-day') {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const durationType = service.durationType ?? 'fixed';
|
|
72
|
+
if (durationType === 'flexible' && !item.endTime) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (durationType !== 'flexible') {
|
|
76
|
+
const result = computeEndTime({
|
|
77
|
+
durationType,
|
|
78
|
+
serviceDuration: service.duration ?? 0,
|
|
79
|
+
startTime: new Date(item.startTime)
|
|
80
|
+
});
|
|
81
|
+
item.endTime = result.endTime.toISOString();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
18
84
|
}
|
|
19
85
|
return data;
|
|
20
86
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/hooks/reservations/calculateEndTime.ts"],"sourcesContent":["import type { CollectionBeforeChangeHook } from 'payload'\n\nimport type { ResolvedReservationPluginConfig } from '../../types.js'\n\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../src/hooks/reservations/calculateEndTime.ts"],"sourcesContent":["import type { CollectionBeforeChangeHook } from 'payload'\n\nimport { ValidationError } from 'payload'\n\nimport type { DurationType, ResolvedReservationPluginConfig } from '../../types.js'\n\nimport { computeEndTime } from '../../services/AvailabilityService.js'\nimport { resolveReservationItems } from '../../utilities/resolveReservationItems.js'\n\nexport const calculateEndTime =\n (config: ResolvedReservationPluginConfig): CollectionBeforeChangeHook =>\n async ({ context, data, req }) => {\n if (context?.skipReservationHooks) {return data}\n\n if (!data?.startTime || !data?.service) {return data}\n\n const items = resolveReservationItems(data)\n\n if (items.length <= 1) {\n // Single-resource: compute top-level endTime\n const serviceId = typeof data.service === 'object' ? data.service.id : data.service\n\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\n if (!service?.duration && service?.durationType !== 'full-day') {return data}\n\n const durationType = ((service.durationType as string) ?? 'fixed') as DurationType\n const startDate = new Date(data.startTime)\n\n if (durationType === 'flexible') {\n if (!data.endTime) {\n throw new ValidationError({\n errors: [{ message: 'endTime is required for flexible duration services', path: 'endTime' }],\n })\n }\n // Validate customer-provided endTime (computeEndTime returns it back)\n computeEndTime({\n durationType: 'flexible',\n endTime: new Date(data.endTime),\n serviceDuration: service.duration as number,\n startTime: startDate,\n })\n } else {\n const result = computeEndTime({\n durationType,\n serviceDuration: (service.duration as number) ?? 0,\n startTime: startDate,\n })\n data.endTime = result.endTime.toISOString()\n }\n } else {\n // Multi-resource: compute endTime per item\n for (const item of data.items as Array<Record<string, unknown>>) {\n if (!item.startTime) {continue}\n\n const itemServiceId = typeof item.service === 'object'\n ? (item.service as { id: string }).id\n : (item.service as string) ?? (typeof data.service === 'object' ? data.service.id : data.service)\n\n if (!itemServiceId) {continue}\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const service = await (req.payload.findByID as any)({\n id: itemServiceId,\n collection: config.slugs.services,\n req,\n })\n\n if (!service?.duration && service?.durationType !== 'full-day') {continue}\n\n const durationType = ((service.durationType as string) ?? 'fixed') as DurationType\n\n if (durationType === 'flexible' && !item.endTime) {continue}\n\n if (durationType !== 'flexible') {\n const result = computeEndTime({\n durationType,\n serviceDuration: (service.duration as number) ?? 0,\n startTime: new Date(item.startTime as string),\n })\n item.endTime = result.endTime.toISOString()\n }\n }\n }\n\n return data\n }\n"],"names":["ValidationError","computeEndTime","resolveReservationItems","calculateEndTime","config","context","data","req","skipReservationHooks","startTime","service","items","length","serviceId","id","payload","findByID","collection","slugs","services","duration","durationType","startDate","Date","endTime","errors","message","path","serviceDuration","result","toISOString","item","itemServiceId"],"mappings":"AAEA,SAASA,eAAe,QAAQ,UAAS;AAIzC,SAASC,cAAc,QAAQ,wCAAuC;AACtE,SAASC,uBAAuB,QAAQ,6CAA4C;AAEpF,OAAO,MAAMC,mBACX,CAACC,SACD,OAAO,EAAEC,OAAO,EAAEC,IAAI,EAAEC,GAAG,EAAE;QAC3B,IAAIF,SAASG,sBAAsB;YAAC,OAAOF;QAAI;QAE/C,IAAI,CAACA,MAAMG,aAAa,CAACH,MAAMI,SAAS;YAAC,OAAOJ;QAAI;QAEpD,MAAMK,QAAQT,wBAAwBI;QAEtC,IAAIK,MAAMC,MAAM,IAAI,GAAG;YACrB,6CAA6C;YAC7C,MAAMC,YAAY,OAAOP,KAAKI,OAAO,KAAK,WAAWJ,KAAKI,OAAO,CAACI,EAAE,GAAGR,KAAKI,OAAO;YAEnF,8DAA8D;YAC9D,MAAMA,UAAU,MAAM,AAACH,IAAIQ,OAAO,CAACC,QAAQ,CAAS;gBAClDF,IAAID;gBACJI,YAAYb,OAAOc,KAAK,CAACC,QAAQ;gBACjCZ;YACF;YAEA,IAAI,CAACG,SAASU,YAAYV,SAASW,iBAAiB,YAAY;gBAAC,OAAOf;YAAI;YAE5E,MAAMe,eAAgB,AAACX,QAAQW,YAAY,IAAe;YAC1D,MAAMC,YAAY,IAAIC,KAAKjB,KAAKG,SAAS;YAEzC,IAAIY,iBAAiB,YAAY;gBAC/B,IAAI,CAACf,KAAKkB,OAAO,EAAE;oBACjB,MAAM,IAAIxB,gBAAgB;wBACxByB,QAAQ;4BAAC;gCAAEC,SAAS;gCAAsDC,MAAM;4BAAU;yBAAE;oBAC9F;gBACF;gBACA,sEAAsE;gBACtE1B,eAAe;oBACboB,cAAc;oBACdG,SAAS,IAAID,KAAKjB,KAAKkB,OAAO;oBAC9BI,iBAAiBlB,QAAQU,QAAQ;oBACjCX,WAAWa;gBACb;YACF,OAAO;gBACL,MAAMO,SAAS5B,eAAe;oBAC5BoB;oBACAO,iBAAiB,AAAClB,QAAQU,QAAQ,IAAe;oBACjDX,WAAWa;gBACb;gBACAhB,KAAKkB,OAAO,GAAGK,OAAOL,OAAO,CAACM,WAAW;YAC3C;QACF,OAAO;YACL,2CAA2C;YAC3C,KAAK,MAAMC,QAAQzB,KAAKK,KAAK,CAAoC;gBAC/D,IAAI,CAACoB,KAAKtB,SAAS,EAAE;oBAAC;gBAAQ;gBAE9B,MAAMuB,gBAAgB,OAAOD,KAAKrB,OAAO,KAAK,WAC1C,AAACqB,KAAKrB,OAAO,CAAoBI,EAAE,GACnC,AAACiB,KAAKrB,OAAO,IAAgB,CAAA,OAAOJ,KAAKI,OAAO,KAAK,WAAWJ,KAAKI,OAAO,CAACI,EAAE,GAAGR,KAAKI,OAAO,AAAD;gBAEjG,IAAI,CAACsB,eAAe;oBAAC;gBAAQ;gBAE7B,8DAA8D;gBAC9D,MAAMtB,UAAU,MAAM,AAACH,IAAIQ,OAAO,CAACC,QAAQ,CAAS;oBAClDF,IAAIkB;oBACJf,YAAYb,OAAOc,KAAK,CAACC,QAAQ;oBACjCZ;gBACF;gBAEA,IAAI,CAACG,SAASU,YAAYV,SAASW,iBAAiB,YAAY;oBAAC;gBAAQ;gBAEzE,MAAMA,eAAgB,AAACX,QAAQW,YAAY,IAAe;gBAE1D,IAAIA,iBAAiB,cAAc,CAACU,KAAKP,OAAO,EAAE;oBAAC;gBAAQ;gBAE3D,IAAIH,iBAAiB,YAAY;oBAC/B,MAAMQ,SAAS5B,eAAe;wBAC5BoB;wBACAO,iBAAiB,AAAClB,QAAQU,QAAQ,IAAe;wBACjDX,WAAW,IAAIc,KAAKQ,KAAKtB,SAAS;oBACpC;oBACAsB,KAAKP,OAAO,GAAGK,OAAOL,OAAO,CAACM,WAAW;gBAC3C;YACF;QACF;QAEA,OAAOxB;IACT,EAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ValidationError } from 'payload';
|
|
2
|
+
export const checkIdempotency = (config)=>async ({ context, data, operation, req })=>{
|
|
3
|
+
if (context?.skipReservationHooks) {
|
|
4
|
+
return data;
|
|
5
|
+
}
|
|
6
|
+
if (operation !== 'create' || !data?.idempotencyKey) {
|
|
7
|
+
return data;
|
|
8
|
+
}
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
10
|
+
const { totalDocs } = await req.payload.count({
|
|
11
|
+
collection: config.slugs.reservations,
|
|
12
|
+
req,
|
|
13
|
+
where: {
|
|
14
|
+
idempotencyKey: {
|
|
15
|
+
equals: data.idempotencyKey
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
if (totalDocs > 0) {
|
|
20
|
+
throw new ValidationError({
|
|
21
|
+
errors: [
|
|
22
|
+
{
|
|
23
|
+
message: 'Duplicate reservation',
|
|
24
|
+
path: 'idempotencyKey'
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
return data;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
//# sourceMappingURL=checkIdempotency.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/hooks/reservations/checkIdempotency.ts"],"sourcesContent":["import type { CollectionBeforeChangeHook } from 'payload'\n\nimport { ValidationError } from 'payload'\n\nimport type { ResolvedReservationPluginConfig } from '../../types.js'\n\nexport const checkIdempotency =\n (config: ResolvedReservationPluginConfig): CollectionBeforeChangeHook =>\n async ({ context, data, operation, req }) => {\n if (context?.skipReservationHooks) {return data}\n if (operation !== 'create' || !data?.idempotencyKey) {return data}\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const { totalDocs } = await (req.payload.count as any)({\n collection: config.slugs.reservations,\n req,\n where: { idempotencyKey: { equals: data.idempotencyKey } },\n })\n\n if (totalDocs > 0) {\n throw new ValidationError({\n errors: [{ message: 'Duplicate reservation', path: 'idempotencyKey' }],\n })\n }\n return data\n }\n"],"names":["ValidationError","checkIdempotency","config","context","data","operation","req","skipReservationHooks","idempotencyKey","totalDocs","payload","count","collection","slugs","reservations","where","equals","errors","message","path"],"mappings":"AAEA,SAASA,eAAe,QAAQ,UAAS;AAIzC,OAAO,MAAMC,mBACX,CAACC,SACD,OAAO,EAAEC,OAAO,EAAEC,IAAI,EAAEC,SAAS,EAAEC,GAAG,EAAE;QACtC,IAAIH,SAASI,sBAAsB;YAAC,OAAOH;QAAI;QAC/C,IAAIC,cAAc,YAAY,CAACD,MAAMI,gBAAgB;YAAC,OAAOJ;QAAI;QAEjE,8DAA8D;QAC9D,MAAM,EAAEK,SAAS,EAAE,GAAG,MAAM,AAACH,IAAII,OAAO,CAACC,KAAK,CAAS;YACrDC,YAAYV,OAAOW,KAAK,CAACC,YAAY;YACrCR;YACAS,OAAO;gBAAEP,gBAAgB;oBAAEQ,QAAQZ,KAAKI,cAAc;gBAAC;YAAE;QAC3D;QAEA,IAAIC,YAAY,GAAG;YACjB,MAAM,IAAIT,gBAAgB;gBACxBiB,QAAQ;oBAAC;wBAAEC,SAAS;wBAAyBC,MAAM;oBAAiB;iBAAE;YACxE;QACF;QACA,OAAOf;IACT,EAAC"}
|