302 lines
11 KiB
JavaScript
302 lines
11 KiB
JavaScript
import { z } from 'zod';
|
|
|
|
const timeSlotSchema = z.object({
|
|
start: z.string().min(1).max(20),
|
|
end: z.string().min(1).max(20),
|
|
});
|
|
|
|
const preferredLocationSchema = z.object({
|
|
label: z.string().min(1).max(160),
|
|
city: z.string().max(120).optional(),
|
|
state: z.string().max(80).optional(),
|
|
latitude: z.number().min(-90).max(90).optional(),
|
|
longitude: z.number().min(-180).max(180).optional(),
|
|
radiusMiles: z.number().nonnegative().optional(),
|
|
});
|
|
|
|
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 shiftPositionSchema = z.object({
|
|
roleId: z.string().uuid().optional(),
|
|
roleCode: z.string().min(1).max(120).optional(),
|
|
roleName: z.string().min(1).max(160).optional(),
|
|
workerCount: z.number().int().positive().optional(),
|
|
workersNeeded: z.number().int().positive().optional(),
|
|
startTime: hhmmSchema,
|
|
endTime: hhmmSchema,
|
|
hourlyRateCents: z.number().int().nonnegative().optional(),
|
|
payRateCents: z.number().int().nonnegative().optional(),
|
|
billRateCents: z.number().int().nonnegative().optional(),
|
|
lunchBreakMinutes: z.number().int().nonnegative().optional(),
|
|
paidBreak: z.boolean().optional(),
|
|
instantBook: z.boolean().optional(),
|
|
metadata: z.record(z.any()).optional(),
|
|
}).superRefine((value, ctx) => {
|
|
if (!value.roleId && !value.roleCode && !value.roleName) {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
message: 'roleId, roleCode, or roleName is required',
|
|
path: ['roleId'],
|
|
});
|
|
}
|
|
});
|
|
|
|
const baseOrderCreateSchema = z.object({
|
|
hubId: z.string().uuid(),
|
|
vendorId: z.string().uuid().optional(),
|
|
eventName: z.string().min(2).max(160),
|
|
timezone: z.string().min(1).max(80).optional(),
|
|
description: z.string().max(5000).optional(),
|
|
notes: z.string().max(5000).optional(),
|
|
serviceType: z.enum(['EVENT', 'CATERING', 'HOTEL', 'RESTAURANT', 'OTHER']).optional(),
|
|
positions: z.array(shiftPositionSchema).min(1),
|
|
metadata: z.record(z.any()).optional(),
|
|
});
|
|
|
|
export const hubCreateSchema = z.object({
|
|
name: z.string().min(1).max(160),
|
|
fullAddress: z.string().max(300).optional(),
|
|
placeId: z.string().max(255).optional(),
|
|
latitude: z.number().min(-90).max(90).optional(),
|
|
longitude: z.number().min(-180).max(180).optional(),
|
|
street: z.string().max(160).optional(),
|
|
city: z.string().max(120).optional(),
|
|
state: z.string().max(80).optional(),
|
|
country: z.string().max(80).optional(),
|
|
zipCode: z.string().max(40).optional(),
|
|
costCenterId: z.string().uuid().optional(),
|
|
geofenceRadiusMeters: z.number().int().positive().optional(),
|
|
nfcTagId: z.string().max(255).optional(),
|
|
});
|
|
|
|
export const hubUpdateSchema = hubCreateSchema.extend({
|
|
hubId: z.string().uuid(),
|
|
});
|
|
|
|
export const hubDeleteSchema = z.object({
|
|
hubId: z.string().uuid(),
|
|
reason: z.string().max(1000).optional(),
|
|
});
|
|
|
|
export const hubAssignNfcSchema = z.object({
|
|
hubId: z.string().uuid(),
|
|
nfcTagId: z.string().min(1).max(255),
|
|
});
|
|
|
|
export const hubAssignManagerSchema = z.object({
|
|
hubId: z.string().uuid(),
|
|
businessMembershipId: z.string().uuid().optional(),
|
|
managerUserId: z.string().min(1).optional(),
|
|
}).refine((value) => value.businessMembershipId || value.managerUserId, {
|
|
message: 'businessMembershipId or managerUserId is required',
|
|
});
|
|
|
|
export const invoiceApproveSchema = z.object({
|
|
invoiceId: z.string().uuid(),
|
|
});
|
|
|
|
export const invoiceDisputeSchema = z.object({
|
|
invoiceId: z.string().uuid(),
|
|
reason: z.string().min(3).max(2000),
|
|
});
|
|
|
|
export const coverageReviewSchema = z.object({
|
|
staffId: z.string().uuid(),
|
|
assignmentId: z.string().uuid().optional(),
|
|
rating: z.number().int().min(1).max(5),
|
|
markAsFavorite: z.boolean().optional(),
|
|
issueFlags: z.array(z.string().min(1).max(80)).max(20).optional(),
|
|
feedback: z.string().max(5000).optional(),
|
|
});
|
|
|
|
export const cancelLateWorkerSchema = z.object({
|
|
assignmentId: z.string().uuid(),
|
|
reason: z.string().max(1000).optional(),
|
|
});
|
|
|
|
export const clientOneTimeOrderSchema = baseOrderCreateSchema.extend({
|
|
orderDate: isoDateSchema,
|
|
});
|
|
|
|
export const clientRecurringOrderSchema = baseOrderCreateSchema.extend({
|
|
startDate: isoDateSchema,
|
|
endDate: isoDateSchema,
|
|
recurrenceDays: z.array(z.number().int().min(0).max(6)).min(1),
|
|
});
|
|
|
|
export const clientPermanentOrderSchema = baseOrderCreateSchema.extend({
|
|
startDate: isoDateSchema,
|
|
endDate: isoDateSchema.optional(),
|
|
daysOfWeek: z.array(z.number().int().min(0).max(6)).min(1).optional(),
|
|
horizonDays: z.number().int().min(7).max(180).optional(),
|
|
});
|
|
|
|
export const clientOrderEditSchema = z.object({
|
|
orderId: z.string().uuid(),
|
|
orderType: z.enum(['ONE_TIME', 'RECURRING', 'PERMANENT']).optional(),
|
|
hubId: z.string().uuid().optional(),
|
|
vendorId: z.string().uuid().optional(),
|
|
eventName: z.string().min(2).max(160).optional(),
|
|
orderDate: isoDateSchema.optional(),
|
|
startDate: isoDateSchema.optional(),
|
|
endDate: isoDateSchema.optional(),
|
|
recurrenceDays: z.array(z.number().int().min(0).max(6)).min(1).optional(),
|
|
daysOfWeek: z.array(z.number().int().min(0).max(6)).min(1).optional(),
|
|
timezone: z.string().min(1).max(80).optional(),
|
|
description: z.string().max(5000).optional(),
|
|
notes: z.string().max(5000).optional(),
|
|
serviceType: z.enum(['EVENT', 'CATERING', 'HOTEL', 'RESTAURANT', 'OTHER']).optional(),
|
|
positions: z.array(shiftPositionSchema).min(1).optional(),
|
|
metadata: z.record(z.any()).optional(),
|
|
}).superRefine((value, ctx) => {
|
|
const keys = Object.keys(value).filter((key) => key !== 'orderId');
|
|
if (keys.length === 0) {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
message: 'At least one field must be provided to create an edited order copy',
|
|
path: [],
|
|
});
|
|
}
|
|
});
|
|
|
|
export const clientOrderCancelSchema = z.object({
|
|
orderId: z.string().uuid(),
|
|
reason: z.string().max(1000).optional(),
|
|
metadata: z.record(z.any()).optional(),
|
|
});
|
|
|
|
export const availabilityDayUpdateSchema = z.object({
|
|
dayOfWeek: z.number().int().min(0).max(6),
|
|
availabilityStatus: z.enum(['AVAILABLE', 'UNAVAILABLE', 'PARTIAL']),
|
|
slots: z.array(timeSlotSchema).max(8).optional(),
|
|
metadata: z.record(z.any()).optional(),
|
|
});
|
|
|
|
export const availabilityQuickSetSchema = z.object({
|
|
startDate: z.string().datetime().optional(),
|
|
endDate: z.string().datetime().optional(),
|
|
quickSetType: z.enum(['all', 'weekdays', 'weekends', 'clear']),
|
|
slots: z.array(timeSlotSchema).max(8).optional(),
|
|
});
|
|
|
|
export const shiftApplySchema = z.object({
|
|
shiftId: z.string().uuid(),
|
|
roleId: z.string().uuid().optional(),
|
|
instantBook: z.boolean().optional(),
|
|
});
|
|
|
|
export const shiftDecisionSchema = z.object({
|
|
shiftId: z.string().uuid(),
|
|
reason: z.string().max(1000).optional(),
|
|
});
|
|
|
|
export const staffClockInSchema = z.object({
|
|
assignmentId: z.string().uuid().optional(),
|
|
shiftId: z.string().uuid().optional(),
|
|
sourceType: z.enum(['NFC', 'GEO', 'QR', 'MANUAL', 'SYSTEM']).optional(),
|
|
sourceReference: z.string().max(255).optional(),
|
|
nfcTagId: z.string().max(255).optional(),
|
|
deviceId: z.string().max(255).optional(),
|
|
latitude: z.number().min(-90).max(90).optional(),
|
|
longitude: z.number().min(-180).max(180).optional(),
|
|
accuracyMeters: z.number().int().nonnegative().optional(),
|
|
capturedAt: z.string().datetime().optional(),
|
|
notes: z.string().max(2000).optional(),
|
|
rawPayload: z.record(z.any()).optional(),
|
|
}).refine((value) => value.assignmentId || value.shiftId, {
|
|
message: 'assignmentId or shiftId is required',
|
|
});
|
|
|
|
export const staffClockOutSchema = z.object({
|
|
assignmentId: z.string().uuid().optional(),
|
|
shiftId: z.string().uuid().optional(),
|
|
applicationId: z.string().uuid().optional(),
|
|
sourceType: z.enum(['NFC', 'GEO', 'QR', 'MANUAL', 'SYSTEM']).optional(),
|
|
sourceReference: z.string().max(255).optional(),
|
|
nfcTagId: z.string().max(255).optional(),
|
|
deviceId: z.string().max(255).optional(),
|
|
latitude: z.number().min(-90).max(90).optional(),
|
|
longitude: z.number().min(-180).max(180).optional(),
|
|
accuracyMeters: z.number().int().nonnegative().optional(),
|
|
capturedAt: z.string().datetime().optional(),
|
|
notes: 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',
|
|
});
|
|
|
|
export const staffProfileSetupSchema = z.object({
|
|
fullName: z.string().min(2).max(160),
|
|
bio: z.string().max(5000).optional(),
|
|
email: z.string().email().optional(),
|
|
phoneNumber: z.string().min(6).max(40),
|
|
preferredLocations: z.array(preferredLocationSchema).max(20).optional(),
|
|
maxDistanceMiles: z.number().nonnegative().max(500).optional(),
|
|
industries: z.array(z.string().min(1).max(80)).max(30).optional(),
|
|
skills: z.array(z.string().min(1).max(80)).max(50).optional(),
|
|
primaryRole: z.string().max(120).optional(),
|
|
tenantId: z.string().uuid().optional(),
|
|
vendorId: z.string().uuid().optional(),
|
|
});
|
|
|
|
export const personalInfoUpdateSchema = z.object({
|
|
firstName: z.string().min(1).max(80).optional(),
|
|
lastName: z.string().min(1).max(80).optional(),
|
|
bio: z.string().max(5000).optional(),
|
|
preferredLocations: z.array(preferredLocationSchema).max(20).optional(),
|
|
maxDistanceMiles: z.number().nonnegative().max(500).optional(),
|
|
email: z.string().email().optional(),
|
|
phone: z.string().min(6).max(40).optional(),
|
|
displayName: z.string().min(2).max(160).optional(),
|
|
});
|
|
|
|
export const profileExperienceSchema = z.object({
|
|
industries: z.array(z.string().min(1).max(80)).max(30).optional(),
|
|
skills: z.array(z.string().min(1).max(80)).max(50).optional(),
|
|
primaryRole: z.string().max(120).optional(),
|
|
});
|
|
|
|
export const preferredLocationsUpdateSchema = z.object({
|
|
preferredLocations: z.array(preferredLocationSchema).max(20),
|
|
maxDistanceMiles: z.number().nonnegative().max(500).optional(),
|
|
});
|
|
|
|
export const emergencyContactCreateSchema = z.object({
|
|
fullName: z.string().min(2).max(160),
|
|
phone: z.string().min(6).max(40),
|
|
relationshipType: z.string().min(1).max(120),
|
|
isPrimary: z.boolean().optional(),
|
|
metadata: z.record(z.any()).optional(),
|
|
});
|
|
|
|
export const emergencyContactUpdateSchema = emergencyContactCreateSchema.partial().extend({
|
|
contactId: z.string().uuid(),
|
|
});
|
|
|
|
const taxFormFieldsSchema = z.record(z.any());
|
|
|
|
export const taxFormDraftSchema = z.object({
|
|
formType: z.enum(['I9', 'W4']),
|
|
fields: taxFormFieldsSchema,
|
|
});
|
|
|
|
export const taxFormSubmitSchema = z.object({
|
|
formType: z.enum(['I9', 'W4']),
|
|
fields: taxFormFieldsSchema,
|
|
});
|
|
|
|
export const bankAccountCreateSchema = z.object({
|
|
bankName: z.string().min(2).max(160),
|
|
accountNumber: z.string().min(4).max(34),
|
|
routingNumber: z.string().min(4).max(20),
|
|
accountType: z.string()
|
|
.transform((value) => value.trim().toUpperCase())
|
|
.pipe(z.enum(['CHECKING', 'SAVINGS'])),
|
|
});
|
|
|
|
export const privacyUpdateSchema = z.object({
|
|
profileVisible: z.boolean(),
|
|
});
|