@venulog/phasing-engine-schemas 0.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 +67 -0
- package/dist/auth.d.ts +114 -0
- package/dist/auth.js +109 -0
- package/dist/common.d.ts +18 -0
- package/dist/common.js +45 -0
- package/dist/company.d.ts +45 -0
- package/dist/company.js +120 -0
- package/dist/enums/bookingStatus.d.ts +6 -0
- package/dist/enums/bookingStatus.js +7 -0
- package/dist/enums/slotStatus.d.ts +5 -0
- package/dist/enums/slotStatus.js +6 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +12 -0
- package/dist/pagination.d.ts +13 -0
- package/dist/pagination.js +11 -0
- package/dist/phaseBooking.d.ts +701 -0
- package/dist/phaseBooking.js +890 -0
- package/dist/phaseSlot.d.ts +82 -0
- package/dist/phaseSlot.js +74 -0
- package/dist/zod.d.ts +2 -0
- package/dist/zod.js +4 -0
- package/package.json +67 -0
|
@@ -0,0 +1,890 @@
|
|
|
1
|
+
// packages/phasing-schemas/src/phaseBooking.ts
|
|
2
|
+
import { paginationSchema } from './pagination.js';
|
|
3
|
+
import { BookingStatus } from './enums/bookingStatus.js';
|
|
4
|
+
import { createSuccessResponseSchema, createMessageDataResponseSchema } from './common.js';
|
|
5
|
+
import { z } from './zod.js';
|
|
6
|
+
import { SlotStatus } from './enums/slotStatus.js';
|
|
7
|
+
// ------------------------------
|
|
8
|
+
// Query parameters schema
|
|
9
|
+
// ------------------------------
|
|
10
|
+
export const getEventBookingsQuerySchema = z
|
|
11
|
+
.object({
|
|
12
|
+
event_code: z.string().min(1, 'Event code is required').openapi({
|
|
13
|
+
description: 'Unique code identifying the event',
|
|
14
|
+
example: 'COEC2025'
|
|
15
|
+
}),
|
|
16
|
+
seller_id: z
|
|
17
|
+
.string()
|
|
18
|
+
.min(1, 'Seller ID is required')
|
|
19
|
+
.transform(val => parseInt(val, 10))
|
|
20
|
+
.refine(val => !isNaN(val) && val > 0, { message: 'Seller ID must be positive' })
|
|
21
|
+
.openapi({
|
|
22
|
+
description: 'ID of the seller (must be a positive integer)',
|
|
23
|
+
example: '1'
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
.extend(paginationSchema.shape)
|
|
27
|
+
.openapi('GetEventBookingsQuery');
|
|
28
|
+
// ------------------------------
|
|
29
|
+
// Path parameters schema
|
|
30
|
+
// ------------------------------
|
|
31
|
+
export const cancelPhaseSlotParamsSchema = z
|
|
32
|
+
.object({
|
|
33
|
+
slotId: z
|
|
34
|
+
.string()
|
|
35
|
+
.min(1, 'Slot ID is required')
|
|
36
|
+
.transform(val => parseInt(val, 10))
|
|
37
|
+
.refine(val => !isNaN(val) && val > 0, { message: 'Slot ID must be positive' })
|
|
38
|
+
.openapi({
|
|
39
|
+
description: 'The ID of the phase slot to cancel',
|
|
40
|
+
example: '1'
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
.openapi('CancelPhaseSlotParams');
|
|
44
|
+
export const closeEventParamsSchema = z
|
|
45
|
+
.object({
|
|
46
|
+
eventId: z
|
|
47
|
+
.string()
|
|
48
|
+
.min(1, 'Event ID is required')
|
|
49
|
+
.transform(val => parseInt(val, 10))
|
|
50
|
+
.refine(val => !isNaN(val) && val > 0, { message: 'Event ID must be positive' })
|
|
51
|
+
.openapi({
|
|
52
|
+
description: 'The ID of the event to close',
|
|
53
|
+
example: '1'
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
.openapi('CloseEventParams');
|
|
57
|
+
// ------------------------------
|
|
58
|
+
// Request body schemas
|
|
59
|
+
// ------------------------------
|
|
60
|
+
export const cancelPhaseSlotBodySchema = z
|
|
61
|
+
.object({
|
|
62
|
+
reason: z.string().optional().openapi({
|
|
63
|
+
description: 'Optional reason for cancellation (for audit purposes)',
|
|
64
|
+
example: 'Event postponed'
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
.openapi('CancelPhaseSlotBody');
|
|
68
|
+
export const closeEventBodySchema = z
|
|
69
|
+
.object({
|
|
70
|
+
reason: z.string().optional().openapi({
|
|
71
|
+
description: 'Optional reason for closing the event phase (for audit purposes)',
|
|
72
|
+
example: 'Event phase completed - moving to next stage'
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
.openapi('CloseEventBody');
|
|
76
|
+
// ------------------------------
|
|
77
|
+
// Response schemas
|
|
78
|
+
// ------------------------------
|
|
79
|
+
export const vehicleTypeSchema = z.object({
|
|
80
|
+
id: z.number(),
|
|
81
|
+
name: z.string()
|
|
82
|
+
});
|
|
83
|
+
export const phaseSlotSchema = z.object({
|
|
84
|
+
id: z.number(),
|
|
85
|
+
event_id: z.number(),
|
|
86
|
+
slot_number: z.number(),
|
|
87
|
+
vehicle_type_id: z.number().nullable(),
|
|
88
|
+
company_role: z.string().nullable(),
|
|
89
|
+
status: z.enum(BookingStatus),
|
|
90
|
+
is_active: z.boolean(),
|
|
91
|
+
created_at: z.string(),
|
|
92
|
+
updated_at: z.string(),
|
|
93
|
+
created_by: z.string().nullable(),
|
|
94
|
+
updated_by: z.string().nullable(),
|
|
95
|
+
vehicle_type: vehicleTypeSchema.nullable().optional()
|
|
96
|
+
});
|
|
97
|
+
export const companySchema = z.object({
|
|
98
|
+
id: z.number(),
|
|
99
|
+
company_name: z.string(),
|
|
100
|
+
vat_number: z.string().nullable(),
|
|
101
|
+
siret_code: z.string().nullable(),
|
|
102
|
+
tva_intracom: z.string().nullable(),
|
|
103
|
+
type: z.enum(['E', 'S']),
|
|
104
|
+
company_role: z.string().nullable(),
|
|
105
|
+
company_street: z.string().nullable(),
|
|
106
|
+
company_city: z.string().nullable(),
|
|
107
|
+
company_postal_code: z.string().nullable(),
|
|
108
|
+
company_country: z.string().nullable(),
|
|
109
|
+
company_address: z.string().nullable(),
|
|
110
|
+
contact_first_name: z.string().nullable(),
|
|
111
|
+
contact_last_name: z.string().nullable(),
|
|
112
|
+
contact_email: z.string().nullable(),
|
|
113
|
+
contact_phone: z.string().nullable(),
|
|
114
|
+
is_active: z.boolean(),
|
|
115
|
+
created_at: z.string(),
|
|
116
|
+
updated_at: z.string(),
|
|
117
|
+
created_by: z.string().nullable(),
|
|
118
|
+
updated_by: z.string().nullable()
|
|
119
|
+
});
|
|
120
|
+
export const phaseBookingSchema = z.object({
|
|
121
|
+
id: z.number(),
|
|
122
|
+
company_id: z.number(),
|
|
123
|
+
phase_slot_id: z.number(),
|
|
124
|
+
status: z.enum(BookingStatus),
|
|
125
|
+
is_active: z.boolean(),
|
|
126
|
+
created_at: z.string(),
|
|
127
|
+
updated_at: z.string(),
|
|
128
|
+
created_by: z.string().nullable(),
|
|
129
|
+
updated_by: z.string().nullable(),
|
|
130
|
+
company: companySchema,
|
|
131
|
+
phase_slot: phaseSlotSchema
|
|
132
|
+
});
|
|
133
|
+
export const createBookingBodySchema = z
|
|
134
|
+
.object({
|
|
135
|
+
company_id: z
|
|
136
|
+
.number()
|
|
137
|
+
.int()
|
|
138
|
+
.positive({
|
|
139
|
+
message: 'Company ID must be a positive integer'
|
|
140
|
+
})
|
|
141
|
+
.openapi({
|
|
142
|
+
description: 'ID of the company making the booking',
|
|
143
|
+
example: 123
|
|
144
|
+
}),
|
|
145
|
+
phase_slot_id: z
|
|
146
|
+
.number()
|
|
147
|
+
.int()
|
|
148
|
+
.positive({
|
|
149
|
+
message: 'Phase slot ID must be a positive integer'
|
|
150
|
+
})
|
|
151
|
+
.openapi({
|
|
152
|
+
description: 'ID of the phase slot to book',
|
|
153
|
+
example: 456
|
|
154
|
+
})
|
|
155
|
+
})
|
|
156
|
+
.openapi('CreateBookingBody');
|
|
157
|
+
export const createBookingDataSchema = z
|
|
158
|
+
.object({
|
|
159
|
+
booking_id: z.number().openapi({
|
|
160
|
+
description: 'ID of the created booking',
|
|
161
|
+
example: 789
|
|
162
|
+
}),
|
|
163
|
+
company_id: z.number().openapi({
|
|
164
|
+
description: 'ID of the company',
|
|
165
|
+
example: 123
|
|
166
|
+
}),
|
|
167
|
+
phase_slot_id: z.number().openapi({
|
|
168
|
+
description: 'ID of the booked phase slot',
|
|
169
|
+
example: 456
|
|
170
|
+
}),
|
|
171
|
+
slot_number: z.number().openapi({
|
|
172
|
+
description: 'Slot number',
|
|
173
|
+
example: 42
|
|
174
|
+
}),
|
|
175
|
+
status: z.enum(BookingStatus).openapi({
|
|
176
|
+
description: 'Booking status',
|
|
177
|
+
example: BookingStatus.BOOKED
|
|
178
|
+
}),
|
|
179
|
+
created_at: z.string().openapi({
|
|
180
|
+
description: 'Timestamp when booking was created',
|
|
181
|
+
example: '2025-12-05T10:30:00.000Z'
|
|
182
|
+
}),
|
|
183
|
+
created_by: z.string().nullable().openapi({
|
|
184
|
+
description: 'ID of the user who created the booking',
|
|
185
|
+
example: 'user-123'
|
|
186
|
+
})
|
|
187
|
+
})
|
|
188
|
+
.openapi('CreateBookingData');
|
|
189
|
+
export const createBookingResponseSchema = createMessageDataResponseSchema(createBookingDataSchema, 'CreateBookingResponse', 'Booking created successfully', 'Details of the created booking');
|
|
190
|
+
export const eventBookingsDataSchema = z
|
|
191
|
+
.object({
|
|
192
|
+
event_id: z.number(),
|
|
193
|
+
event_code: z.string(),
|
|
194
|
+
event_name: z.string(),
|
|
195
|
+
bookings: z.array(phaseBookingSchema),
|
|
196
|
+
total_count: z.number()
|
|
197
|
+
})
|
|
198
|
+
.openapi('EventBookingsData');
|
|
199
|
+
export const eventBookingsResponseSchema = createSuccessResponseSchema(eventBookingsDataSchema, 'EventBookingsResponse', 'Event bookings data with phase slot details');
|
|
200
|
+
export const cancelPhaseSlotDataSchema = z
|
|
201
|
+
.object({
|
|
202
|
+
slot_id: z.number().openapi({
|
|
203
|
+
description: 'ID of the cancelled slot',
|
|
204
|
+
example: 1
|
|
205
|
+
}),
|
|
206
|
+
status: z.enum(SlotStatus).openapi({
|
|
207
|
+
description: 'New status of the slot',
|
|
208
|
+
example: SlotStatus.AVAILABLE
|
|
209
|
+
}),
|
|
210
|
+
cancelled_at: z.string().openapi({
|
|
211
|
+
description: 'Timestamp when the slot was cancelled',
|
|
212
|
+
example: '2025-12-03T20:47:00.000Z'
|
|
213
|
+
}),
|
|
214
|
+
cancelled_by: z.string().nullable().openapi({
|
|
215
|
+
description: 'ID of the user who cancelled the slot',
|
|
216
|
+
example: null
|
|
217
|
+
}),
|
|
218
|
+
reason: z.string().nullable().openapi({
|
|
219
|
+
description: 'Reason for cancellation',
|
|
220
|
+
example: 'Event postponed'
|
|
221
|
+
})
|
|
222
|
+
})
|
|
223
|
+
.openapi('CancelPhaseSlotData');
|
|
224
|
+
export const cancelPhaseSlotResponseSchema = createMessageDataResponseSchema(cancelPhaseSlotDataSchema, 'CancelPhaseSlotResponse', 'Phase slot cancelled successfully', 'Details of the cancelled phase slot');
|
|
225
|
+
export const closeEventDataSchema = z
|
|
226
|
+
.object({
|
|
227
|
+
event_id: z.number().openapi({
|
|
228
|
+
description: 'ID of the close event',
|
|
229
|
+
example: 1
|
|
230
|
+
}),
|
|
231
|
+
event_code: z.string().nullable().openapi({
|
|
232
|
+
description: 'Code of the close event',
|
|
233
|
+
example: 'EVENT2025'
|
|
234
|
+
}),
|
|
235
|
+
event_name: z.string().openapi({
|
|
236
|
+
description: 'Name of the close event',
|
|
237
|
+
example: 'Annual Trade Show 2025'
|
|
238
|
+
}),
|
|
239
|
+
is_active: z.boolean().openapi({
|
|
240
|
+
description: 'Active status of the event',
|
|
241
|
+
example: false
|
|
242
|
+
}),
|
|
243
|
+
close_at: z.string().openapi({
|
|
244
|
+
description: 'Timestamp when the event was closed',
|
|
245
|
+
example: '2025-12-04T10:30:00.000Z'
|
|
246
|
+
}),
|
|
247
|
+
close_by: z.string().nullable().openapi({
|
|
248
|
+
description: 'ID of the user who closed the event',
|
|
249
|
+
example: null
|
|
250
|
+
}),
|
|
251
|
+
reason: z.string().nullable().openapi({
|
|
252
|
+
description: 'Reason for closing the event',
|
|
253
|
+
example: 'Event cancelled due to logistical issues'
|
|
254
|
+
})
|
|
255
|
+
})
|
|
256
|
+
.openapi('CloseEventData');
|
|
257
|
+
export const closeEventResponseSchema = createMessageDataResponseSchema(closeEventDataSchema, 'CloseEventResponse', 'Event phase closed successfully', 'Details of the closed event phase');
|
|
258
|
+
export const confirmBookingParamsSchema = z.object({
|
|
259
|
+
bookingId: z.coerce.number().int().positive({
|
|
260
|
+
message: 'Booking ID must be a positive integer'
|
|
261
|
+
})
|
|
262
|
+
});
|
|
263
|
+
// Response schema
|
|
264
|
+
export const confirmBookingResponseSchema = z.object({
|
|
265
|
+
success: z.boolean(),
|
|
266
|
+
message: z.string(),
|
|
267
|
+
data: z.object({
|
|
268
|
+
booking_id: z.number(),
|
|
269
|
+
booking_status: z.enum(BookingStatus),
|
|
270
|
+
slot_id: z.number(),
|
|
271
|
+
slot_status: z.enum(SlotStatus),
|
|
272
|
+
confirmed_at: z.string(),
|
|
273
|
+
confirmed_by: z.string().nullable()
|
|
274
|
+
})
|
|
275
|
+
});
|
|
276
|
+
export const refuseBookingParamsSchema = z
|
|
277
|
+
.object({
|
|
278
|
+
bookingId: z.coerce
|
|
279
|
+
.number()
|
|
280
|
+
.int()
|
|
281
|
+
.positive({
|
|
282
|
+
message: 'Booking ID must be a positive integer'
|
|
283
|
+
})
|
|
284
|
+
.openapi({
|
|
285
|
+
description: 'The ID of the booking to refuse',
|
|
286
|
+
example: 1
|
|
287
|
+
})
|
|
288
|
+
})
|
|
289
|
+
.openapi('RefuseBookingParams');
|
|
290
|
+
// Response data schema for refuse endpoint
|
|
291
|
+
export const refuseBookingDataSchema = z
|
|
292
|
+
.object({
|
|
293
|
+
booking_id: z.number().openapi({
|
|
294
|
+
description: 'ID of the refused booking',
|
|
295
|
+
example: 1
|
|
296
|
+
}),
|
|
297
|
+
booking_status: z.enum([BookingStatus.REFUSED]).openapi({
|
|
298
|
+
description: 'New status of the booking',
|
|
299
|
+
example: BookingStatus.REFUSED
|
|
300
|
+
}),
|
|
301
|
+
slot_id: z.number().openapi({
|
|
302
|
+
description: 'ID of the associated phase slot',
|
|
303
|
+
example: 10
|
|
304
|
+
}),
|
|
305
|
+
slot_status: z.enum([SlotStatus.AVAILABLE]).openapi({
|
|
306
|
+
description: 'New status of the phase slot',
|
|
307
|
+
example: SlotStatus.AVAILABLE
|
|
308
|
+
}),
|
|
309
|
+
refused_at: z.string().openapi({
|
|
310
|
+
description: 'ISO 8601 timestamp when the booking was refused',
|
|
311
|
+
example: '2025-12-08T10:30:00.000Z'
|
|
312
|
+
}),
|
|
313
|
+
refused_by: z.string().nullable().openapi({
|
|
314
|
+
description: 'ID of the user who refused the booking',
|
|
315
|
+
example: 'user-123'
|
|
316
|
+
})
|
|
317
|
+
})
|
|
318
|
+
.openapi('RefuseBookingData');
|
|
319
|
+
// Response schema for refuse endpoint
|
|
320
|
+
export const refuseBookingResponseSchema = createMessageDataResponseSchema(refuseBookingDataSchema, 'RefuseBookingResponse', 'Phase booking refused successfully', 'Details of the refused booking');
|
|
321
|
+
// ------------------------------
|
|
322
|
+
// Create Phase Slots schemas
|
|
323
|
+
// ------------------------------
|
|
324
|
+
export const createPhaseSlotsBodySchema = z
|
|
325
|
+
.object({
|
|
326
|
+
event_id: z
|
|
327
|
+
.number()
|
|
328
|
+
.int()
|
|
329
|
+
.positive({
|
|
330
|
+
message: 'Event ID must be a positive integer'
|
|
331
|
+
})
|
|
332
|
+
.openapi({
|
|
333
|
+
description: 'ID of the event to create the slots for',
|
|
334
|
+
example: 1
|
|
335
|
+
}),
|
|
336
|
+
slots: z
|
|
337
|
+
.array(z.object({
|
|
338
|
+
slot_number: z
|
|
339
|
+
.number()
|
|
340
|
+
.int()
|
|
341
|
+
.positive({
|
|
342
|
+
message: 'Slot number must be a positive integer'
|
|
343
|
+
})
|
|
344
|
+
.openapi({
|
|
345
|
+
description: 'Unique slot number within the event',
|
|
346
|
+
example: 42
|
|
347
|
+
}),
|
|
348
|
+
vehicle_type_id: z
|
|
349
|
+
.number()
|
|
350
|
+
.int()
|
|
351
|
+
.positive({
|
|
352
|
+
message: 'Vehicle type ID must be a positive integer'
|
|
353
|
+
})
|
|
354
|
+
.nullable()
|
|
355
|
+
.optional()
|
|
356
|
+
.openapi({
|
|
357
|
+
description: 'Optional vehicle type ID for the slot',
|
|
358
|
+
example: 1
|
|
359
|
+
}),
|
|
360
|
+
company_role: z.string().min(1).nullable().optional().openapi({
|
|
361
|
+
description: 'Optional company role for the slot',
|
|
362
|
+
example: 'buyer'
|
|
363
|
+
}),
|
|
364
|
+
status: z.enum(SlotStatus).optional().openapi({
|
|
365
|
+
description: 'Initial status of the slot (defaults to available)',
|
|
366
|
+
example: SlotStatus.AVAILABLE
|
|
367
|
+
})
|
|
368
|
+
}))
|
|
369
|
+
.min(1, 'At least one slot must be provided')
|
|
370
|
+
.openapi({
|
|
371
|
+
description: 'Array of slots to create',
|
|
372
|
+
example: [
|
|
373
|
+
{ slot_number: 42, vehicle_type_id: 1, company_role: 'buyer' },
|
|
374
|
+
{ slot_number: 43, vehicle_type_id: 2, company_role: 'seller' }
|
|
375
|
+
]
|
|
376
|
+
})
|
|
377
|
+
})
|
|
378
|
+
.openapi('CreatePhaseSlotsBody');
|
|
379
|
+
export const createPhaseSlotDataSchema = z
|
|
380
|
+
.object({
|
|
381
|
+
slot_id: z.number().openapi({
|
|
382
|
+
description: 'ID of the created phase slot',
|
|
383
|
+
example: 123
|
|
384
|
+
}),
|
|
385
|
+
event_id: z.number().openapi({
|
|
386
|
+
description: 'ID of the event',
|
|
387
|
+
example: 1
|
|
388
|
+
}),
|
|
389
|
+
slot_number: z.number().openapi({
|
|
390
|
+
description: 'Slot number',
|
|
391
|
+
example: 42
|
|
392
|
+
}),
|
|
393
|
+
vehicle_type_id: z.number().nullable().openapi({
|
|
394
|
+
description: 'Vehicle type ID',
|
|
395
|
+
example: 1
|
|
396
|
+
}),
|
|
397
|
+
company_role: z.string().nullable().openapi({
|
|
398
|
+
description: 'Company role',
|
|
399
|
+
example: 'buyer'
|
|
400
|
+
}),
|
|
401
|
+
status: z.enum(SlotStatus).openapi({
|
|
402
|
+
description: 'Slot status',
|
|
403
|
+
example: SlotStatus.AVAILABLE
|
|
404
|
+
}),
|
|
405
|
+
is_active: z.boolean().openapi({
|
|
406
|
+
description: 'Whether the slot is active',
|
|
407
|
+
example: true,
|
|
408
|
+
examples: [true, false]
|
|
409
|
+
}),
|
|
410
|
+
created_at: z.string().openapi({
|
|
411
|
+
description: 'Timestamp when slot was created',
|
|
412
|
+
example: '2025-12-09T10:30:00.000Z'
|
|
413
|
+
}),
|
|
414
|
+
created_by: z.string().nullable().openapi({
|
|
415
|
+
description: 'ID of the user who created the slot',
|
|
416
|
+
example: 'user-123'
|
|
417
|
+
})
|
|
418
|
+
})
|
|
419
|
+
.openapi('CreatePhaseSlotData');
|
|
420
|
+
export const createPhaseSlotsDataSchema = z
|
|
421
|
+
.object({
|
|
422
|
+
event_id: z.number().openapi({
|
|
423
|
+
description: 'ID of the event',
|
|
424
|
+
example: 1
|
|
425
|
+
}),
|
|
426
|
+
total_created: z.number().openapi({
|
|
427
|
+
description: 'Total number of slots successfully created',
|
|
428
|
+
example: 2
|
|
429
|
+
}),
|
|
430
|
+
created_slots: z.array(createPhaseSlotDataSchema).openapi({
|
|
431
|
+
description: 'Array of successfully created slots',
|
|
432
|
+
example: []
|
|
433
|
+
}),
|
|
434
|
+
failed_slots: z
|
|
435
|
+
.array(z.object({
|
|
436
|
+
slot_number: z.number().openapi({
|
|
437
|
+
description: 'Slot number that failed to create',
|
|
438
|
+
example: 44
|
|
439
|
+
}),
|
|
440
|
+
error: z.string().openapi({
|
|
441
|
+
description: 'Error message explaining why the slot creation failed',
|
|
442
|
+
example: 'Slot number 44 already exists for event 1'
|
|
443
|
+
})
|
|
444
|
+
}))
|
|
445
|
+
.openapi({
|
|
446
|
+
description: 'Array of slots that failed to create with error details',
|
|
447
|
+
example: []
|
|
448
|
+
})
|
|
449
|
+
})
|
|
450
|
+
.openapi('CreatePhaseSlotsData');
|
|
451
|
+
export const createPhaseSlotsResponseSchema = createMessageDataResponseSchema(createPhaseSlotsDataSchema, 'CreatePhaseSlotsResponse', 'Bulk phase slots operation completed', 'Details of the bulk creation operation including successes and failures');
|
|
452
|
+
// ------------------------------
|
|
453
|
+
// Update Phase Slot schemas
|
|
454
|
+
// ------------------------------
|
|
455
|
+
export const updatePhaseSlotParamsSchema = z
|
|
456
|
+
.object({
|
|
457
|
+
slotId: z
|
|
458
|
+
.string()
|
|
459
|
+
.min(1, 'Slot ID is required')
|
|
460
|
+
.transform(val => parseInt(val, 10))
|
|
461
|
+
.refine(val => !isNaN(val) && val > 0, { message: 'Slot ID must be positive' })
|
|
462
|
+
.openapi({
|
|
463
|
+
description: 'The ID of the phase slot to update',
|
|
464
|
+
example: '123'
|
|
465
|
+
})
|
|
466
|
+
})
|
|
467
|
+
.openapi('UpdatePhaseSlotParams');
|
|
468
|
+
export const updatePhaseSlotBodySchema = z
|
|
469
|
+
.object({
|
|
470
|
+
slot_number: z
|
|
471
|
+
.number()
|
|
472
|
+
.int()
|
|
473
|
+
.positive({
|
|
474
|
+
message: 'Slot number must be a positive integer'
|
|
475
|
+
})
|
|
476
|
+
.optional()
|
|
477
|
+
.openapi({
|
|
478
|
+
description: 'New slot number (must be unique within the event)',
|
|
479
|
+
example: 43
|
|
480
|
+
}),
|
|
481
|
+
vehicle_type_id: z
|
|
482
|
+
.number()
|
|
483
|
+
.int()
|
|
484
|
+
.positive({
|
|
485
|
+
message: 'Vehicle type ID must be a positive integer'
|
|
486
|
+
})
|
|
487
|
+
.nullable()
|
|
488
|
+
.optional()
|
|
489
|
+
.openapi({
|
|
490
|
+
description: 'New vehicle type ID for the slot',
|
|
491
|
+
example: 2
|
|
492
|
+
}),
|
|
493
|
+
company_role: z.string().min(1).nullable().optional().openapi({
|
|
494
|
+
description: 'New company role for the slot',
|
|
495
|
+
example: 'seller'
|
|
496
|
+
}),
|
|
497
|
+
status: z.enum(SlotStatus).optional().openapi({
|
|
498
|
+
description: 'New status for the slot',
|
|
499
|
+
example: SlotStatus.RESERVED
|
|
500
|
+
})
|
|
501
|
+
})
|
|
502
|
+
.refine(data => Object.keys(data).length > 0, {
|
|
503
|
+
message: 'At least one field must be provided for update'
|
|
504
|
+
})
|
|
505
|
+
.openapi('UpdatePhaseSlotBody');
|
|
506
|
+
export const updatePhaseSlotDataSchema = z
|
|
507
|
+
.object({
|
|
508
|
+
slot_id: z.number().openapi({
|
|
509
|
+
description: 'ID of the updated phase slot',
|
|
510
|
+
example: 123
|
|
511
|
+
}),
|
|
512
|
+
event_id: z.number().openapi({
|
|
513
|
+
description: 'ID of the event',
|
|
514
|
+
example: 1
|
|
515
|
+
}),
|
|
516
|
+
slot_number: z.number().openapi({
|
|
517
|
+
description: 'Updated slot number',
|
|
518
|
+
example: 43
|
|
519
|
+
}),
|
|
520
|
+
vehicle_type_id: z.number().nullable().openapi({
|
|
521
|
+
description: 'Updated vehicle type ID',
|
|
522
|
+
example: 2
|
|
523
|
+
}),
|
|
524
|
+
company_role: z.string().nullable().openapi({
|
|
525
|
+
description: 'Updated company role',
|
|
526
|
+
example: 'seller'
|
|
527
|
+
}),
|
|
528
|
+
status: z.enum(SlotStatus).openapi({
|
|
529
|
+
description: 'Updated slot status',
|
|
530
|
+
example: SlotStatus.RESERVED
|
|
531
|
+
}),
|
|
532
|
+
is_active: z.boolean().openapi({
|
|
533
|
+
description: 'Whether the slot is active',
|
|
534
|
+
example: true
|
|
535
|
+
}),
|
|
536
|
+
updated_at: z.string().openapi({
|
|
537
|
+
description: 'Timestamp when slot was updated',
|
|
538
|
+
example: '2025-12-09T10:35:00.000Z'
|
|
539
|
+
}),
|
|
540
|
+
updated_by: z.string().nullable().openapi({
|
|
541
|
+
description: 'ID of the user who updated the slot',
|
|
542
|
+
example: 'user-123'
|
|
543
|
+
})
|
|
544
|
+
})
|
|
545
|
+
.openapi('UpdatePhaseSlotData');
|
|
546
|
+
export const updatePhaseSlotResponseSchema = createMessageDataResponseSchema(updatePhaseSlotDataSchema, 'UpdatePhaseSlotResponse', 'Phase slot updated successfully', 'Details of the updated phase slot');
|
|
547
|
+
// ------------------------------
|
|
548
|
+
// Phase Slot Assemblies & Dismantlings Schemas
|
|
549
|
+
// ------------------------------
|
|
550
|
+
export const phaseSlotAssemblySchema = z
|
|
551
|
+
.object({
|
|
552
|
+
id: z.number().int().positive().openapi({
|
|
553
|
+
description: 'Unique identifier for the assembly',
|
|
554
|
+
example: 1
|
|
555
|
+
}),
|
|
556
|
+
date: z.string().openapi({
|
|
557
|
+
description: 'Date of the assembly (YYYY-MM-DD format)',
|
|
558
|
+
example: '2025-12-15'
|
|
559
|
+
}),
|
|
560
|
+
start_time: z.string().openapi({
|
|
561
|
+
description: 'Start time of the assembly (HH:MM format)',
|
|
562
|
+
example: '06:00'
|
|
563
|
+
}),
|
|
564
|
+
end_time: z.string().openapi({
|
|
565
|
+
description: 'End time of the assembly (HH:MM format)',
|
|
566
|
+
example: '09:00'
|
|
567
|
+
}),
|
|
568
|
+
duration: z.number().openapi({
|
|
569
|
+
description: 'Duration of the assembly in minutes',
|
|
570
|
+
example: 180
|
|
571
|
+
}),
|
|
572
|
+
phase_slot_id: z.number().int().positive().openapi({
|
|
573
|
+
description: 'ID of the associated phase slot',
|
|
574
|
+
example: 101
|
|
575
|
+
}),
|
|
576
|
+
created_at: z.string().openapi({
|
|
577
|
+
description: 'Timestamp when assembly was created',
|
|
578
|
+
example: '2025-12-09T10:00:00.000Z'
|
|
579
|
+
}),
|
|
580
|
+
updated_at: z.string().openapi({
|
|
581
|
+
description: 'Timestamp when assembly was updated',
|
|
582
|
+
example: '2025-12-09T10:30:00.000Z'
|
|
583
|
+
})
|
|
584
|
+
})
|
|
585
|
+
.openapi('PhaseSlotAssembly');
|
|
586
|
+
export const phaseSlotDismantlingSchema = z
|
|
587
|
+
.object({
|
|
588
|
+
id: z.number().int().positive().openapi({
|
|
589
|
+
description: 'Unique identifier for the dismantling',
|
|
590
|
+
example: 1
|
|
591
|
+
}),
|
|
592
|
+
date: z.string().openapi({
|
|
593
|
+
description: 'Date of the dismantling (YYYY-MM-DD format)',
|
|
594
|
+
example: '2025-12-15'
|
|
595
|
+
}),
|
|
596
|
+
start_time: z.string().openapi({
|
|
597
|
+
description: 'Start time of the dismantling (HH:MM format)',
|
|
598
|
+
example: '06:00'
|
|
599
|
+
}),
|
|
600
|
+
end_time: z.string().openapi({
|
|
601
|
+
description: 'End time of the dismantling (HH:MM format)',
|
|
602
|
+
example: '09:00'
|
|
603
|
+
}),
|
|
604
|
+
duration: z.number().openapi({
|
|
605
|
+
description: 'Duration of the dismantling in minutes',
|
|
606
|
+
example: 180
|
|
607
|
+
}),
|
|
608
|
+
phase_slot_id: z.number().int().positive().openapi({
|
|
609
|
+
description: 'ID of the associated phase slot',
|
|
610
|
+
example: 101
|
|
611
|
+
}),
|
|
612
|
+
created_at: z.string().openapi({
|
|
613
|
+
description: 'Timestamp when dismantling was created',
|
|
614
|
+
example: '2025-12-09T10:00:00.000Z'
|
|
615
|
+
}),
|
|
616
|
+
updated_at: z.string().openapi({
|
|
617
|
+
description: 'Timestamp when dismantling was updated',
|
|
618
|
+
example: '2025-12-09T10:30:00.000Z'
|
|
619
|
+
})
|
|
620
|
+
})
|
|
621
|
+
.openapi('PhaseSlotDismantling');
|
|
622
|
+
export const upsertPhaseSlotAssemblySchema = z
|
|
623
|
+
.object({
|
|
624
|
+
date: z
|
|
625
|
+
.string()
|
|
626
|
+
.regex(/^\d{4}-\d{2}-\d{2}$/, 'Date must be in YYYY-MM-DD format')
|
|
627
|
+
.openapi({
|
|
628
|
+
description: 'Date of the assembly (YYYY-MM-DD format)',
|
|
629
|
+
example: '2025-12-15'
|
|
630
|
+
}),
|
|
631
|
+
start_time: z
|
|
632
|
+
.string()
|
|
633
|
+
.regex(/^\d{2}:\d{2}$/, 'Time must be in HH:MM format')
|
|
634
|
+
.optional()
|
|
635
|
+
.openapi({
|
|
636
|
+
description: 'Start time of the assembly (HH:MM format). Defaults to 06:00',
|
|
637
|
+
example: '06:00'
|
|
638
|
+
}),
|
|
639
|
+
end_time: z
|
|
640
|
+
.string()
|
|
641
|
+
.regex(/^\d{2}:\d{2}$/, 'Time must be in HH:MM format')
|
|
642
|
+
.optional()
|
|
643
|
+
.openapi({
|
|
644
|
+
description: 'End time of the assembly (HH:MM format). Defaults to 09:00',
|
|
645
|
+
example: '09:00'
|
|
646
|
+
}),
|
|
647
|
+
duration: z.number().positive().optional().openapi({
|
|
648
|
+
description: 'Duration of the assembly in minutes. Defaults to 60',
|
|
649
|
+
example: 180
|
|
650
|
+
}),
|
|
651
|
+
phase_slot_id: z.number().int().positive().openapi({
|
|
652
|
+
description: 'ID of the associated phase slot',
|
|
653
|
+
example: 101
|
|
654
|
+
})
|
|
655
|
+
})
|
|
656
|
+
.openapi('UpsertPhaseSlotAssembly');
|
|
657
|
+
export const upsertPhaseSlotDismantlingSchema = z
|
|
658
|
+
.object({
|
|
659
|
+
date: z
|
|
660
|
+
.string()
|
|
661
|
+
.regex(/^\d{4}-\d{2}-\d{2}$/, 'Date must be in YYYY-MM-DD format')
|
|
662
|
+
.openapi({
|
|
663
|
+
description: 'Date of the dismantling (YYYY-MM-DD format)',
|
|
664
|
+
example: '2025-12-15'
|
|
665
|
+
}),
|
|
666
|
+
start_time: z
|
|
667
|
+
.string()
|
|
668
|
+
.regex(/^\d{2}:\d{2}$/, 'Time must be in HH:MM format')
|
|
669
|
+
.optional()
|
|
670
|
+
.openapi({
|
|
671
|
+
description: 'Start time of the dismantling (HH:MM format). Defaults to 06:00',
|
|
672
|
+
example: '06:00'
|
|
673
|
+
}),
|
|
674
|
+
end_time: z
|
|
675
|
+
.string()
|
|
676
|
+
.regex(/^\d{2}:\d{2}$/, 'Time must be in HH:MM format')
|
|
677
|
+
.optional()
|
|
678
|
+
.openapi({
|
|
679
|
+
description: 'End time of the dismantling (HH:MM format). Defaults to 09:00',
|
|
680
|
+
example: '09:00'
|
|
681
|
+
}),
|
|
682
|
+
duration: z.number().positive().optional().openapi({
|
|
683
|
+
description: 'Duration of the dismantling in minutes. Defaults to 60',
|
|
684
|
+
example: 180
|
|
685
|
+
}),
|
|
686
|
+
phase_slot_id: z.number().int().positive().openapi({
|
|
687
|
+
description: 'ID of the associated phase slot',
|
|
688
|
+
example: 101
|
|
689
|
+
})
|
|
690
|
+
})
|
|
691
|
+
.openapi('UpsertPhaseSlotDismantling');
|
|
692
|
+
// ------------------------------
|
|
693
|
+
// Combined Phase Slot Schedules Schemas (Assembly + Dismantling)
|
|
694
|
+
// ------------------------------
|
|
695
|
+
export const phaseSlotScheduleOperationSchema = z
|
|
696
|
+
.object({
|
|
697
|
+
id: z.number().int().positive().optional().openapi({
|
|
698
|
+
description: 'Unique identifier for existing records (required for updates and deletes)',
|
|
699
|
+
example: 1
|
|
700
|
+
}),
|
|
701
|
+
start_time: z
|
|
702
|
+
.string()
|
|
703
|
+
.regex(/^\d{2}:\d{2}$/, 'Time must be in HH:MM format')
|
|
704
|
+
.optional()
|
|
705
|
+
.openapi({
|
|
706
|
+
description: 'Start time (HH:MM format)',
|
|
707
|
+
example: '06:00'
|
|
708
|
+
}),
|
|
709
|
+
end_time: z
|
|
710
|
+
.string()
|
|
711
|
+
.regex(/^\d{2}:\d{2}$/, 'Time must be in HH:MM format')
|
|
712
|
+
.optional()
|
|
713
|
+
.openapi({
|
|
714
|
+
description: 'End time (HH:MM format)',
|
|
715
|
+
example: '09:00'
|
|
716
|
+
}),
|
|
717
|
+
duration: z.number().positive().optional().openapi({
|
|
718
|
+
description: 'Duration in minutes',
|
|
719
|
+
example: 180
|
|
720
|
+
}),
|
|
721
|
+
date: z
|
|
722
|
+
.string()
|
|
723
|
+
.regex(/^\d{4}-\d{2}-\d{2}$/, 'Date must be in YYYY-MM-DD format')
|
|
724
|
+
.optional()
|
|
725
|
+
.openapi({
|
|
726
|
+
description: 'Date for assembly (YYYY-MM-DD format)',
|
|
727
|
+
example: '2025-12-15'
|
|
728
|
+
}),
|
|
729
|
+
_delete: z.boolean().optional().openapi({
|
|
730
|
+
description: 'Flag to mark this record for deletion. Requires existing id.',
|
|
731
|
+
example: false
|
|
732
|
+
})
|
|
733
|
+
})
|
|
734
|
+
.check(data => {
|
|
735
|
+
// If delete is true, id is required
|
|
736
|
+
if (data.value._delete === true && !data.value.id) {
|
|
737
|
+
data.issues.push({
|
|
738
|
+
code: 'custom',
|
|
739
|
+
message: 'ID is required when delete is true',
|
|
740
|
+
path: ['id'],
|
|
741
|
+
input: data.value
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
// If delete is false or undefined, date is required
|
|
745
|
+
if ((data.value._delete === false || data.value._delete === undefined) && !data.value.date) {
|
|
746
|
+
data.issues.push({
|
|
747
|
+
code: 'custom',
|
|
748
|
+
message: 'Date is required when delete is false or undefined',
|
|
749
|
+
path: ['date'],
|
|
750
|
+
input: data.value
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
})
|
|
754
|
+
.openapi('PhaseSlotScheduleOperation');
|
|
755
|
+
export const phaseSlotScheduleSchema = z
|
|
756
|
+
.object({
|
|
757
|
+
phase_slot_id: z.number().int().positive().openapi({
|
|
758
|
+
description: 'ID of the associated phase slot',
|
|
759
|
+
example: 101
|
|
760
|
+
}),
|
|
761
|
+
assembly: phaseSlotScheduleOperationSchema.openapi({
|
|
762
|
+
description: 'Assembly operation details with date'
|
|
763
|
+
}),
|
|
764
|
+
dismantling: phaseSlotScheduleOperationSchema.openapi({
|
|
765
|
+
description: 'Dismantling operation details with date'
|
|
766
|
+
})
|
|
767
|
+
})
|
|
768
|
+
.openapi('PhaseSlotSchedule');
|
|
769
|
+
export const upsertPhaseSlotSchedulesBodySchema = z
|
|
770
|
+
.object({
|
|
771
|
+
schedules: z
|
|
772
|
+
.array(phaseSlotScheduleSchema)
|
|
773
|
+
.min(1, 'At least one schedule is required')
|
|
774
|
+
.openapi({
|
|
775
|
+
description: 'Array of phase slot schedules to upsert',
|
|
776
|
+
example: [
|
|
777
|
+
{
|
|
778
|
+
phase_slot_id: 101,
|
|
779
|
+
assembly: {
|
|
780
|
+
date: '2025-12-15',
|
|
781
|
+
start_time: '06:00',
|
|
782
|
+
end_time: '09:00',
|
|
783
|
+
duration: 180
|
|
784
|
+
},
|
|
785
|
+
dismantling: {
|
|
786
|
+
date: '2025-12-16',
|
|
787
|
+
start_time: '15:00',
|
|
788
|
+
end_time: '18:00',
|
|
789
|
+
duration: 180
|
|
790
|
+
}
|
|
791
|
+
},
|
|
792
|
+
{
|
|
793
|
+
phase_slot_id: 102,
|
|
794
|
+
assembly: {
|
|
795
|
+
id: 15,
|
|
796
|
+
_delete: true
|
|
797
|
+
}
|
|
798
|
+
},
|
|
799
|
+
{
|
|
800
|
+
phase_slot_id: 103,
|
|
801
|
+
dismantling: {
|
|
802
|
+
id: 42,
|
|
803
|
+
_delete: true
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
]
|
|
807
|
+
})
|
|
808
|
+
})
|
|
809
|
+
.openapi('UpsertPhaseSlotSchedulesBody');
|
|
810
|
+
export const upsertPhaseSlotSchedulesDataSchema = z
|
|
811
|
+
.object({
|
|
812
|
+
total_processed: z.number().int().nonnegative().openapi({
|
|
813
|
+
description: 'Total number of schedules processed',
|
|
814
|
+
example: 2
|
|
815
|
+
}),
|
|
816
|
+
total_assemblies_upserted: z.number().int().nonnegative().openapi({
|
|
817
|
+
description: 'Total number of assemblies successfully upserted',
|
|
818
|
+
example: 2
|
|
819
|
+
}),
|
|
820
|
+
total_dismantlings_upserted: z.number().int().nonnegative().openapi({
|
|
821
|
+
description: 'Total number of dismantlings successfully upserted',
|
|
822
|
+
example: 1
|
|
823
|
+
}),
|
|
824
|
+
total_assemblies_deleted: z.number().int().nonnegative().openapi({
|
|
825
|
+
description: 'Total number of assemblies successfully deleted',
|
|
826
|
+
example: 0
|
|
827
|
+
}),
|
|
828
|
+
total_dismantlings_deleted: z.number().int().nonnegative().openapi({
|
|
829
|
+
description: 'Total number of dismantlings successfully deleted',
|
|
830
|
+
example: 0
|
|
831
|
+
}),
|
|
832
|
+
upserted_assemblies: z.array(phaseSlotAssemblySchema).openapi({
|
|
833
|
+
description: 'Array of successfully upserted assemblies'
|
|
834
|
+
}),
|
|
835
|
+
upserted_dismantlings: z.array(phaseSlotDismantlingSchema).openapi({
|
|
836
|
+
description: 'Array of successfully upserted dismantlings'
|
|
837
|
+
}),
|
|
838
|
+
deleted_assemblies: z
|
|
839
|
+
.array(z.object({
|
|
840
|
+
id: z.number().int().positive(),
|
|
841
|
+
phase_slot_id: z.number().int().positive()
|
|
842
|
+
}))
|
|
843
|
+
.openapi({
|
|
844
|
+
description: 'Array of successfully deleted assemblies',
|
|
845
|
+
example: []
|
|
846
|
+
}),
|
|
847
|
+
deleted_dismantlings: z
|
|
848
|
+
.array(z.object({
|
|
849
|
+
id: z.number().int().positive(),
|
|
850
|
+
phase_slot_id: z.number().int().positive()
|
|
851
|
+
}))
|
|
852
|
+
.openapi({
|
|
853
|
+
description: 'Array of successfully deleted dismantlings',
|
|
854
|
+
example: []
|
|
855
|
+
}),
|
|
856
|
+
failed_operations: z
|
|
857
|
+
.array(z.object({
|
|
858
|
+
phase_slot_id: z.number().int().positive(),
|
|
859
|
+
operation: z.enum(['assembly', 'dismantling']),
|
|
860
|
+
action: z.enum(['upsert', 'delete']),
|
|
861
|
+
error: z.string(),
|
|
862
|
+
data: z.union([
|
|
863
|
+
z.object({
|
|
864
|
+
id: z.number().int().positive().optional(),
|
|
865
|
+
date: z.string().optional(),
|
|
866
|
+
start_time: z.string().optional(),
|
|
867
|
+
end_time: z.string().optional(),
|
|
868
|
+
duration: z.number().positive().optional(),
|
|
869
|
+
_delete: z.boolean().optional()
|
|
870
|
+
}),
|
|
871
|
+
z.object({
|
|
872
|
+
id: z.number().int().positive()
|
|
873
|
+
})
|
|
874
|
+
])
|
|
875
|
+
}))
|
|
876
|
+
.openapi({
|
|
877
|
+
description: 'Array of operations that failed to process',
|
|
878
|
+
example: [
|
|
879
|
+
{
|
|
880
|
+
phase_slot_id: 999,
|
|
881
|
+
operation: 'assembly',
|
|
882
|
+
action: 'upsert',
|
|
883
|
+
error: 'Phase slot 999 not found or inactive',
|
|
884
|
+
data: { date: '2025-12-15', start_time: '06:00' }
|
|
885
|
+
}
|
|
886
|
+
]
|
|
887
|
+
})
|
|
888
|
+
})
|
|
889
|
+
.openapi('UpsertPhaseSlotSchedulesData');
|
|
890
|
+
export const upsertPhaseSlotSchedulesResponseSchema = createMessageDataResponseSchema(upsertPhaseSlotSchedulesDataSchema, 'UpsertPhaseSlotSchedulesResponse', 'Bulk phase slot schedules upsert completed', 'Results of the combined upsert operation');
|