gorombo-payload-appointments 1.0.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/endpoints/getAvailableSlots.d.ts +3 -9
- package/dist/endpoints/getAvailableSlots.js +250 -255
- package/dist/endpoints/getAvailableSlots.js.map +1 -1
- package/dist/hooks/sendCustomerEmail.d.ts +3 -3
- package/dist/hooks/sendCustomerEmail.js +65 -65
- package/dist/hooks/sendCustomerEmail.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +12 -21
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
- package/dist/collections/TeamMembers.d.ts +0 -2
- package/dist/collections/TeamMembers.js +0 -184
- package/dist/collections/TeamMembers.js.map +0 -1
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import type { PayloadHandler } from 'payload';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* Query params:
|
|
6
|
-
* - date: YYYY-MM-DD (required)
|
|
7
|
-
* - serviceId: string (required)
|
|
8
|
-
* - teamMemberId: string (optional)
|
|
9
|
-
*
|
|
10
|
-
* Returns available time slots for booking
|
|
3
|
+
* Factory function to create the available slots handler with configurable team collection
|
|
4
|
+
* @param teamCollectionSlug - The slug of the external team collection to use
|
|
11
5
|
*/
|
|
12
|
-
export declare const
|
|
6
|
+
export declare const createGetAvailableSlotsHandler: (teamCollectionSlug: string) => PayloadHandler;
|
|
@@ -1,291 +1,286 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
// Get service details
|
|
41
|
-
const service = await req.payload.findByID({
|
|
42
|
-
id: serviceId,
|
|
43
|
-
collection: 'services'
|
|
44
|
-
});
|
|
45
|
-
if (!service) {
|
|
46
|
-
return Response.json({
|
|
47
|
-
error: 'Service not found'
|
|
48
|
-
}, {
|
|
49
|
-
status: 404
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
if (!service.isActive) {
|
|
53
|
-
return Response.json({
|
|
54
|
-
error: 'Service is not available for booking'
|
|
55
|
-
}, {
|
|
56
|
-
status: 400
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
// Get opening times settings
|
|
60
|
-
const openingTimes = await req.payload.findGlobal({
|
|
61
|
-
slug: 'opening-times'
|
|
62
|
-
});
|
|
63
|
-
// Check booking window
|
|
64
|
-
const now = new Date();
|
|
65
|
-
const maxBookingDate = new Date(now);
|
|
66
|
-
maxBookingDate.setDate(maxBookingDate.getDate() + (openingTimes?.maxAdvanceBookingDays || 30));
|
|
67
|
-
const minBookingDate = new Date(now);
|
|
68
|
-
minBookingDate.setHours(minBookingDate.getHours() + (openingTimes?.minAdvanceBookingHours || 1));
|
|
69
|
-
if (requestedDate > maxBookingDate) {
|
|
70
|
-
return Response.json({
|
|
71
|
-
error: `Cannot book more than ${openingTimes?.maxAdvanceBookingDays || 30} days in advance`
|
|
72
|
-
}, {
|
|
73
|
-
status: 400
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
if (requestedDate < minBookingDate) {
|
|
77
|
-
return Response.json({
|
|
78
|
-
error: `Must book at least ${openingTimes?.minAdvanceBookingHours || 1} hours in advance`
|
|
79
|
-
}, {
|
|
80
|
-
status: 400
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
// Get day of week
|
|
84
|
-
const dayNames = [
|
|
85
|
-
'sunday',
|
|
86
|
-
'monday',
|
|
87
|
-
'tuesday',
|
|
88
|
-
'wednesday',
|
|
89
|
-
'thursday',
|
|
90
|
-
'friday',
|
|
91
|
-
'saturday'
|
|
92
|
-
];
|
|
93
|
-
const dayOfWeek = dayNames[requestedDate.getDay()];
|
|
94
|
-
// Find business hours for this day
|
|
95
|
-
const daySchedule = openingTimes?.schedule?.find((s)=>s.day === dayOfWeek);
|
|
96
|
-
if (!daySchedule || !daySchedule.isOpen) {
|
|
97
|
-
return Response.json({
|
|
98
|
-
date: dateParam,
|
|
99
|
-
message: 'Business is closed on this day',
|
|
100
|
-
serviceId,
|
|
101
|
-
slots: [],
|
|
102
|
-
teamMemberId: teamMemberId || undefined
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
// Calculate slot duration (service duration + buffer)
|
|
106
|
-
const slotDuration = service.duration || 30;
|
|
107
|
-
const bufferBefore = service.bufferBefore || 0;
|
|
108
|
-
const bufferAfter = service.bufferAfter || 0;
|
|
109
|
-
const totalSlotTime = slotDuration + bufferBefore + bufferAfter;
|
|
110
|
-
// Parse business hours
|
|
111
|
-
const [openHour, openMin] = (daySchedule.openTime || '09:00').split(':').map(Number);
|
|
112
|
-
const [closeHour, closeMin] = (daySchedule.closeTime || '17:00').split(':').map(Number);
|
|
113
|
-
// Parse break times if set
|
|
114
|
-
let breakStart = null;
|
|
115
|
-
let breakEnd = null;
|
|
116
|
-
if (daySchedule.breakStart && daySchedule.breakEnd) {
|
|
117
|
-
const [breakStartHour, breakStartMin] = daySchedule.breakStart.split(':').map(Number);
|
|
118
|
-
const [breakEndHour, breakEndMin] = daySchedule.breakEnd.split(':').map(Number);
|
|
119
|
-
breakStart = breakStartHour * 60 + breakStartMin;
|
|
120
|
-
breakEnd = breakEndHour * 60 + breakEndMin;
|
|
121
|
-
}
|
|
122
|
-
// Get team member availability if specified
|
|
123
|
-
let teamMemberSchedule = null;
|
|
124
|
-
if (teamMemberId) {
|
|
125
|
-
const teamMember = await req.payload.findByID({
|
|
126
|
-
id: teamMemberId,
|
|
127
|
-
collection: 'team-members'
|
|
2
|
+
* Factory function to create the available slots handler with configurable team collection
|
|
3
|
+
* @param teamCollectionSlug - The slug of the external team collection to use
|
|
4
|
+
*/ export const createGetAvailableSlotsHandler = (teamCollectionSlug)=>async (req)=>{
|
|
5
|
+
try {
|
|
6
|
+
const url = new URL(req.url || '', 'http://localhost');
|
|
7
|
+
const dateParam = url.searchParams.get('date');
|
|
8
|
+
const serviceId = url.searchParams.get('serviceId');
|
|
9
|
+
const teamMemberId = url.searchParams.get('teamMemberId');
|
|
10
|
+
// Validate required params
|
|
11
|
+
if (!dateParam) {
|
|
12
|
+
return Response.json({
|
|
13
|
+
error: 'Missing required parameter: date (YYYY-MM-DD format)'
|
|
14
|
+
}, {
|
|
15
|
+
status: 400
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
if (!serviceId) {
|
|
19
|
+
return Response.json({
|
|
20
|
+
error: 'Missing required parameter: serviceId'
|
|
21
|
+
}, {
|
|
22
|
+
status: 400
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
// Parse date
|
|
26
|
+
const requestedDate = new Date(dateParam);
|
|
27
|
+
if (isNaN(requestedDate.getTime())) {
|
|
28
|
+
return Response.json({
|
|
29
|
+
error: 'Invalid date format. Use YYYY-MM-DD'
|
|
30
|
+
}, {
|
|
31
|
+
status: 400
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
// Get service details
|
|
35
|
+
const service = await req.payload.findByID({
|
|
36
|
+
id: serviceId,
|
|
37
|
+
collection: 'services'
|
|
128
38
|
});
|
|
129
|
-
if (!
|
|
39
|
+
if (!service) {
|
|
130
40
|
return Response.json({
|
|
131
|
-
error: '
|
|
41
|
+
error: 'Service not found'
|
|
132
42
|
}, {
|
|
133
43
|
status: 404
|
|
134
44
|
});
|
|
135
45
|
}
|
|
136
|
-
if (!
|
|
46
|
+
if (!service.isActive) {
|
|
137
47
|
return Response.json({
|
|
138
|
-
error: '
|
|
48
|
+
error: 'Service is not available for booking'
|
|
139
49
|
}, {
|
|
140
50
|
status: 400
|
|
141
51
|
});
|
|
142
52
|
}
|
|
143
|
-
//
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
53
|
+
// Get opening times settings
|
|
54
|
+
const openingTimes = await req.payload.findGlobal({
|
|
55
|
+
slug: 'opening-times'
|
|
56
|
+
});
|
|
57
|
+
// Check booking window
|
|
58
|
+
const now = new Date();
|
|
59
|
+
const maxBookingDate = new Date(now);
|
|
60
|
+
maxBookingDate.setDate(maxBookingDate.getDate() + (openingTimes?.maxAdvanceBookingDays || 30));
|
|
61
|
+
const minBookingDate = new Date(now);
|
|
62
|
+
minBookingDate.setHours(minBookingDate.getHours() + (openingTimes?.minAdvanceBookingHours || 1));
|
|
63
|
+
if (requestedDate > maxBookingDate) {
|
|
147
64
|
return Response.json({
|
|
148
|
-
error:
|
|
65
|
+
error: `Cannot book more than ${openingTimes?.maxAdvanceBookingDays || 30} days in advance`
|
|
149
66
|
}, {
|
|
150
67
|
status: 400
|
|
151
68
|
});
|
|
152
69
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
70
|
+
if (requestedDate < minBookingDate) {
|
|
71
|
+
return Response.json({
|
|
72
|
+
error: `Must book at least ${openingTimes?.minAdvanceBookingHours || 1} hours in advance`
|
|
73
|
+
}, {
|
|
74
|
+
status: 400
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
// Get day of week
|
|
78
|
+
const dayNames = [
|
|
79
|
+
'sunday',
|
|
80
|
+
'monday',
|
|
81
|
+
'tuesday',
|
|
82
|
+
'wednesday',
|
|
83
|
+
'thursday',
|
|
84
|
+
'friday',
|
|
85
|
+
'saturday'
|
|
86
|
+
];
|
|
87
|
+
const dayOfWeek = dayNames[requestedDate.getDay()];
|
|
88
|
+
// Find business hours for this day
|
|
89
|
+
const daySchedule = openingTimes?.schedule?.find((s)=>s.day === dayOfWeek);
|
|
90
|
+
if (!daySchedule || !daySchedule.isOpen) {
|
|
156
91
|
return Response.json({
|
|
157
92
|
date: dateParam,
|
|
158
|
-
message: '
|
|
93
|
+
message: 'Business is closed on this day',
|
|
159
94
|
serviceId,
|
|
160
95
|
slots: [],
|
|
161
|
-
teamMemberId
|
|
96
|
+
teamMemberId: teamMemberId || undefined
|
|
162
97
|
});
|
|
163
98
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
},
|
|
192
|
-
|
|
99
|
+
// Calculate slot duration (service duration + buffer)
|
|
100
|
+
const slotDuration = service.duration || 30;
|
|
101
|
+
const bufferBefore = service.bufferBefore || 0;
|
|
102
|
+
const bufferAfter = service.bufferAfter || 0;
|
|
103
|
+
const totalSlotTime = slotDuration + bufferBefore + bufferAfter;
|
|
104
|
+
// Parse business hours
|
|
105
|
+
const [openHour, openMin] = (daySchedule.openTime || '09:00').split(':').map(Number);
|
|
106
|
+
const [closeHour, closeMin] = (daySchedule.closeTime || '17:00').split(':').map(Number);
|
|
107
|
+
// Parse break times if set
|
|
108
|
+
let breakStart = null;
|
|
109
|
+
let breakEnd = null;
|
|
110
|
+
if (daySchedule.breakStart && daySchedule.breakEnd) {
|
|
111
|
+
const [breakStartHour, breakStartMin] = daySchedule.breakStart.split(':').map(Number);
|
|
112
|
+
const [breakEndHour, breakEndMin] = daySchedule.breakEnd.split(':').map(Number);
|
|
113
|
+
breakStart = breakStartHour * 60 + breakStartMin;
|
|
114
|
+
breakEnd = breakEndHour * 60 + breakEndMin;
|
|
115
|
+
}
|
|
116
|
+
// Get team member availability if specified
|
|
117
|
+
let teamMemberSchedule = null;
|
|
118
|
+
if (teamMemberId) {
|
|
119
|
+
const teamMember = await req.payload.findByID({
|
|
120
|
+
id: teamMemberId,
|
|
121
|
+
collection: teamCollectionSlug
|
|
122
|
+
});
|
|
123
|
+
if (!teamMember) {
|
|
124
|
+
return Response.json({
|
|
125
|
+
error: 'Team member not found'
|
|
126
|
+
}, {
|
|
127
|
+
status: 404
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
// Only block if explicitly set to false - undefined means taking appointments
|
|
131
|
+
if (teamMember.takingAppointments === false) {
|
|
132
|
+
return Response.json({
|
|
133
|
+
error: 'Team member is not taking appointments'
|
|
134
|
+
}, {
|
|
135
|
+
status: 400
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
// Check if team member provides this service
|
|
139
|
+
const teamMemberServices = teamMember.services || [];
|
|
140
|
+
const serviceIds = teamMemberServices.map((s)=>typeof s === 'object' ? s.id : s);
|
|
141
|
+
if (serviceIds.length > 0 && !serviceIds.includes(serviceId)) {
|
|
142
|
+
return Response.json({
|
|
143
|
+
error: 'Team member does not provide this service'
|
|
144
|
+
}, {
|
|
145
|
+
status: 400
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
// Get team member's schedule for this day
|
|
149
|
+
teamMemberSchedule = teamMember.availability?.find((a)=>a.day === dayOfWeek);
|
|
150
|
+
if (teamMemberSchedule && !teamMemberSchedule.isAvailable) {
|
|
151
|
+
return Response.json({
|
|
152
|
+
date: dateParam,
|
|
153
|
+
message: 'Team member is not available on this day',
|
|
154
|
+
serviceId,
|
|
155
|
+
slots: [],
|
|
156
|
+
teamMemberId
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// Get existing appointments for this day
|
|
161
|
+
const dayStart = new Date(requestedDate);
|
|
162
|
+
dayStart.setHours(0, 0, 0, 0);
|
|
163
|
+
const dayEnd = new Date(requestedDate);
|
|
164
|
+
dayEnd.setHours(23, 59, 59, 999);
|
|
165
|
+
const existingAppointments = await req.payload.find({
|
|
166
|
+
collection: 'appointments',
|
|
167
|
+
limit: 100,
|
|
168
|
+
where: {
|
|
169
|
+
and: [
|
|
193
170
|
{
|
|
194
|
-
|
|
195
|
-
|
|
171
|
+
startDateTime: {
|
|
172
|
+
greater_than_equal: dayStart.toISOString()
|
|
196
173
|
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
if (teamMemberSchedule) {
|
|
219
|
-
const [tmOpenHour, tmOpenMin] = (teamMemberSchedule.startTime || '09:00').split(':').map(Number);
|
|
220
|
-
const [tmCloseHour, tmCloseMin] = (teamMemberSchedule.endTime || '17:00').split(':').map(Number);
|
|
221
|
-
effectiveOpenMinutes = Math.max(effectiveOpenMinutes, tmOpenHour * 60 + tmOpenMin);
|
|
222
|
-
effectiveCloseMinutes = Math.min(effectiveCloseMinutes, tmCloseHour * 60 + tmCloseMin);
|
|
223
|
-
// Apply team member break
|
|
224
|
-
if (teamMemberSchedule.breakStart && teamMemberSchedule.breakEnd) {
|
|
225
|
-
const [tmBreakStartHour, tmBreakStartMin] = teamMemberSchedule.breakStart.split(':').map(Number);
|
|
226
|
-
const [tmBreakEndHour, tmBreakEndMin] = teamMemberSchedule.breakEnd.split(':').map(Number);
|
|
227
|
-
// Use the more restrictive break
|
|
228
|
-
if (breakStart === null) {
|
|
229
|
-
breakStart = tmBreakStartHour * 60 + tmBreakStartMin;
|
|
230
|
-
breakEnd = tmBreakEndHour * 60 + tmBreakEndMin;
|
|
231
|
-
} else {
|
|
232
|
-
breakStart = Math.min(breakStart, tmBreakStartHour * 60 + tmBreakStartMin);
|
|
233
|
-
breakEnd = Math.max(breakEnd, tmBreakEndHour * 60 + tmBreakEndMin);
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
startDateTime: {
|
|
177
|
+
less_than: dayEnd.toISOString()
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
status: {
|
|
182
|
+
not_in: [
|
|
183
|
+
'cancelled'
|
|
184
|
+
]
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
...teamMemberId ? [
|
|
188
|
+
{
|
|
189
|
+
teamMember: {
|
|
190
|
+
equals: teamMemberId
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
] : []
|
|
194
|
+
]
|
|
234
195
|
}
|
|
196
|
+
});
|
|
197
|
+
// Convert appointments to blocked time ranges (in minutes from midnight)
|
|
198
|
+
const blockedRanges = [];
|
|
199
|
+
for (const apt of existingAppointments.docs){
|
|
200
|
+
const aptStart = new Date(apt.startDateTime);
|
|
201
|
+
const aptEnd = apt.endDateTime ? new Date(apt.endDateTime) : new Date(aptStart.getTime() + 30 * 60000);
|
|
202
|
+
const startMinutes = aptStart.getHours() * 60 + aptStart.getMinutes();
|
|
203
|
+
const endMinutes = aptEnd.getHours() * 60 + aptEnd.getMinutes();
|
|
204
|
+
blockedRanges.push({
|
|
205
|
+
end: endMinutes + bufferAfter,
|
|
206
|
+
start: startMinutes - bufferBefore
|
|
207
|
+
});
|
|
235
208
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
209
|
+
// Determine effective working hours
|
|
210
|
+
let effectiveOpenMinutes = openHour * 60 + openMin;
|
|
211
|
+
let effectiveCloseMinutes = closeHour * 60 + closeMin;
|
|
212
|
+
// Apply team member schedule if available
|
|
213
|
+
if (teamMemberSchedule) {
|
|
214
|
+
const [tmOpenHour, tmOpenMin] = (teamMemberSchedule.startTime || '09:00').split(':').map(Number);
|
|
215
|
+
const [tmCloseHour, tmCloseMin] = (teamMemberSchedule.endTime || '17:00').split(':').map(Number);
|
|
216
|
+
effectiveOpenMinutes = Math.max(effectiveOpenMinutes, tmOpenHour * 60 + tmOpenMin);
|
|
217
|
+
effectiveCloseMinutes = Math.min(effectiveCloseMinutes, tmCloseHour * 60 + tmCloseMin);
|
|
218
|
+
// Apply team member break
|
|
219
|
+
if (teamMemberSchedule.breakStart && teamMemberSchedule.breakEnd) {
|
|
220
|
+
const [tmBreakStartHour, tmBreakStartMin] = teamMemberSchedule.breakStart.split(':').map(Number);
|
|
221
|
+
const [tmBreakEndHour, tmBreakEndMin] = teamMemberSchedule.breakEnd.split(':').map(Number);
|
|
222
|
+
// Use the more restrictive break
|
|
223
|
+
if (breakStart === null) {
|
|
224
|
+
breakStart = tmBreakStartHour * 60 + tmBreakStartMin;
|
|
225
|
+
breakEnd = tmBreakEndHour * 60 + tmBreakEndMin;
|
|
226
|
+
} else {
|
|
227
|
+
breakStart = Math.min(breakStart, tmBreakStartHour * 60 + tmBreakStartMin);
|
|
228
|
+
breakEnd = Math.max(breakEnd, tmBreakEndHour * 60 + tmBreakEndMin);
|
|
229
|
+
}
|
|
246
230
|
}
|
|
247
231
|
}
|
|
248
|
-
//
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
232
|
+
// Generate slots
|
|
233
|
+
const slots = [];
|
|
234
|
+
const slotInterval = openingTimes?.slotDuration || 30;
|
|
235
|
+
for(let minutes = effectiveOpenMinutes; minutes + totalSlotTime <= effectiveCloseMinutes; minutes += slotInterval){
|
|
236
|
+
const slotEnd = minutes + totalSlotTime;
|
|
237
|
+
// Check if slot is during break
|
|
238
|
+
if (breakStart !== null && breakEnd !== null) {
|
|
239
|
+
if (minutes < breakEnd && slotEnd > breakStart) {
|
|
240
|
+
continue; // Skip slots that overlap with break
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
// Check if slot conflicts with existing appointments
|
|
244
|
+
let isBlocked = false;
|
|
245
|
+
for (const blocked of blockedRanges){
|
|
246
|
+
if (minutes < blocked.end && slotEnd > blocked.start) {
|
|
247
|
+
isBlocked = true;
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
// Create slot datetime
|
|
252
|
+
const slotStart = new Date(requestedDate);
|
|
253
|
+
slotStart.setHours(Math.floor(minutes / 60), minutes % 60, 0, 0);
|
|
254
|
+
const slotEndDate = new Date(requestedDate);
|
|
255
|
+
slotEndDate.setHours(Math.floor(slotEnd / 60), slotEnd % 60, 0, 0);
|
|
256
|
+
// Check if slot is in the past
|
|
257
|
+
if (slotStart < now) {
|
|
252
258
|
isBlocked = true;
|
|
253
|
-
break;
|
|
254
259
|
}
|
|
260
|
+
slots.push({
|
|
261
|
+
available: !isBlocked,
|
|
262
|
+
end: slotEndDate.toISOString(),
|
|
263
|
+
start: slotStart.toISOString()
|
|
264
|
+
});
|
|
255
265
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
266
|
+
const response = {
|
|
267
|
+
date: dateParam,
|
|
268
|
+
serviceId,
|
|
269
|
+
slots,
|
|
270
|
+
teamMemberId: teamMemberId || undefined
|
|
271
|
+
};
|
|
272
|
+
return Response.json(response);
|
|
273
|
+
} catch (error) {
|
|
274
|
+
req.payload.logger.error({
|
|
275
|
+
error,
|
|
276
|
+
msg: 'Error getting available slots'
|
|
277
|
+
});
|
|
278
|
+
return Response.json({
|
|
279
|
+
error: 'Internal server error'
|
|
280
|
+
}, {
|
|
281
|
+
status: 500
|
|
269
282
|
});
|
|
270
283
|
}
|
|
271
|
-
|
|
272
|
-
date: dateParam,
|
|
273
|
-
serviceId,
|
|
274
|
-
slots,
|
|
275
|
-
teamMemberId: teamMemberId || undefined
|
|
276
|
-
};
|
|
277
|
-
return Response.json(response);
|
|
278
|
-
} catch (error) {
|
|
279
|
-
req.payload.logger.error({
|
|
280
|
-
error,
|
|
281
|
-
msg: 'Error getting available slots'
|
|
282
|
-
});
|
|
283
|
-
return Response.json({
|
|
284
|
-
error: 'Internal server error'
|
|
285
|
-
}, {
|
|
286
|
-
status: 500
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
};
|
|
284
|
+
};
|
|
290
285
|
|
|
291
286
|
//# sourceMappingURL=getAvailableSlots.js.map
|