feat(backend): implement v2 domain slice and live smoke

This commit is contained in:
zouantchaw
2026-03-11 18:23:55 +01:00
parent bc068373e9
commit fe43ff23cf
40 changed files with 5191 additions and 99 deletions

View File

@@ -0,0 +1,14 @@
import { z } from 'zod';
export const attendanceCommandSchema = z.object({
assignmentId: z.string().uuid(),
sourceType: z.enum(['NFC', 'GEO', 'QR', 'MANUAL', 'SYSTEM']),
sourceReference: z.string().max(255).optional(),
nfcTagUid: 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(),
rawPayload: z.record(z.any()).optional(),
});

View File

@@ -0,0 +1,7 @@
import { z } from 'zod';
export const favoriteStaffSchema = z.object({
tenantId: z.string().uuid(),
businessId: z.string().uuid(),
staffId: z.string().uuid(),
});

View File

@@ -0,0 +1,8 @@
import { z } from 'zod';
export const orderCancelSchema = z.object({
orderId: z.string().uuid(),
tenantId: z.string().uuid(),
reason: z.string().max(1000).optional(),
metadata: z.record(z.any()).optional(),
});

View File

@@ -0,0 +1,57 @@
import { z } from 'zod';
const roleSchema = z.object({
roleCode: z.string().min(1).max(100),
roleName: z.string().min(1).max(120),
workersNeeded: z.number().int().positive(),
payRateCents: z.number().int().nonnegative().optional(),
billRateCents: z.number().int().nonnegative().optional(),
metadata: z.record(z.any()).optional(),
});
const shiftSchema = z.object({
shiftCode: z.string().min(1).max(80),
title: z.string().min(1).max(160),
status: z.enum([
'DRAFT',
'OPEN',
'PENDING_CONFIRMATION',
'ASSIGNED',
'ACTIVE',
'COMPLETED',
'CANCELLED',
]).optional(),
startsAt: z.string().datetime(),
endsAt: z.string().datetime(),
timezone: z.string().min(1).max(80).optional(),
clockPointId: z.string().uuid().optional(),
locationName: z.string().max(160).optional(),
locationAddress: z.string().max(300).optional(),
latitude: z.number().min(-90).max(90).optional(),
longitude: z.number().min(-180).max(180).optional(),
geofenceRadiusMeters: z.number().int().positive().optional(),
requiredWorkers: z.number().int().positive(),
notes: z.string().max(5000).optional(),
metadata: z.record(z.any()).optional(),
roles: z.array(roleSchema).min(1),
});
export const orderCreateSchema = z.object({
tenantId: z.string().uuid(),
businessId: z.string().uuid(),
vendorId: z.string().uuid().optional(),
orderNumber: z.string().min(1).max(80),
title: z.string().min(1).max(160),
description: z.string().max(5000).optional(),
status: z.enum(['DRAFT', 'OPEN', 'FILLED', 'ACTIVE', 'COMPLETED', 'CANCELLED']).optional(),
serviceType: z.enum(['EVENT', 'CATERING', 'HOTEL', 'RESTAURANT', 'OTHER']).optional(),
startsAt: z.string().datetime().optional(),
endsAt: z.string().datetime().optional(),
locationName: z.string().max(160).optional(),
locationAddress: z.string().max(300).optional(),
latitude: z.number().min(-90).max(90).optional(),
longitude: z.number().min(-180).max(180).optional(),
notes: z.string().max(5000).optional(),
metadata: z.record(z.any()).optional(),
shifts: z.array(shiftSchema).min(1),
});

View File

@@ -0,0 +1,35 @@
import { z } from 'zod';
const nullableString = (max) => z.union([z.string().max(max), z.null()]);
const nullableDateTime = z.union([z.string().datetime(), z.null()]);
const nullableUuid = z.union([z.string().uuid(), z.null()]);
const orderUpdateShape = {
orderId: z.string().uuid(),
tenantId: z.string().uuid(),
vendorId: nullableUuid.optional(),
title: nullableString(160).optional(),
description: nullableString(5000).optional(),
status: z.enum(['DRAFT', 'OPEN', 'FILLED', 'ACTIVE', 'COMPLETED']).optional(),
serviceType: z.enum(['EVENT', 'CATERING', 'HOTEL', 'RESTAURANT', 'OTHER']).optional(),
startsAt: nullableDateTime.optional(),
endsAt: nullableDateTime.optional(),
locationName: nullableString(160).optional(),
locationAddress: nullableString(300).optional(),
latitude: z.union([z.number().min(-90).max(90), z.null()]).optional(),
longitude: z.union([z.number().min(-180).max(180), z.null()]).optional(),
notes: nullableString(5000).optional(),
metadata: z.record(z.any()).optional(),
};
export const orderUpdateSchema = z.object(orderUpdateShape).superRefine((value, ctx) => {
const mutableKeys = Object.keys(orderUpdateShape).filter((key) => !['orderId', 'tenantId'].includes(key));
const hasMutableField = mutableKeys.some((key) => Object.prototype.hasOwnProperty.call(value, key));
if (!hasMutableField) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'At least one mutable order field must be provided',
path: [],
});
}
});

View File

@@ -0,0 +1,8 @@
import { z } from 'zod';
export const shiftAcceptSchema = z.object({
shiftId: z.string().uuid().optional(),
shiftRoleId: z.string().uuid(),
workforceId: z.string().uuid(),
metadata: z.record(z.any()).optional(),
});

View File

@@ -0,0 +1,10 @@
import { z } from 'zod';
export const shiftAssignStaffSchema = z.object({
shiftId: z.string().uuid(),
tenantId: z.string().uuid(),
shiftRoleId: z.string().uuid(),
workforceId: z.string().uuid(),
applicationId: z.string().uuid().optional(),
metadata: z.record(z.any()).optional(),
});

View File

@@ -0,0 +1,17 @@
import { z } from 'zod';
export const shiftStatusChangeSchema = z.object({
shiftId: z.string().uuid(),
tenantId: z.string().uuid(),
status: z.enum([
'DRAFT',
'OPEN',
'PENDING_CONFIRMATION',
'ASSIGNED',
'ACTIVE',
'COMPLETED',
'CANCELLED',
]),
reason: z.string().max(1000).optional(),
metadata: z.record(z.any()).optional(),
});

View File

@@ -0,0 +1,11 @@
import { z } from 'zod';
export const staffReviewSchema = z.object({
tenantId: z.string().uuid(),
businessId: z.string().uuid(),
staffId: z.string().uuid(),
assignmentId: z.string().uuid(),
rating: z.number().int().min(1).max(5),
reviewText: z.string().max(5000).optional(),
tags: z.array(z.string().min(1).max(80)).max(20).optional(),
});