pmtool / src /types /asset-request.ts
devarshia5's picture
Upload 487 files
d97b8f9 verified
Raw
History Blame Contribute Delete
15.7 kB
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;