feat(attendance): add geofence monitoring and policy controls
This commit is contained in:
@@ -16,6 +16,7 @@ const preferredLocationSchema = z.object({
|
||||
|
||||
const hhmmSchema = z.string().regex(/^\d{2}:\d{2}$/, 'Time must use HH:MM format');
|
||||
const isoDateSchema = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'Date must use YYYY-MM-DD format');
|
||||
const clockInModeSchema = z.enum(['NFC_REQUIRED', 'GEO_REQUIRED', 'EITHER']);
|
||||
|
||||
const shiftPositionSchema = z.object({
|
||||
roleId: z.string().uuid().optional(),
|
||||
@@ -68,6 +69,8 @@ export const hubCreateSchema = z.object({
|
||||
costCenterId: z.string().uuid().optional(),
|
||||
geofenceRadiusMeters: z.number().int().positive().optional(),
|
||||
nfcTagId: z.string().max(255).optional(),
|
||||
clockInMode: clockInModeSchema.optional(),
|
||||
allowClockInOverride: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export const hubUpdateSchema = hubCreateSchema.extend({
|
||||
@@ -203,6 +206,7 @@ export const staffClockInSchema = z.object({
|
||||
accuracyMeters: z.number().int().nonnegative().optional(),
|
||||
capturedAt: z.string().datetime().optional(),
|
||||
notes: z.string().max(2000).optional(),
|
||||
overrideReason: z.string().max(2000).optional(),
|
||||
rawPayload: z.record(z.any()).optional(),
|
||||
}).refine((value) => value.assignmentId || value.shiftId, {
|
||||
message: 'assignmentId or shiftId is required',
|
||||
@@ -221,12 +225,34 @@ export const staffClockOutSchema = z.object({
|
||||
accuracyMeters: z.number().int().nonnegative().optional(),
|
||||
capturedAt: z.string().datetime().optional(),
|
||||
notes: z.string().max(2000).optional(),
|
||||
overrideReason: z.string().max(2000).optional(),
|
||||
breakMinutes: z.number().int().nonnegative().optional(),
|
||||
rawPayload: z.record(z.any()).optional(),
|
||||
}).refine((value) => value.assignmentId || value.shiftId || value.applicationId, {
|
||||
message: 'assignmentId, shiftId, or applicationId is required',
|
||||
});
|
||||
|
||||
const locationPointSchema = z.object({
|
||||
capturedAt: z.string().datetime(),
|
||||
latitude: z.number().min(-90).max(90).optional(),
|
||||
longitude: z.number().min(-180).max(180).optional(),
|
||||
accuracyMeters: z.number().int().nonnegative().optional(),
|
||||
speedMps: z.number().nonnegative().optional(),
|
||||
isMocked: z.boolean().optional(),
|
||||
metadata: z.record(z.any()).optional(),
|
||||
});
|
||||
|
||||
export const staffLocationBatchSchema = z.object({
|
||||
assignmentId: z.string().uuid().optional(),
|
||||
shiftId: z.string().uuid().optional(),
|
||||
sourceType: z.enum(['NFC', 'GEO', 'QR', 'MANUAL', 'SYSTEM']).default('GEO'),
|
||||
deviceId: z.string().max(255).optional(),
|
||||
points: z.array(locationPointSchema).min(1).max(96),
|
||||
metadata: z.record(z.any()).optional(),
|
||||
}).refine((value) => value.assignmentId || value.shiftId, {
|
||||
message: 'assignmentId or shiftId is required',
|
||||
});
|
||||
|
||||
export const staffProfileSetupSchema = z.object({
|
||||
fullName: z.string().min(2).max(160),
|
||||
bio: z.string().max(5000).optional(),
|
||||
|
||||
Reference in New Issue
Block a user