Spaces:
Sleeping
Sleeping
| import { z } from 'zod'; | |
| import { ASSET_TYPES, AssetType } from './asset-management'; | |
| // ============================================================================ | |
| // CORE ENTITY INTERFACES | |
| // ============================================================================ | |
| /** | |
| * Request status type representing the lifecycle of an asset request | |
| */ | |
| export type RequestStatus = 'pending' | 'approved' | 'rejected' | 'assigned' | 'acknowledged'; | |
| /** | |
| * Requested Asset Item entity representing individual items in a request | |
| */ | |
| export interface RequestedAssetItem { | |
| assetType: string; | |
| quantity: number; | |
| specifications: string; | |
| } | |
| /** | |
| * Asset Request entity representing an employee's request for assets | |
| */ | |
| export interface AssetRequest { | |
| requestID: number; | |
| employeeID: number; | |
| employeeName: string; | |
| requestedAssets: RequestedAssetItem[]; | |
| justification: string; | |
| status: RequestStatus; | |
| submittedDate: string; // ISO 8601 | |
| reviewedDate: string | null; // ISO 8601 | |
| reviewedBy: number | null; // Admin employee ID | |
| reviewerName: string | null; | |
| adminComments: string | null; | |
| assignedDate: string | null; // ISO 8601 | |
| assignedBy: number | null; // Admin employee ID | |
| assignerName: string | null; | |
| acknowledgedDate: string | null; // ISO 8601 | |
| acknowledgedBy: number | null; // Employee ID who acknowledged | |
| acknowledgementComments: string | null; | |
| } | |
| /** | |
| * Assigned Asset Detail entity for tracking specific assets assigned to a request | |
| */ | |
| export interface AssignedAssetDetail { | |
| assetID: number; | |
| assetCode: string; | |
| assetType: string; | |
| quantity: number; | |
| } | |
| // ============================================================================ | |
| // API REQUEST/RESPONSE TYPES | |
| // ============================================================================ | |
| /** | |
| * Request type for creating a new asset request (excludes auto-generated fields) | |
| */ | |
| export interface AssetRequestCreateRequest { | |
| employeeID: number; | |
| requestedAssets: RequestedAssetItem[]; | |
| justification: string; | |
| } | |
| /** | |
| * Request type for approving an asset request | |
| */ | |
| export interface AssetRequestApprovalRequest { | |
| adminComments: string; | |
| reviewedBy: number; | |
| } | |
| /** | |
| * Request type for rejecting an asset request | |
| */ | |
| export interface AssetRequestRejectionRequest { | |
| adminComments: string; | |
| reviewedBy: number; | |
| } | |
| /** | |
| * Request type for assigning assets to an approved request | |
| */ | |
| export interface AssetRequestAssignmentRequest { | |
| assignedAssets: AssignedAssetDetail[]; | |
| assignedBy: number; | |
| } | |
| /** | |
| * Request type for adding admin comments without changing status | |
| */ | |
| export interface AssetRequestCommentRequest { | |
| adminComments: string; | |
| commentedBy: number; | |
| } | |
| /** | |
| * Availability status for a requested asset type | |
| */ | |
| export interface AssetAvailabilityStatus { | |
| assetType: string; | |
| requested: number; | |
| available: number; | |
| canFulfill: boolean; | |
| } | |
| /** | |
| * Extended Asset Request with availability information | |
| */ | |
| export interface AssetRequestWithAvailability extends AssetRequest { | |
| availabilityStatus: AssetAvailabilityStatus[]; | |
| } | |
| /** | |
| * Availability check result for requested assets | |
| */ | |
| export interface AvailabilityCheck { | |
| canFulfill: boolean; | |
| details: { | |
| assetType: string; | |
| requested: number; | |
| available: number; | |
| shortfall: number; | |
| }[]; | |
| } | |
| /** | |
| * Filter options for asset request listing | |
| */ | |
| export interface RequestFilters { | |
| status?: RequestStatus; | |
| employeeId?: number; | |
| dateFrom?: string; | |
| dateTo?: string; | |
| } | |
| // ============================================================================ | |
| // ZOD VALIDATION SCHEMAS | |
| // ============================================================================ | |
| /** | |
| * Zod schema for RequestedAssetItem validation | |
| */ | |
| export const RequestedAssetItemSchema = z.object({ | |
| assetType: z.string() | |
| .min(1, 'Asset type is required') | |
| .refine(val => ASSET_TYPES.includes(val as AssetType), { | |
| message: 'Please select a valid asset type' | |
| }), | |
| quantity: z.number() | |
| .int('Quantity must be a whole number') | |
| .positive('Quantity must be positive') | |
| .max(10, 'Cannot request more than 10 of the same item'), | |
| specifications: z.string() | |
| .max(500, 'Specifications must be 500 characters or less') | |
| .optional() | |
| .default('') | |
| }); | |
| /** | |
| * Zod schema for Asset Request creation | |
| */ | |
| export const AssetRequestCreateSchema = z.object({ | |
| employeeID: z.number() | |
| .int('Employee ID must be a whole number') | |
| .positive('Employee ID must be positive'), | |
| requestedAssets: z.array(RequestedAssetItemSchema) | |
| .min(1, 'At least one asset must be requested') | |
| .max(20, 'Cannot request more than 20 items at once'), | |
| justification: z.string() | |
| .min(10, 'Please provide at least 10 characters of justification') | |
| .max(1000, 'Justification must be 1000 characters or less') | |
| .refine(val => val.trim().length >= 10, { | |
| message: 'Justification cannot be empty or contain only spaces' | |
| }) | |
| }); | |
| /** | |
| * Zod schema for Asset Request approval | |
| */ | |
| export const AssetRequestApprovalSchema = z.object({ | |
| adminComments: z.string() | |
| .min(5, 'Please provide comments for your decision') | |
| .max(1000, 'Comments must be 1000 characters or less') | |
| .refine(val => val.trim().length >= 5, { | |
| message: 'Comments cannot be empty or contain only spaces' | |
| }), | |
| reviewedBy: z.number() | |
| .int('Reviewer ID must be a whole number') | |
| .positive('Reviewer ID must be positive') | |
| }); | |
| /** | |
| * Zod schema for Asset Request rejection | |
| */ | |
| export const AssetRequestRejectionSchema = z.object({ | |
| adminComments: z.string() | |
| .min(5, 'Please provide comments for your decision') | |
| .max(1000, 'Comments must be 1000 characters or less') | |
| .refine(val => val.trim().length >= 5, { | |
| message: 'Comments cannot be empty or contain only spaces' | |
| }), | |
| reviewedBy: z.number() | |
| .int('Reviewer ID must be a whole number') | |
| .positive('Reviewer ID must be positive') | |
| }); | |
| /** | |
| * Zod schema for Assigned Asset Detail | |
| */ | |
| export const AssignedAssetDetailSchema = z.object({ | |
| assetID: z.number() | |
| .int('Asset ID must be a whole number') | |
| .positive('Asset ID must be positive'), | |
| assetCode: z.string() | |
| .min(1, 'Asset code is required') | |
| .max(20, 'Asset code must be 20 characters or less'), | |
| assetType: z.string() | |
| .min(1, 'Asset type is required') | |
| .max(50, 'Asset type must be 50 characters or less'), | |
| quantity: z.number() | |
| .int('Quantity must be a whole number') | |
| .positive('Quantity must be positive') | |
| }); | |
| /** | |
| * Zod schema for Asset Request assignment | |
| */ | |
| export const AssetRequestAssignmentSchema = z.object({ | |
| assignedAssets: z.array(AssignedAssetDetailSchema) | |
| .min(1, 'At least one asset must be assigned'), | |
| assignedBy: z.number() | |
| .int('Assigner ID must be a whole number') | |
| .positive('Assigner ID must be positive') | |
| }); | |
| /** | |
| * Zod schema for full Asset Request validation | |
| */ | |
| export const AssetRequestSchema = z.object({ | |
| requestID: z.number() | |
| .int('Request ID must be a whole number') | |
| .positive('Request ID must be positive'), | |
| employeeID: z.number() | |
| .int('Employee ID must be a whole number') | |
| .positive('Employee ID must be positive'), | |
| employeeName: z.string() | |
| .min(1, 'Employee name is required') | |
| .max(200, 'Employee name must be 200 characters or less'), | |
| requestedAssets: z.array(RequestedAssetItemSchema) | |
| .min(1, 'At least one asset must be requested'), | |
| justification: z.string() | |
| .min(10, 'Justification must be at least 10 characters') | |
| .max(1000, 'Justification must be 1000 characters or less'), | |
| status: z.enum(['pending', 'approved', 'rejected', 'assigned', 'acknowledged'], { | |
| errorMap: () => ({ message: 'Status must be pending, approved, rejected, assigned, or acknowledged' }) | |
| }), | |
| submittedDate: z.string().datetime('Submitted date must be in ISO 8601 format'), | |
| reviewedDate: z.string().datetime('Reviewed date must be in ISO 8601 format').nullable(), | |
| reviewedBy: z.number() | |
| .int('Reviewer ID must be a whole number') | |
| .positive('Reviewer ID must be positive') | |
| .nullable(), | |
| reviewerName: z.string() | |
| .max(200, 'Reviewer name must be 200 characters or less') | |
| .nullable(), | |
| adminComments: z.string() | |
| .max(1000, 'Admin comments must be 1000 characters or less') | |
| .nullable(), | |
| assignedDate: z.string().datetime('Assigned date must be in ISO 8601 format').nullable(), | |
| assignedBy: z.number() | |
| .int('Assigner ID must be a whole number') | |
| .positive('Assigner ID must be positive') | |
| .nullable(), | |
| assignerName: z.string() | |
| .max(200, 'Assigner name must be 200 characters or less') | |
| .nullable(), | |
| acknowledgedDate: z.string().datetime('Acknowledged date must be in ISO 8601 format').nullable(), | |
| acknowledgedBy: z.number() | |
| .int('Acknowledger ID must be a whole number') | |
| .positive('Acknowledger ID must be positive') | |
| .nullable(), | |
| acknowledgementComments: z.string() | |
| .max(1000, 'Acknowledgement comments must be 1000 characters or less') | |
| .nullable() | |
| }); | |
| // ============================================================================ | |
| // TYPE GUARDS AND VALIDATION HELPERS | |
| // ============================================================================ | |
| /** | |
| * Type guard to check if an object is a valid RequestedAssetItem | |
| */ | |
| export function isRequestedAssetItem(obj: any): obj is RequestedAssetItem { | |
| return RequestedAssetItemSchema.safeParse(obj).success; | |
| } | |
| /** | |
| * Type guard to check if an object is a valid AssetRequest | |
| */ | |
| export function isAssetRequest(obj: any): obj is AssetRequest { | |
| return AssetRequestSchema.safeParse(obj).success; | |
| } | |
| /** | |
| * Type guard to check if a status is valid | |
| */ | |
| export function isValidRequestStatus(status: string): status is RequestStatus { | |
| return ['pending', 'approved', 'rejected', 'assigned', 'acknowledged'].includes(status); | |
| } | |
| /** | |
| * Validates status transition rules | |
| * @param currentStatus - The current status of the request | |
| * @param newStatus - The desired new status | |
| * @returns true if the transition is valid, false otherwise | |
| */ | |
| export function isValidStatusTransition( | |
| currentStatus: RequestStatus, | |
| newStatus: RequestStatus | |
| ): boolean { | |
| const validTransitions: Record<RequestStatus, RequestStatus[]> = { | |
| pending: ['approved', 'rejected'], | |
| approved: ['assigned'], | |
| rejected: [], // Cannot transition from rejected | |
| assigned: ['acknowledged'], // Can be acknowledged by employee | |
| acknowledged: [] // Cannot transition from acknowledged | |
| }; | |
| return validTransitions[currentStatus]?.includes(newStatus) ?? false; | |
| } | |
| /** | |
| * Validates and transforms API response data to ensure type safety | |
| */ | |
| export function validateAssetRequestApiResponse(data: any): AssetRequest { | |
| const result = AssetRequestSchema.safeParse(data); | |
| if (!result.success) { | |
| console.error('Asset Request validation failed:', result.error); | |
| throw new Error(`Invalid asset request data: ${result.error.message}`); | |
| } | |
| return result.data as AssetRequest; | |
| } | |
| /** | |
| * Validates asset request creation data | |
| */ | |
| export function validateAssetRequestCreate(data: any): AssetRequestCreateRequest { | |
| const result = AssetRequestCreateSchema.safeParse(data); | |
| if (!result.success) { | |
| console.error('Asset Request creation validation failed:', result.error); | |
| throw new Error(`Invalid asset request creation data: ${result.error.message}`); | |
| } | |
| return result.data as AssetRequestCreateRequest; | |
| } | |
| /** | |
| * Validates asset request approval data | |
| */ | |
| export function validateAssetRequestApproval(data: any): AssetRequestApprovalRequest { | |
| const result = AssetRequestApprovalSchema.safeParse(data); | |
| if (!result.success) { | |
| console.error('Asset Request approval validation failed:', result.error); | |
| throw new Error(`Invalid approval data: ${result.error.message}`); | |
| } | |
| return result.data as AssetRequestApprovalRequest; | |
| } | |
| /** | |
| * Validates asset request rejection data | |
| */ | |
| export function validateAssetRequestRejection(data: any): AssetRequestRejectionRequest { | |
| const result = AssetRequestRejectionSchema.safeParse(data); | |
| if (!result.success) { | |
| console.error('Asset Request rejection validation failed:', result.error); | |
| throw new Error(`Invalid rejection data: ${result.error.message}`); | |
| } | |
| return result.data as AssetRequestRejectionRequest; | |
| } | |
| /** | |
| * Validates asset request assignment data | |
| */ | |
| export function validateAssetRequestAssignment(data: any): AssetRequestAssignmentRequest { | |
| const result = AssetRequestAssignmentSchema.safeParse(data); | |
| if (!result.success) { | |
| console.error('Asset Request assignment validation failed:', result.error); | |
| throw new Error(`Invalid assignment data: ${result.error.message}`); | |
| } | |
| return result.data as AssetRequestAssignmentRequest; | |
| } | |
| // ============================================================================ | |
| // FORM VALIDATION HELPERS | |
| // ============================================================================ | |
| /** | |
| * Helper to get validation errors for Asset Request creation forms | |
| */ | |
| export function getAssetRequestCreateValidationErrors(data: any): Record<string, string> { | |
| const result = AssetRequestCreateSchema.safeParse(data); | |
| if (result.success) return {}; | |
| const errors: Record<string, string> = {}; | |
| result.error.errors.forEach(error => { | |
| const field = error.path.join('.'); | |
| errors[field] = error.message; | |
| }); | |
| return errors; | |
| } | |
| /** | |
| * Helper to get validation errors for Asset Request approval forms | |
| */ | |
| export function getAssetRequestApprovalValidationErrors(data: any): Record<string, string> { | |
| const result = AssetRequestApprovalSchema.safeParse(data); | |
| if (result.success) return {}; | |
| const errors: Record<string, string> = {}; | |
| result.error.errors.forEach(error => { | |
| const field = error.path.join('.'); | |
| errors[field] = error.message; | |
| }); | |
| return errors; | |
| } | |
| /** | |
| * Helper to get validation errors for Asset Request assignment forms | |
| */ | |
| export function getAssetRequestAssignmentValidationErrors(data: any): Record<string, string> { | |
| const result = AssetRequestAssignmentSchema.safeParse(data); | |
| if (result.success) return {}; | |
| const errors: Record<string, string> = {}; | |
| result.error.errors.forEach(error => { | |
| const field = error.path.join('.'); | |
| errors[field] = error.message; | |
| }); | |
| return errors; | |
| } | |
| // ============================================================================ | |
| // UTILITY CONSTANTS | |
| // ============================================================================ | |
| /** | |
| * Request status display labels | |
| */ | |
| export const REQUEST_STATUS_LABELS: Record<RequestStatus, string> = { | |
| pending: 'Pending Review', | |
| approved: 'Approved', | |
| rejected: 'Rejected', | |
| assigned: 'Assigned', | |
| acknowledged: 'Acknowledged' | |
| }; | |
| /** | |
| * Request status colors for UI display | |
| */ | |
| export const REQUEST_STATUS_COLORS: Record<RequestStatus, string> = { | |
| pending: 'warning', | |
| approved: 'success', | |
| rejected: 'error', | |
| assigned: 'info', | |
| acknowledged: 'success' | |
| }; | |
| /** | |
| * Configuration constants for asset requests | |
| */ | |
| export const ASSET_REQUEST_CONFIG = { | |
| MAX_ITEMS_PER_REQUEST: 20, | |
| MAX_QUANTITY_PER_ITEM: 10, | |
| MIN_JUSTIFICATION_LENGTH: 10, | |
| MAX_JUSTIFICATION_LENGTH: 1000, | |
| MIN_ADMIN_COMMENTS_LENGTH: 5, | |
| MAX_ADMIN_COMMENTS_LENGTH: 1000, | |
| MAX_SPECIFICATIONS_LENGTH: 500 | |
| } as const; | |