@venulog/phasing-engine-schemas 0.3.0-alpha.0 → 0.4.0-alpha.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/common.d.ts +9 -0
- package/dist/common.js +29 -0
- package/dist/enums/index.d.ts +3 -0
- package/dist/enums/index.js +4 -0
- package/dist/enums/parkingAreaScheduleType.d.ts +4 -0
- package/dist/enums/parkingAreaScheduleType.js +5 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/parkingArea.d.ts +591 -0
- package/dist/parkingArea.js +796 -0
- package/dist/phaseBooking.d.ts +504 -0
- package/dist/phaseBooking.js +431 -1
- package/dist/phaseSlot.d.ts +2 -0
- package/dist/phaseSlot.js +15 -0
- package/package.json +9 -1
|
@@ -0,0 +1,796 @@
|
|
|
1
|
+
// packages/phasing-schemas/src/parkingArea.ts
|
|
2
|
+
import { z } from './zod.js';
|
|
3
|
+
import { createSuccessResponseSchema, createMessageDataResponseSchema } from './common.js';
|
|
4
|
+
import { ParkingAreaScheduleType } from './enums/parkingAreaScheduleType.js';
|
|
5
|
+
// ------------------------------
|
|
6
|
+
// Geometry Schemas
|
|
7
|
+
// ------------------------------
|
|
8
|
+
/**
|
|
9
|
+
* A point in 2D space using percentage-based coordinates (0-100)
|
|
10
|
+
* relative to the site plan image dimensions
|
|
11
|
+
*/
|
|
12
|
+
export const pointSchema = z
|
|
13
|
+
.object({
|
|
14
|
+
x: z.number().min(0).max(100).openapi({
|
|
15
|
+
description: 'X coordinate as percentage of image width (0-100)',
|
|
16
|
+
example: 25.5
|
|
17
|
+
}),
|
|
18
|
+
y: z.number().min(0).max(100).openapi({
|
|
19
|
+
description: 'Y coordinate as percentage of image height (0-100)',
|
|
20
|
+
example: 30.2
|
|
21
|
+
})
|
|
22
|
+
})
|
|
23
|
+
.openapi('Point');
|
|
24
|
+
/**
|
|
25
|
+
* A polygon defined by 4 vertices (for rotated rectangles)
|
|
26
|
+
* Points should be in clockwise or counter-clockwise order
|
|
27
|
+
*/
|
|
28
|
+
export const polygonCoordinatesSchema = z
|
|
29
|
+
.array(pointSchema)
|
|
30
|
+
.min(3, 'Polygon must have at least 3 points')
|
|
31
|
+
.max(100, 'Polygon cannot have more than 100 points')
|
|
32
|
+
.openapi('PolygonCoordinates');
|
|
33
|
+
/**
|
|
34
|
+
* Rectangle geometry for parking areas
|
|
35
|
+
* Includes center, dimensions, and rotation angle
|
|
36
|
+
*/
|
|
37
|
+
export const rectangleGeometrySchema = z
|
|
38
|
+
.object({
|
|
39
|
+
center: pointSchema.openapi({
|
|
40
|
+
description: 'Center point of the rectangle'
|
|
41
|
+
}),
|
|
42
|
+
width: z.number().positive().openapi({
|
|
43
|
+
description: 'Width of the rectangle as percentage of image width',
|
|
44
|
+
example: 15.0
|
|
45
|
+
}),
|
|
46
|
+
height: z.number().positive().openapi({
|
|
47
|
+
description: 'Height of the rectangle as percentage of image height',
|
|
48
|
+
example: 10.0
|
|
49
|
+
}),
|
|
50
|
+
angle: z.number().min(-180).max(180).default(0).openapi({
|
|
51
|
+
description: 'Rotation angle in degrees (-180 to 180)',
|
|
52
|
+
example: 45
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
.openapi('RectangleGeometry');
|
|
56
|
+
/**
|
|
57
|
+
* GeoJSON-compatible geometry for PostGIS storage
|
|
58
|
+
* Uses percentage-based coordinates that can be converted to actual coordinates
|
|
59
|
+
*/
|
|
60
|
+
export const geoJsonPolygonSchema = z
|
|
61
|
+
.object({
|
|
62
|
+
type: z.literal('Polygon').openapi({
|
|
63
|
+
description: 'GeoJSON geometry type',
|
|
64
|
+
example: 'Polygon'
|
|
65
|
+
}),
|
|
66
|
+
coordinates: z
|
|
67
|
+
.array(z.array(z.tuple([z.number(), z.number()])))
|
|
68
|
+
.openapi({
|
|
69
|
+
description: 'GeoJSON polygon coordinates (array of rings, first ring is outer boundary)',
|
|
70
|
+
example: [[[0, 0], [100, 0], [100, 100], [0, 100], [0, 0]]]
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
.openapi('GeoJsonPolygon');
|
|
74
|
+
// ------------------------------
|
|
75
|
+
// Parking Area Schemas
|
|
76
|
+
// ------------------------------
|
|
77
|
+
/**
|
|
78
|
+
* Core parking area item schema matching database structure
|
|
79
|
+
*/
|
|
80
|
+
export const parkingAreaItemSchema = z
|
|
81
|
+
.object({
|
|
82
|
+
id: z.number().int().positive().openapi({
|
|
83
|
+
description: 'Parking area ID',
|
|
84
|
+
example: 1
|
|
85
|
+
}),
|
|
86
|
+
event_id: z.number().int().positive().openapi({
|
|
87
|
+
description: 'Event ID this parking area belongs to',
|
|
88
|
+
example: 1
|
|
89
|
+
}),
|
|
90
|
+
name: z.string().nullable().openapi({
|
|
91
|
+
description: 'Name of the parking area',
|
|
92
|
+
example: 'Zone A - Main Entrance'
|
|
93
|
+
}),
|
|
94
|
+
geometry: geoJsonPolygonSchema.nullable().openapi({
|
|
95
|
+
description: 'GeoJSON polygon geometry for the parking area'
|
|
96
|
+
}),
|
|
97
|
+
site_plan_image_url: z.string().url().nullable().openapi({
|
|
98
|
+
description: 'URL to the site plan image',
|
|
99
|
+
example: 'https://storage.example.com/site-plans/event-123/floor-1.png'
|
|
100
|
+
}),
|
|
101
|
+
surface_area_sqm: z.number().nonnegative().nullable().openapi({
|
|
102
|
+
description: 'Surface area in square meters',
|
|
103
|
+
example: 150.5
|
|
104
|
+
}),
|
|
105
|
+
is_active: z.boolean().openapi({
|
|
106
|
+
description: 'Whether the parking area is active',
|
|
107
|
+
example: true
|
|
108
|
+
}),
|
|
109
|
+
created_at: z.string().openapi({
|
|
110
|
+
description: 'Creation timestamp (ISO 8601)',
|
|
111
|
+
example: '2025-12-20T10:30:00Z'
|
|
112
|
+
}),
|
|
113
|
+
updated_at: z.string().openapi({
|
|
114
|
+
description: 'Last update timestamp (ISO 8601)',
|
|
115
|
+
example: '2025-12-20T10:30:00Z'
|
|
116
|
+
}),
|
|
117
|
+
created_by: z.string().uuid().nullable().openapi({
|
|
118
|
+
description: 'UUID of user who created this parking area',
|
|
119
|
+
example: '550e8400-e29b-41d4-a716-446655440000'
|
|
120
|
+
}),
|
|
121
|
+
updated_by: z.string().uuid().nullable().openapi({
|
|
122
|
+
description: 'UUID of user who last updated this parking area',
|
|
123
|
+
example: '550e8400-e29b-41d4-a716-446655440000'
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
.openapi('ParkingAreaItem');
|
|
127
|
+
/**
|
|
128
|
+
* Parking area with schedule information (for list views)
|
|
129
|
+
*/
|
|
130
|
+
export const parkingAreaWithSchedulesSchema = parkingAreaItemSchema
|
|
131
|
+
.extend({
|
|
132
|
+
schedules: z
|
|
133
|
+
.array(z.object({
|
|
134
|
+
id: z.number().int().positive(),
|
|
135
|
+
date: z.string(),
|
|
136
|
+
start_time: z.string(),
|
|
137
|
+
end_time: z.string(),
|
|
138
|
+
duration: z.number(),
|
|
139
|
+
parking_area_schedule_type: z.enum([
|
|
140
|
+
ParkingAreaScheduleType.ASSEMBLY,
|
|
141
|
+
ParkingAreaScheduleType.DISMANTLING
|
|
142
|
+
]),
|
|
143
|
+
company_role: z.string().nullable()
|
|
144
|
+
}))
|
|
145
|
+
.optional()
|
|
146
|
+
.openapi({
|
|
147
|
+
description: 'Associated schedules for this parking area'
|
|
148
|
+
})
|
|
149
|
+
})
|
|
150
|
+
.openapi('ParkingAreaWithSchedules');
|
|
151
|
+
// ------------------------------
|
|
152
|
+
// Query Schemas
|
|
153
|
+
// ------------------------------
|
|
154
|
+
export const getParkingAreasByEventIdParamsSchema = z
|
|
155
|
+
.object({
|
|
156
|
+
eventId: z
|
|
157
|
+
.string()
|
|
158
|
+
.min(1)
|
|
159
|
+
.transform(val => parseInt(val, 10))
|
|
160
|
+
.refine(val => !isNaN(val) && val > 0, {
|
|
161
|
+
message: 'Event ID must be a positive number'
|
|
162
|
+
})
|
|
163
|
+
.openapi({
|
|
164
|
+
description: 'The ID of the event',
|
|
165
|
+
example: '1'
|
|
166
|
+
})
|
|
167
|
+
})
|
|
168
|
+
.openapi('GetParkingAreasByEventIdParams');
|
|
169
|
+
export const getParkingAreaByIdParamsSchema = z
|
|
170
|
+
.object({
|
|
171
|
+
areaId: z
|
|
172
|
+
.string()
|
|
173
|
+
.min(1)
|
|
174
|
+
.transform(val => parseInt(val, 10))
|
|
175
|
+
.refine(val => !isNaN(val) && val > 0, {
|
|
176
|
+
message: 'Parking area ID must be a positive number'
|
|
177
|
+
})
|
|
178
|
+
.openapi({
|
|
179
|
+
description: 'The ID of the parking area',
|
|
180
|
+
example: '1'
|
|
181
|
+
})
|
|
182
|
+
})
|
|
183
|
+
.openapi('GetParkingAreaByIdParams');
|
|
184
|
+
// ------------------------------
|
|
185
|
+
// Create Parking Area Schemas
|
|
186
|
+
// ------------------------------
|
|
187
|
+
export const createParkingAreaBodySchema = z
|
|
188
|
+
.object({
|
|
189
|
+
event_id: z.number().int().positive().openapi({
|
|
190
|
+
description: 'ID of the event',
|
|
191
|
+
example: 1
|
|
192
|
+
}),
|
|
193
|
+
name: z.string().min(1).max(255).openapi({
|
|
194
|
+
description: 'Name of the parking area',
|
|
195
|
+
example: 'Zone A - Main Entrance'
|
|
196
|
+
}),
|
|
197
|
+
geometry: geoJsonPolygonSchema.optional().openapi({
|
|
198
|
+
description: 'GeoJSON polygon geometry for the parking area'
|
|
199
|
+
}),
|
|
200
|
+
site_plan_image_url: z.string().url().optional().openapi({
|
|
201
|
+
description: 'URL to the site plan image',
|
|
202
|
+
example: 'https://storage.example.com/site-plans/event-123/floor-1.png'
|
|
203
|
+
}),
|
|
204
|
+
surface_area_sqm: z.number().nonnegative().optional().openapi({
|
|
205
|
+
description: 'Surface area in square meters',
|
|
206
|
+
example: 150.5
|
|
207
|
+
})
|
|
208
|
+
})
|
|
209
|
+
.openapi('CreateParkingAreaBody');
|
|
210
|
+
export const createParkingAreaDataSchema = z
|
|
211
|
+
.object({
|
|
212
|
+
parking_area_id: z.number().openapi({
|
|
213
|
+
description: 'ID of the created parking area',
|
|
214
|
+
example: 123
|
|
215
|
+
}),
|
|
216
|
+
event_id: z.number().openapi({
|
|
217
|
+
description: 'ID of the event',
|
|
218
|
+
example: 1
|
|
219
|
+
}),
|
|
220
|
+
name: z.string().nullable().openapi({
|
|
221
|
+
description: 'Name of the parking area',
|
|
222
|
+
example: 'Zone A - Main Entrance'
|
|
223
|
+
}),
|
|
224
|
+
geometry: geoJsonPolygonSchema.nullable().openapi({
|
|
225
|
+
description: 'GeoJSON polygon geometry'
|
|
226
|
+
}),
|
|
227
|
+
site_plan_image_url: z.string().nullable().openapi({
|
|
228
|
+
description: 'URL to the site plan image',
|
|
229
|
+
example: 'https://storage.example.com/site-plans/event-123/floor-1.png'
|
|
230
|
+
}),
|
|
231
|
+
surface_area_sqm: z.number().nullable().openapi({
|
|
232
|
+
description: 'Surface area in square meters',
|
|
233
|
+
example: 150.5
|
|
234
|
+
}),
|
|
235
|
+
is_active: z.boolean().openapi({
|
|
236
|
+
description: 'Whether the parking area is active',
|
|
237
|
+
example: true
|
|
238
|
+
}),
|
|
239
|
+
created_at: z.string().openapi({
|
|
240
|
+
description: 'Timestamp when parking area was created',
|
|
241
|
+
example: '2025-12-20T10:30:00.000Z'
|
|
242
|
+
}),
|
|
243
|
+
created_by: z.string().nullable().openapi({
|
|
244
|
+
description: 'ID of the user who created the parking area',
|
|
245
|
+
example: 'user-123'
|
|
246
|
+
})
|
|
247
|
+
})
|
|
248
|
+
.openapi('CreateParkingAreaData');
|
|
249
|
+
export const createParkingAreaResponseSchema = createMessageDataResponseSchema(createParkingAreaDataSchema, 'CreateParkingAreaResponse', 'Parking area created successfully', 'Details of the created parking area');
|
|
250
|
+
// ------------------------------
|
|
251
|
+
// Bulk Create Parking Areas Schemas
|
|
252
|
+
// ------------------------------
|
|
253
|
+
export const bulkCreateParkingAreasBodySchema = z
|
|
254
|
+
.object({
|
|
255
|
+
event_id: z.number().int().positive().openapi({
|
|
256
|
+
description: 'ID of the event',
|
|
257
|
+
example: 1
|
|
258
|
+
}),
|
|
259
|
+
parking_areas: z
|
|
260
|
+
.array(z.object({
|
|
261
|
+
name: z.string().min(1).max(255).openapi({
|
|
262
|
+
description: 'Name of the parking area',
|
|
263
|
+
example: 'Zone A'
|
|
264
|
+
}),
|
|
265
|
+
geometry: geoJsonPolygonSchema.optional(),
|
|
266
|
+
surface_area_sqm: z.number().nonnegative().optional()
|
|
267
|
+
}))
|
|
268
|
+
.min(1, 'At least one parking area must be provided')
|
|
269
|
+
.openapi({
|
|
270
|
+
description: 'Array of parking areas to create'
|
|
271
|
+
}),
|
|
272
|
+
site_plan_image_url: z.string().url().optional().openapi({
|
|
273
|
+
description: 'URL to the site plan image (shared by all areas in this batch)',
|
|
274
|
+
example: 'https://storage.example.com/site-plans/event-123/floor-1.png'
|
|
275
|
+
})
|
|
276
|
+
})
|
|
277
|
+
.openapi('BulkCreateParkingAreasBody');
|
|
278
|
+
export const bulkCreateParkingAreasDataSchema = z
|
|
279
|
+
.object({
|
|
280
|
+
event_id: z.number().openapi({
|
|
281
|
+
description: 'ID of the event',
|
|
282
|
+
example: 1
|
|
283
|
+
}),
|
|
284
|
+
total_created: z.number().openapi({
|
|
285
|
+
description: 'Total number of parking areas successfully created',
|
|
286
|
+
example: 5
|
|
287
|
+
}),
|
|
288
|
+
created_areas: z.array(createParkingAreaDataSchema).openapi({
|
|
289
|
+
description: 'Array of successfully created parking areas'
|
|
290
|
+
}),
|
|
291
|
+
failed_areas: z
|
|
292
|
+
.array(z.object({
|
|
293
|
+
index: z.number().openapi({
|
|
294
|
+
description: 'Index of the parking area in the request that failed',
|
|
295
|
+
example: 2
|
|
296
|
+
}),
|
|
297
|
+
name: z.string().openapi({
|
|
298
|
+
description: 'Name of the parking area that failed',
|
|
299
|
+
example: 'Zone C'
|
|
300
|
+
}),
|
|
301
|
+
error: z.string().openapi({
|
|
302
|
+
description: 'Error message explaining the failure',
|
|
303
|
+
example: 'Invalid geometry coordinates'
|
|
304
|
+
})
|
|
305
|
+
}))
|
|
306
|
+
.openapi({
|
|
307
|
+
description: 'Array of parking areas that failed to create'
|
|
308
|
+
})
|
|
309
|
+
})
|
|
310
|
+
.openapi('BulkCreateParkingAreasData');
|
|
311
|
+
export const bulkCreateParkingAreasResponseSchema = createMessageDataResponseSchema(bulkCreateParkingAreasDataSchema, 'BulkCreateParkingAreasResponse', 'Bulk parking areas operation completed', 'Details of the bulk creation operation');
|
|
312
|
+
// ------------------------------
|
|
313
|
+
// Update Parking Area Schemas
|
|
314
|
+
// ------------------------------
|
|
315
|
+
export const updateParkingAreaParamsSchema = z
|
|
316
|
+
.object({
|
|
317
|
+
areaId: z
|
|
318
|
+
.string()
|
|
319
|
+
.min(1)
|
|
320
|
+
.transform(val => parseInt(val, 10))
|
|
321
|
+
.refine(val => !isNaN(val) && val > 0, {
|
|
322
|
+
message: 'Parking area ID must be a positive number'
|
|
323
|
+
})
|
|
324
|
+
.openapi({
|
|
325
|
+
description: 'The ID of the parking area to update',
|
|
326
|
+
example: '123'
|
|
327
|
+
})
|
|
328
|
+
})
|
|
329
|
+
.openapi('UpdateParkingAreaParams');
|
|
330
|
+
export const updateParkingAreaBodySchema = z
|
|
331
|
+
.object({
|
|
332
|
+
name: z.string().min(1).max(255).optional().openapi({
|
|
333
|
+
description: 'New name for the parking area',
|
|
334
|
+
example: 'Zone B - Loading Dock'
|
|
335
|
+
}),
|
|
336
|
+
geometry: geoJsonPolygonSchema.nullable().optional().openapi({
|
|
337
|
+
description: 'New GeoJSON polygon geometry'
|
|
338
|
+
}),
|
|
339
|
+
site_plan_image_url: z.string().url().nullable().optional().openapi({
|
|
340
|
+
description: 'New site plan image URL',
|
|
341
|
+
example: 'https://storage.example.com/site-plans/event-123/floor-2.png'
|
|
342
|
+
}),
|
|
343
|
+
surface_area_sqm: z.number().nonnegative().nullable().optional().openapi({
|
|
344
|
+
description: 'New surface area in square meters',
|
|
345
|
+
example: 200.0
|
|
346
|
+
}),
|
|
347
|
+
is_active: z.boolean().optional().openapi({
|
|
348
|
+
description: 'Whether the parking area is active',
|
|
349
|
+
example: true
|
|
350
|
+
})
|
|
351
|
+
})
|
|
352
|
+
.refine(data => Object.keys(data).length > 0, {
|
|
353
|
+
message: 'At least one field must be provided for update'
|
|
354
|
+
})
|
|
355
|
+
.openapi('UpdateParkingAreaBody');
|
|
356
|
+
export const updateParkingAreaDataSchema = z
|
|
357
|
+
.object({
|
|
358
|
+
parking_area_id: z.number().openapi({
|
|
359
|
+
description: 'ID of the updated parking area',
|
|
360
|
+
example: 123
|
|
361
|
+
}),
|
|
362
|
+
event_id: z.number().openapi({
|
|
363
|
+
description: 'ID of the event',
|
|
364
|
+
example: 1
|
|
365
|
+
}),
|
|
366
|
+
name: z.string().nullable().openapi({
|
|
367
|
+
description: 'Updated name',
|
|
368
|
+
example: 'Zone B - Loading Dock'
|
|
369
|
+
}),
|
|
370
|
+
geometry: geoJsonPolygonSchema.nullable().openapi({
|
|
371
|
+
description: 'Updated GeoJSON polygon geometry'
|
|
372
|
+
}),
|
|
373
|
+
site_plan_image_url: z.string().nullable().openapi({
|
|
374
|
+
description: 'Updated site plan image URL'
|
|
375
|
+
}),
|
|
376
|
+
surface_area_sqm: z.number().nullable().openapi({
|
|
377
|
+
description: 'Updated surface area in square meters',
|
|
378
|
+
example: 200.0
|
|
379
|
+
}),
|
|
380
|
+
is_active: z.boolean().openapi({
|
|
381
|
+
description: 'Whether the parking area is active',
|
|
382
|
+
example: true
|
|
383
|
+
}),
|
|
384
|
+
updated_at: z.string().openapi({
|
|
385
|
+
description: 'Timestamp when parking area was updated',
|
|
386
|
+
example: '2025-12-20T11:00:00.000Z'
|
|
387
|
+
}),
|
|
388
|
+
updated_by: z.string().nullable().openapi({
|
|
389
|
+
description: 'ID of the user who updated the parking area',
|
|
390
|
+
example: 'user-123'
|
|
391
|
+
})
|
|
392
|
+
})
|
|
393
|
+
.openapi('UpdateParkingAreaData');
|
|
394
|
+
export const updateParkingAreaResponseSchema = createMessageDataResponseSchema(updateParkingAreaDataSchema, 'UpdateParkingAreaResponse', 'Parking area updated successfully', 'Details of the updated parking area');
|
|
395
|
+
// ------------------------------
|
|
396
|
+
// Delete Parking Area Schemas
|
|
397
|
+
// ------------------------------
|
|
398
|
+
export const deleteParkingAreaParamsSchema = z
|
|
399
|
+
.object({
|
|
400
|
+
areaId: z
|
|
401
|
+
.string()
|
|
402
|
+
.min(1)
|
|
403
|
+
.transform(val => parseInt(val, 10))
|
|
404
|
+
.refine(val => !isNaN(val) && val > 0, {
|
|
405
|
+
message: 'Parking area ID must be a positive number'
|
|
406
|
+
})
|
|
407
|
+
.openapi({
|
|
408
|
+
description: 'The ID of the parking area to delete',
|
|
409
|
+
example: '123'
|
|
410
|
+
})
|
|
411
|
+
})
|
|
412
|
+
.openapi('DeleteParkingAreaParams');
|
|
413
|
+
export const deleteParkingAreaDataSchema = z
|
|
414
|
+
.object({
|
|
415
|
+
parking_area_id: z.number().openapi({
|
|
416
|
+
description: 'ID of the deleted parking area',
|
|
417
|
+
example: 123
|
|
418
|
+
}),
|
|
419
|
+
deleted_at: z.string().openapi({
|
|
420
|
+
description: 'Timestamp when parking area was deleted',
|
|
421
|
+
example: '2025-12-20T12:00:00.000Z'
|
|
422
|
+
})
|
|
423
|
+
})
|
|
424
|
+
.openapi('DeleteParkingAreaData');
|
|
425
|
+
export const deleteParkingAreaResponseSchema = createMessageDataResponseSchema(deleteParkingAreaDataSchema, 'DeleteParkingAreaResponse', 'Parking area deleted successfully', 'Details of the deleted parking area');
|
|
426
|
+
// ------------------------------
|
|
427
|
+
// Get Parking Areas Response Schemas
|
|
428
|
+
// ------------------------------
|
|
429
|
+
export const getParkingAreasDataSchema = z
|
|
430
|
+
.object({
|
|
431
|
+
event_id: z.number().openapi({
|
|
432
|
+
description: 'ID of the event',
|
|
433
|
+
example: 1
|
|
434
|
+
}),
|
|
435
|
+
parking_areas: z.array(parkingAreaItemSchema).openapi({
|
|
436
|
+
description: 'List of parking areas for the event'
|
|
437
|
+
}),
|
|
438
|
+
total_count: z.number().openapi({
|
|
439
|
+
description: 'Total number of parking areas',
|
|
440
|
+
example: 10
|
|
441
|
+
})
|
|
442
|
+
})
|
|
443
|
+
.openapi('GetParkingAreasData');
|
|
444
|
+
export const getParkingAreasResponseSchema = createSuccessResponseSchema(getParkingAreasDataSchema, 'GetParkingAreasResponse', 'Parking areas for the event');
|
|
445
|
+
export const getParkingAreaDetailResponseSchema = createSuccessResponseSchema(parkingAreaWithSchedulesSchema, 'GetParkingAreaDetailResponse', 'Parking area details with schedules');
|
|
446
|
+
// ------------------------------
|
|
447
|
+
// Parking Area Schedule Schemas
|
|
448
|
+
// ------------------------------
|
|
449
|
+
export const parkingAreaScheduleSchema = z
|
|
450
|
+
.object({
|
|
451
|
+
id: z.number().int().positive().openapi({
|
|
452
|
+
description: 'Unique identifier for the schedule',
|
|
453
|
+
example: 1
|
|
454
|
+
}),
|
|
455
|
+
date: z.string().openapi({
|
|
456
|
+
description: 'Date of the schedule (YYYY-MM-DD format)',
|
|
457
|
+
example: '2025-12-15'
|
|
458
|
+
}),
|
|
459
|
+
start_time: z.string().openapi({
|
|
460
|
+
description: 'Start time (HH:MM format)',
|
|
461
|
+
example: '06:00'
|
|
462
|
+
}),
|
|
463
|
+
end_time: z.string().openapi({
|
|
464
|
+
description: 'End time (HH:MM format)',
|
|
465
|
+
example: '09:00'
|
|
466
|
+
}),
|
|
467
|
+
duration: z.number().openapi({
|
|
468
|
+
description: 'Duration in minutes',
|
|
469
|
+
example: 180
|
|
470
|
+
}),
|
|
471
|
+
parking_area_id: z.number().int().positive().openapi({
|
|
472
|
+
description: 'ID of the associated parking area',
|
|
473
|
+
example: 101
|
|
474
|
+
}),
|
|
475
|
+
parking_area_schedule_type: z
|
|
476
|
+
.enum([ParkingAreaScheduleType.ASSEMBLY, ParkingAreaScheduleType.DISMANTLING])
|
|
477
|
+
.openapi({
|
|
478
|
+
description: 'Type of schedule',
|
|
479
|
+
example: ParkingAreaScheduleType.ASSEMBLY
|
|
480
|
+
}),
|
|
481
|
+
company_role: z.string().nullable().openapi({
|
|
482
|
+
description: 'Company role allowed for this schedule',
|
|
483
|
+
example: 'exhibitor'
|
|
484
|
+
}),
|
|
485
|
+
created_at: z.string().openapi({
|
|
486
|
+
description: 'Timestamp when schedule was created',
|
|
487
|
+
example: '2025-12-09T10:00:00.000Z'
|
|
488
|
+
}),
|
|
489
|
+
updated_at: z.string().openapi({
|
|
490
|
+
description: 'Timestamp when schedule was updated',
|
|
491
|
+
example: '2025-12-09T10:30:00.000Z'
|
|
492
|
+
}),
|
|
493
|
+
created_by: z.string().uuid().nullable().openapi({
|
|
494
|
+
description: 'UUID of user who created the schedule',
|
|
495
|
+
example: '550e8400-e29b-41d4-a716-446655440000'
|
|
496
|
+
}),
|
|
497
|
+
updated_by: z.string().uuid().nullable().openapi({
|
|
498
|
+
description: 'UUID of user who last updated the schedule',
|
|
499
|
+
example: '550e8400-e29b-41d4-a716-446655440000'
|
|
500
|
+
})
|
|
501
|
+
})
|
|
502
|
+
.openapi('ParkingAreaSchedule');
|
|
503
|
+
// ------------------------------
|
|
504
|
+
// Upsert Parking Area Schedules Schemas
|
|
505
|
+
// ------------------------------
|
|
506
|
+
export const upsertParkingAreaScheduleItemSchema = z
|
|
507
|
+
.object({
|
|
508
|
+
id: z.number().int().positive().optional().openapi({
|
|
509
|
+
description: 'Unique identifier for existing schedule (required for updates)',
|
|
510
|
+
example: 1
|
|
511
|
+
}),
|
|
512
|
+
parking_area_id: z.number().int().positive().openapi({
|
|
513
|
+
description: 'ID of the associated parking area',
|
|
514
|
+
example: 101
|
|
515
|
+
}),
|
|
516
|
+
date: z
|
|
517
|
+
.string()
|
|
518
|
+
.regex(/^\d{4}-\d{2}-\d{2}$/, 'Date must be in YYYY-MM-DD format')
|
|
519
|
+
.openapi({
|
|
520
|
+
description: 'Date of the schedule (YYYY-MM-DD format)',
|
|
521
|
+
example: '2025-12-15'
|
|
522
|
+
}),
|
|
523
|
+
start_time: z
|
|
524
|
+
.string()
|
|
525
|
+
.regex(/^\d{2}:\d{2}$/, 'Time must be in HH:MM format')
|
|
526
|
+
.default('06:00')
|
|
527
|
+
.openapi({
|
|
528
|
+
description: 'Start time (HH:MM format)',
|
|
529
|
+
example: '06:00'
|
|
530
|
+
}),
|
|
531
|
+
end_time: z
|
|
532
|
+
.string()
|
|
533
|
+
.regex(/^\d{2}:\d{2}$/, 'Time must be in HH:MM format')
|
|
534
|
+
.default('09:00')
|
|
535
|
+
.openapi({
|
|
536
|
+
description: 'End time (HH:MM format)',
|
|
537
|
+
example: '09:00'
|
|
538
|
+
}),
|
|
539
|
+
duration: z.number().positive().default(60).openapi({
|
|
540
|
+
description: 'Duration in minutes',
|
|
541
|
+
example: 180
|
|
542
|
+
}),
|
|
543
|
+
parking_area_schedule_type: z
|
|
544
|
+
.enum([ParkingAreaScheduleType.ASSEMBLY, ParkingAreaScheduleType.DISMANTLING])
|
|
545
|
+
.openapi({
|
|
546
|
+
description: 'Type of schedule',
|
|
547
|
+
example: ParkingAreaScheduleType.ASSEMBLY
|
|
548
|
+
}),
|
|
549
|
+
company_role: z.string().nullable().optional().openapi({
|
|
550
|
+
description: 'Company role allowed for this schedule',
|
|
551
|
+
example: 'exhibitor'
|
|
552
|
+
})
|
|
553
|
+
})
|
|
554
|
+
.openapi('UpsertParkingAreaScheduleItem');
|
|
555
|
+
export const deleteParkingAreaScheduleItemSchema = z
|
|
556
|
+
.object({
|
|
557
|
+
id: z.number().int().positive().openapi({
|
|
558
|
+
description: 'ID of the schedule to delete',
|
|
559
|
+
example: 1
|
|
560
|
+
})
|
|
561
|
+
})
|
|
562
|
+
.openapi('DeleteParkingAreaScheduleItem');
|
|
563
|
+
export const upsertParkingAreaSchedulesBodySchema = z
|
|
564
|
+
.object({
|
|
565
|
+
upsert: z
|
|
566
|
+
.array(upsertParkingAreaScheduleItemSchema)
|
|
567
|
+
.optional()
|
|
568
|
+
.openapi({
|
|
569
|
+
description: 'Array of schedules to create or update'
|
|
570
|
+
}),
|
|
571
|
+
delete: z
|
|
572
|
+
.array(deleteParkingAreaScheduleItemSchema)
|
|
573
|
+
.optional()
|
|
574
|
+
.openapi({
|
|
575
|
+
description: 'Array of schedule IDs to delete'
|
|
576
|
+
})
|
|
577
|
+
})
|
|
578
|
+
.refine(data => (data.upsert && data.upsert.length > 0) || (data.delete && data.delete.length > 0), {
|
|
579
|
+
message: 'At least one upsert or delete operation is required'
|
|
580
|
+
})
|
|
581
|
+
.openapi('UpsertParkingAreaSchedulesBody');
|
|
582
|
+
export const upsertParkingAreaSchedulesDataSchema = z
|
|
583
|
+
.object({
|
|
584
|
+
total_upserted: z.number().int().nonnegative().openapi({
|
|
585
|
+
description: 'Total number of schedules successfully upserted',
|
|
586
|
+
example: 3
|
|
587
|
+
}),
|
|
588
|
+
total_deleted: z.number().int().nonnegative().openapi({
|
|
589
|
+
description: 'Total number of schedules successfully deleted',
|
|
590
|
+
example: 1
|
|
591
|
+
}),
|
|
592
|
+
upserted_schedules: z.array(parkingAreaScheduleSchema).openapi({
|
|
593
|
+
description: 'Array of successfully upserted schedules'
|
|
594
|
+
}),
|
|
595
|
+
deleted_schedules: z
|
|
596
|
+
.array(z.object({
|
|
597
|
+
id: z.number().int().positive()
|
|
598
|
+
}))
|
|
599
|
+
.openapi({
|
|
600
|
+
description: 'Array of successfully deleted schedule IDs'
|
|
601
|
+
}),
|
|
602
|
+
failed_operations: z
|
|
603
|
+
.array(z.object({
|
|
604
|
+
operation: z.enum(['upsert', 'delete']),
|
|
605
|
+
error: z.string(),
|
|
606
|
+
data: z.union([upsertParkingAreaScheduleItemSchema, deleteParkingAreaScheduleItemSchema])
|
|
607
|
+
}))
|
|
608
|
+
.openapi({
|
|
609
|
+
description: 'Array of operations that failed to process'
|
|
610
|
+
})
|
|
611
|
+
})
|
|
612
|
+
.openapi('UpsertParkingAreaSchedulesData');
|
|
613
|
+
export const upsertParkingAreaSchedulesResponseSchema = createMessageDataResponseSchema(upsertParkingAreaSchedulesDataSchema, 'UpsertParkingAreaSchedulesResponse', 'Parking area schedules operation completed', 'Results of the upsert/delete operations');
|
|
614
|
+
// ------------------------------
|
|
615
|
+
// Site Plan Image Upload Schemas
|
|
616
|
+
// ------------------------------
|
|
617
|
+
export const uploadSitePlanImageParamsSchema = z
|
|
618
|
+
.object({
|
|
619
|
+
eventId: z
|
|
620
|
+
.string()
|
|
621
|
+
.min(1)
|
|
622
|
+
.transform(val => parseInt(val, 10))
|
|
623
|
+
.refine(val => !isNaN(val) && val > 0, {
|
|
624
|
+
message: 'Event ID must be a positive number'
|
|
625
|
+
})
|
|
626
|
+
.openapi({
|
|
627
|
+
description: 'The ID of the event',
|
|
628
|
+
example: '1'
|
|
629
|
+
})
|
|
630
|
+
})
|
|
631
|
+
.openapi('UploadSitePlanImageParams');
|
|
632
|
+
export const uploadSitePlanImageDataSchema = z
|
|
633
|
+
.object({
|
|
634
|
+
image_url: z.string().url().openapi({
|
|
635
|
+
description: 'URL of the uploaded site plan image',
|
|
636
|
+
example: 'https://storage.example.com/site-plans/event-123/floor-1.png'
|
|
637
|
+
}),
|
|
638
|
+
file_name: z.string().openapi({
|
|
639
|
+
description: 'Original file name',
|
|
640
|
+
example: 'floor-plan.png'
|
|
641
|
+
}),
|
|
642
|
+
file_size: z.number().openapi({
|
|
643
|
+
description: 'File size in bytes',
|
|
644
|
+
example: 1024000
|
|
645
|
+
}),
|
|
646
|
+
mime_type: z.string().openapi({
|
|
647
|
+
description: 'MIME type of the uploaded file',
|
|
648
|
+
example: 'image/png'
|
|
649
|
+
}),
|
|
650
|
+
uploaded_at: z.string().openapi({
|
|
651
|
+
description: 'Timestamp when the image was uploaded',
|
|
652
|
+
example: '2025-12-20T10:30:00.000Z'
|
|
653
|
+
})
|
|
654
|
+
})
|
|
655
|
+
.openapi('UploadSitePlanImageData');
|
|
656
|
+
export const uploadSitePlanImageResponseSchema = createMessageDataResponseSchema(uploadSitePlanImageDataSchema, 'UploadSitePlanImageResponse', 'Site plan image uploaded successfully', 'Details of the uploaded image');
|
|
657
|
+
// ------------------------------
|
|
658
|
+
// Available Time Slots (Parking Area version)
|
|
659
|
+
// ------------------------------
|
|
660
|
+
export const getAvailableParkingAreaSlotsQuerySchema = z
|
|
661
|
+
.object({
|
|
662
|
+
event_id: z
|
|
663
|
+
.string()
|
|
664
|
+
.min(1)
|
|
665
|
+
.transform(val => parseInt(val, 10))
|
|
666
|
+
.refine(val => !isNaN(val) && val > 0, {
|
|
667
|
+
message: 'Event ID must be a positive number'
|
|
668
|
+
})
|
|
669
|
+
.openapi({
|
|
670
|
+
description: 'The ID of the event',
|
|
671
|
+
example: '1'
|
|
672
|
+
}),
|
|
673
|
+
year: z
|
|
674
|
+
.string()
|
|
675
|
+
.regex(/^\d{4}$/, 'Year must be in YYYY format')
|
|
676
|
+
.transform(val => parseInt(val, 10))
|
|
677
|
+
.openapi({
|
|
678
|
+
description: 'Year for filtering slots (YYYY)',
|
|
679
|
+
example: '2026'
|
|
680
|
+
}),
|
|
681
|
+
month: z
|
|
682
|
+
.string()
|
|
683
|
+
.regex(/^(0?[1-9]|1[0-2])$/, 'Month must be between 1 and 12')
|
|
684
|
+
.transform(val => parseInt(val, 10))
|
|
685
|
+
.openapi({
|
|
686
|
+
description: 'Month for filtering slots (1-12)',
|
|
687
|
+
example: '6'
|
|
688
|
+
}),
|
|
689
|
+
parking_area_id: z
|
|
690
|
+
.string()
|
|
691
|
+
.min(1)
|
|
692
|
+
.transform(val => parseInt(val, 10))
|
|
693
|
+
.refine(val => !isNaN(val) && val > 0, {
|
|
694
|
+
message: 'Parking area ID must be a positive number'
|
|
695
|
+
})
|
|
696
|
+
.openapi({
|
|
697
|
+
description: 'The ID of the parking area',
|
|
698
|
+
example: '1'
|
|
699
|
+
}),
|
|
700
|
+
schedule_type: z.enum(['assembly', 'dismantling']).optional().openapi({
|
|
701
|
+
description: 'Filter by schedule type',
|
|
702
|
+
example: 'assembly'
|
|
703
|
+
})
|
|
704
|
+
})
|
|
705
|
+
.openapi('GetAvailableParkingAreaSlotsQuery');
|
|
706
|
+
export const availableParkingAreaTimeSlotSchema = z
|
|
707
|
+
.object({
|
|
708
|
+
parking_area_schedule_id: z.number().openapi({
|
|
709
|
+
description: 'Parking area schedule ID',
|
|
710
|
+
example: 1
|
|
711
|
+
}),
|
|
712
|
+
date: z.string().openapi({
|
|
713
|
+
description: 'Date of the time slot (YYYY-MM-DD)',
|
|
714
|
+
example: '2026-06-15'
|
|
715
|
+
}),
|
|
716
|
+
start_time: z.string().openapi({
|
|
717
|
+
description: 'Start time of the slot (HH:MM)',
|
|
718
|
+
example: '08:00'
|
|
719
|
+
}),
|
|
720
|
+
end_time: z.string().openapi({
|
|
721
|
+
description: 'End time of the slot (HH:MM)',
|
|
722
|
+
example: '08:30'
|
|
723
|
+
}),
|
|
724
|
+
duration: z.number().openapi({
|
|
725
|
+
description: 'Duration in minutes',
|
|
726
|
+
example: 30
|
|
727
|
+
}),
|
|
728
|
+
schedule_type: z.enum(['assembly', 'dismantling']).openapi({
|
|
729
|
+
description: 'Type of schedule',
|
|
730
|
+
example: 'assembly'
|
|
731
|
+
}),
|
|
732
|
+
company_role: z.string().nullable().openapi({
|
|
733
|
+
description: 'Company role allowed for this schedule',
|
|
734
|
+
example: 'exhibitor'
|
|
735
|
+
}),
|
|
736
|
+
max_capacity: z.number().openapi({
|
|
737
|
+
description: 'Maximum booking capacity for this slot',
|
|
738
|
+
example: 5
|
|
739
|
+
}),
|
|
740
|
+
current_bookings: z.number().openapi({
|
|
741
|
+
description: 'Number of current bookings',
|
|
742
|
+
example: 3
|
|
743
|
+
}),
|
|
744
|
+
confirmed_bookings: z.number().openapi({
|
|
745
|
+
description: 'Number of confirmed bookings',
|
|
746
|
+
example: 1
|
|
747
|
+
}),
|
|
748
|
+
available_capacity: z.number().openapi({
|
|
749
|
+
description: 'Remaining available capacity',
|
|
750
|
+
example: 2
|
|
751
|
+
}),
|
|
752
|
+
is_available: z.boolean().openapi({
|
|
753
|
+
description: 'Whether the slot is available for booking',
|
|
754
|
+
example: true
|
|
755
|
+
})
|
|
756
|
+
})
|
|
757
|
+
.openapi('AvailableParkingAreaTimeSlot');
|
|
758
|
+
export const availableParkingAreaSlotsResponseSchema = z
|
|
759
|
+
.object({
|
|
760
|
+
event_id: z.number().openapi({
|
|
761
|
+
description: 'ID of the event',
|
|
762
|
+
example: 1
|
|
763
|
+
}),
|
|
764
|
+
event_code: z.string().openapi({
|
|
765
|
+
description: 'Code of the event',
|
|
766
|
+
example: 'COEC2025'
|
|
767
|
+
}),
|
|
768
|
+
event_name: z.string().openapi({
|
|
769
|
+
description: 'Name of the event',
|
|
770
|
+
example: 'COEC 2025'
|
|
771
|
+
}),
|
|
772
|
+
parking_area_id: z.number().openapi({
|
|
773
|
+
description: 'ID of the parking area',
|
|
774
|
+
example: 1
|
|
775
|
+
}),
|
|
776
|
+
parking_area_name: z.string().nullable().openapi({
|
|
777
|
+
description: 'Name of the parking area',
|
|
778
|
+
example: 'Zone A - Main Entrance'
|
|
779
|
+
}),
|
|
780
|
+
year: z.number().openapi({
|
|
781
|
+
description: 'Year of the slots',
|
|
782
|
+
example: 2026
|
|
783
|
+
}),
|
|
784
|
+
month: z.number().openapi({
|
|
785
|
+
description: 'Month of the slots',
|
|
786
|
+
example: 6
|
|
787
|
+
}),
|
|
788
|
+
available_slots: z.array(availableParkingAreaTimeSlotSchema).openapi({
|
|
789
|
+
description: 'List of available time slots with capacity information'
|
|
790
|
+
}),
|
|
791
|
+
total_slots: z.number().openapi({
|
|
792
|
+
description: 'Total number of time slots in the period',
|
|
793
|
+
example: 48
|
|
794
|
+
})
|
|
795
|
+
})
|
|
796
|
+
.openapi('AvailableParkingAreaSlotsResponse');
|