pmtool / src /types /asset-management.ts
devarshia5's picture
Upload 487 files
d97b8f9 verified
Raw
History Blame Contribute Delete
23.3 kB
import { z } from 'zod';
// ============================================================================
// CORE ENTITY INTERFACES
// ============================================================================
/**
* Asset entity representing basic asset information
*/
export interface Asset {
assetID: number;
assetCode: string;
assetType: string;
brandModel: string;
serialNumber: string;
assetCondition: string;
}
/**
* System Details entity representing technical specifications linked to assets
*/
export interface SystemDetails {
systemDetailsID: number;
assetID: number;
systemModel: string;
processor: string;
installedRAM: string;
graphics: string;
screenResolution: string;
diskModel: string;
diskSize: string;
manufacturer: string;
}
/**
* Asset Assignment entity representing employee assignment tracking
*/
export interface AssetAssignment {
assignmentID: number;
assetID: number;
employeeID: number;
employeeName: string;
assetCode: string;
assignedDate: string; // ISO 8601 format
returnedDate: string | null; // ISO 8601 format
remarks: string;
}
/**
* Asset Item entity representing individual components/items attached to an asset
*/
export interface AssetItem {
itemID: number;
assetID: number;
itemType: string;
itemName: string;
quantity: number;
specifications: string;
serialNumber?: string;
condition: string;
notes?: string;
addedDate: string; // ISO 8601 format
}
// ============================================================================
// API REQUEST/RESPONSE TYPES
// ============================================================================
/**
* Request type for creating a new asset (excludes auto-generated ID)
*/
export interface AssetCreateRequest {
assetCode: string;
assetType: string;
brandModel: string;
serialNumber: string;
assetCondition: string;
purchaseDate?: string;
warrantyExpiry?: string;
purchaseCost?: number;
currentValue?: number;
location?: string;
notes?: string;
}
/**
* Request type for updating an existing asset
*/
export interface AssetUpdateRequest {
assetID: number;
assetCode: string;
assetType: string;
brandModel: string;
serialNumber: string;
assetCondition: string;
}
/**
* Request type for creating new system details (excludes auto-generated ID)
*/
export interface SystemDetailsCreateRequest {
assetID: number;
systemModel: string;
processor: string;
installedRAM: string;
graphics: string;
screenResolution: string;
diskModel: string;
diskSize: string;
manufacturer: string;
}
/**
* Request type for updating existing system details
*/
export interface SystemDetailsUpdateRequest {
systemDetailsID: number;
assetID: number;
systemModel: string;
processor: string;
installedRAM: string;
graphics: string;
screenResolution: string;
diskModel: string;
diskSize: string;
manufacturer: string;
}
/**
* Request type for creating a new asset assignment (excludes auto-generated ID)
*/
export interface AssetAssignmentCreateRequest {
assetID: number;
employeeID: number;
employeeName: string;
assetCode: string;
assignedDate: string; // ISO 8601 format
returnedDate: string | null; // ISO 8601 format
remarks: string;
}
/**
* Request type for updating an existing asset assignment
*/
export interface AssetAssignmentUpdateRequest {
assignmentID: number;
assetID: number;
employeeID: number;
employeeName: string;
assetCode: string;
assignedDate: string; // ISO 8601 format
returnedDate: string | null; // ISO 8601 format
remarks: string;
}
/**
* Request type for creating a new asset item (excludes auto-generated ID)
*/
export interface AssetItemCreateRequest {
assetID: number;
itemType: string;
itemName: string;
quantity: number;
specifications: string;
serialNumber?: string;
condition: string;
notes?: string;
addedDate: string; // ISO 8601 format
}
/**
* Request type for updating an existing asset item
*/
export interface AssetItemUpdateRequest {
itemID: number;
assetID: number;
itemType: string;
itemName: string;
quantity: number;
specifications: string;
serialNumber?: string;
condition: string;
notes?: string;
addedDate: string; // ISO 8601 format
}
// ============================================================================
// ENUM TYPES AND CONSTANTS
// ============================================================================
/**
* Valid asset condition values
*/
export type AssetCondition = 'Excellent' | 'Good' | 'Fair' | 'Poor';
/**
* Common asset types
*/
export type AssetType =
| 'Laptop'
| 'Desktop'
| 'Monitor'
| 'Keyboard'
| 'Mouse'
| 'Wired Mouse'
| 'Wireless Mouse'
| 'Laptop Stand'
| 'Extended Screen'
| 'Printer'
| 'Scanner'
| 'Tablet'
| 'Phone'
| 'Other';
/**
* Asset condition options for dropdowns
*/
export const ASSET_CONDITIONS: AssetCondition[] = ['Excellent', 'Good', 'Fair', 'Poor'];
/**
* Asset type options for dropdowns
*/
export const ASSET_TYPES: AssetType[] = [
'Laptop',
'Desktop',
'Monitor',
'Keyboard',
'Mouse',
'Wired Mouse',
'Wireless Mouse',
'Laptop Stand',
'Extended Screen',
'Printer',
'Scanner',
'Tablet',
'Phone',
'Other'
];
/**
* Asset item types
*/
export type AssetItemType =
| 'RAM'
| 'Storage'
| 'Battery'
| 'Charger'
| 'Cable'
| 'Adapter'
| 'Keyboard'
| 'Mouse'
| 'Webcam'
| 'Headset'
| 'Docking Station'
| 'Monitor'
| 'Other';
/**
* Asset item type options for dropdowns
*/
export const ASSET_ITEM_TYPES: AssetItemType[] = [
'RAM',
'Storage',
'Battery',
'Charger',
'Cable',
'Adapter',
'Keyboard',
'Mouse',
'Webcam',
'Headset',
'Docking Station',
'Monitor',
'Other'
];
// ============================================================================
// ZOD VALIDATION SCHEMAS
// ============================================================================
/**
* Zod schema for Asset validation
*/
export const AssetSchema = z.object({
assetID: z.number().int().positive().optional(),
assetCode: z.string()
.min(1, 'Asset code is required')
.max(20, 'Asset code must be 20 characters or less')
.refine(val => val.trim().length > 0, {
message: 'Asset code cannot be empty or contain only spaces'
})
.refine(val => /^[A-Za-z0-9\-_]+$/.test(val.trim()), {
message: 'Asset code can only contain letters, numbers, hyphens, and underscores'
}),
assetType: z.string()
.min(1, 'Asset type is required')
.max(50, 'Asset type must be 50 characters or less')
.refine(val => ASSET_TYPES.includes(val as AssetType), {
message: 'Please select a valid asset type from the list'
}),
brandModel: z.string()
.min(1, 'Brand model is required')
.max(100, 'Brand model must be 100 characters or less')
.refine(val => val.trim().length > 0, {
message: 'Brand model cannot be empty or contain only spaces'
}),
serialNumber: z.string()
.min(1, 'Serial number is required')
.max(50, 'Serial number must be 50 characters or less')
.refine(val => val.trim().length > 0, {
message: 'Serial number cannot be empty or contain only spaces'
})
.refine(val => /^[A-Za-z0-9\-_]+$/.test(val.trim()), {
message: 'Serial number can only contain letters, numbers, hyphens, and underscores'
}),
assetCondition: z.enum(['Excellent', 'Good', 'Fair', 'Poor'], {
errorMap: () => ({ message: 'Asset condition must be Excellent, Good, Fair, or Poor' })
}),
});
/**
* Zod schema for Asset creation (excludes ID)
*/
export const AssetCreateSchema = AssetSchema.omit({ assetID: true });
/**
* Zod schema for Asset updates (requires ID and assetCode)
*/
export const AssetUpdateSchema = AssetSchema.required({ assetID: true, assetCode: true });
/**
* Zod schema for System Details validation
*/
export const SystemDetailsSchema = z.object({
systemDetailsID: z.number().int().positive().optional(),
assetID: z.number().int().positive('Asset ID must be a positive number'),
systemModel: z.string()
.min(1, 'System model is required')
.max(100, 'System model must be 100 characters or less')
.refine(val => val.trim().length > 0, {
message: 'System model cannot be empty or contain only spaces'
}),
processor: z.string()
.min(1, 'Processor information is required')
.max(100, 'Processor must be 100 characters or less')
.refine(val => val.trim().length > 0, {
message: 'Processor information cannot be empty or contain only spaces'
}),
installedRAM: z.string()
.min(1, 'RAM information is required')
.max(50, 'RAM must be 50 characters or less')
.refine(val => val.trim().length > 0, {
message: 'RAM information cannot be empty or contain only spaces'
}),
graphics: z.string()
.min(1, 'Graphics information is required')
.max(100, 'Graphics must be 100 characters or less')
.refine(val => val.trim().length > 0, {
message: 'Graphics information cannot be empty or contain only spaces'
}),
screenResolution: z.string()
.min(1, 'Screen resolution is required')
.max(50, 'Screen resolution must be 50 characters or less')
.refine(val => val.trim().length > 0, {
message: 'Screen resolution cannot be empty or contain only spaces'
})
.refine(val => /^\d+x\d+$/.test(val.trim()) || val === 'custom', {
message: 'Screen resolution must be in format "1920x1080" or select from the dropdown'
}),
diskModel: z.string()
.min(1, 'Disk model is required')
.max(100, 'Disk model must be 100 characters or less')
.refine(val => val.trim().length > 0, {
message: 'Disk model cannot be empty or contain only spaces'
}),
diskSize: z.string()
.min(1, 'Disk size is required')
.max(50, 'Disk size must be 50 characters or less')
.refine(val => val.trim().length > 0, {
message: 'Disk size cannot be empty or contain only spaces'
}),
manufacturer: z.string()
.min(1, 'Manufacturer is required')
.max(100, 'Manufacturer must be 100 characters or less')
.refine(val => val.trim().length > 0, {
message: 'Manufacturer cannot be empty or contain only spaces'
}),
});
/**
* Zod schema for System Details creation (excludes ID)
*/
export const SystemDetailsCreateSchema = SystemDetailsSchema.omit({ systemDetailsID: true });
/**
* Zod schema for System Details updates (requires ID)
*/
export const SystemDetailsUpdateSchema = SystemDetailsSchema.required({ systemDetailsID: true });
/**
* Base Zod schema for Asset Assignment validation (without refinements)
*/
const AssetAssignmentBaseSchema = z.object({
assignmentID: z.number().int().positive().optional(),
assetID: z.number().int().positive('Asset ID must be a positive number'),
employeeID: z.number().int().positive('Employee ID must be a positive number'),
employeeName: z.string()
.min(1, 'Employee name is required')
.max(100, 'Employee name must be 100 characters or less')
.refine(val => val.trim().length > 0, {
message: 'Employee name cannot be empty or contain only spaces'
}),
assetCode: z.string()
.min(1, 'Asset code is required')
.max(20, 'Asset code must be 20 characters or less')
.refine(val => val.trim().length > 0, {
message: 'Asset code cannot be empty or contain only spaces'
}),
assignedDate: z.string().datetime('Assigned date must be in ISO 8601 format'),
returnedDate: z.string().datetime('Returned date must be in ISO 8601 format').nullable(),
remarks: z.string()
.min(1, 'Remarks are required')
.max(500, 'Remarks must be 500 characters or less')
.refine(val => val.trim().length > 0, {
message: 'Remarks cannot be empty or contain only spaces'
}),
});
/**
* Zod schema for Asset Assignment validation (with date validation refinement)
*/
export const AssetAssignmentSchema = AssetAssignmentBaseSchema.refine(data => {
// If both dates are provided, returned date must be after assigned date
if (data.returnedDate && data.assignedDate) {
const assignedDate = new Date(data.assignedDate);
const returnedDate = new Date(data.returnedDate);
return returnedDate > assignedDate;
}
return true;
}, {
message: 'Returned date must be after the assigned date',
path: ['returnedDate']
});
/**
* Zod schema for Asset Assignment creation (excludes ID)
*/
export const AssetAssignmentCreateSchema = AssetAssignmentBaseSchema.omit({ assignmentID: true }).refine(data => {
// If both dates are provided, returned date must be after assigned date
if (data.returnedDate && data.assignedDate) {
const assignedDate = new Date(data.assignedDate);
const returnedDate = new Date(data.returnedDate);
return returnedDate > assignedDate;
}
return true;
}, {
message: 'Returned date must be after the assigned date',
path: ['returnedDate']
});
/**
* Zod schema for Asset Assignment updates (requires ID)
*/
export const AssetAssignmentUpdateSchema = AssetAssignmentBaseSchema.required({ assignmentID: true }).refine(data => {
// If both dates are provided, returned date must be after assigned date
if (data.returnedDate && data.assignedDate) {
const assignedDate = new Date(data.assignedDate);
const returnedDate = new Date(data.returnedDate);
return returnedDate > assignedDate;
}
return true;
}, {
message: 'Returned date must be after the assigned date',
path: ['returnedDate']
});
/**
* Zod schema for Asset Assignment form editing (excludes ID for form validation)
* Uses Date objects instead of strings for form compatibility
*/
export const AssetAssignmentFormSchema = z.object({
assetID: z.number().int().positive('Asset ID must be a positive number'),
employeeID: z.number().int().positive('Employee ID must be a positive number'),
employeeName: z.string()
.min(1, 'Employee name is required')
.max(100, 'Employee name must be 100 characters or less')
.refine(val => val.trim().length > 0, {
message: 'Employee name cannot be empty or contain only spaces'
}),
assetCode: z.string()
.min(1, 'Asset code is required')
.max(20, 'Asset code must be 20 characters or less')
.refine(val => val.trim().length > 0, {
message: 'Asset code cannot be empty or contain only spaces'
}),
assignedDate: z.date({
required_error: 'Assigned date is required',
invalid_type_error: 'Assigned date must be a valid date'
}),
returnedDate: z.date({
invalid_type_error: 'Returned date must be a valid date'
}).nullable(),
remarks: z.string()
.min(1, 'Remarks are required')
.max(500, 'Remarks must be 500 characters or less')
.refine(val => val.trim().length > 0, {
message: 'Remarks cannot be empty or contain only spaces'
}),
}).refine(data => {
// If both dates are provided, returned date must be after assigned date
if (data.returnedDate && data.assignedDate) {
return data.returnedDate > data.assignedDate;
}
return true;
}, {
message: 'Returned date must be after the assigned date',
path: ['returnedDate']
});
/**
* Zod schema for Asset Item validation
*/
export const AssetItemSchema = z.object({
itemID: z.number().int().positive().optional(),
assetID: z.number().int().positive('Asset ID must be a positive number'),
itemType: z.string()
.min(1, 'Item type is required')
.max(50, 'Item type must be 50 characters or less'),
itemName: z.string()
.min(1, 'Item name is required')
.max(100, 'Item name must be 100 characters or less')
.refine(val => val.trim().length > 0, {
message: 'Item name cannot be empty or contain only spaces'
}),
quantity: z.number().int().positive('Quantity must be a positive number').min(1, 'Quantity must be at least 1'),
specifications: z.string()
.min(1, 'Specifications are required')
.max(200, 'Specifications must be 200 characters or less')
.refine(val => val.trim().length > 0, {
message: 'Specifications cannot be empty or contain only spaces'
}),
serialNumber: z.string()
.max(50, 'Serial number must be 50 characters or less')
.optional(),
condition: z.enum(['Excellent', 'Good', 'Fair', 'Poor'], {
errorMap: () => ({ message: 'Condition must be Excellent, Good, Fair, or Poor' })
}),
notes: z.string()
.max(500, 'Notes must be 500 characters or less')
.optional(),
addedDate: z.string().datetime('Added date must be in ISO 8601 format'),
});
/**
* Zod schema for Asset Item creation (excludes ID)
*/
export const AssetItemCreateSchema = AssetItemSchema.omit({ itemID: true });
/**
* Zod schema for Asset Item updates (requires ID)
*/
export const AssetItemUpdateSchema = AssetItemSchema.required({ itemID: true });
// ============================================================================
// UTILITY TYPES AND INTERFACES
// ============================================================================
/**
* Generic API response wrapper
*/
export interface AssetManagementApiResponse<T> {
success: boolean;
data?: T;
message?: string;
error?: string;
}
/**
* Extended Asset interface with related data for detailed views
*/
export interface AssetWithDetails extends Asset {
systemDetails?: SystemDetails;
assignments?: AssetAssignment[];
currentAssignment?: AssetAssignment;
items?: AssetItem[];
}
/**
* Extended System Details interface with asset information
*/
export interface SystemDetailsWithAsset extends SystemDetails {
asset?: Asset;
}
/**
* Extended Asset Assignment interface with related entity information
*/
export interface AssetAssignmentWithDetails extends AssetAssignment {
asset?: Asset;
employeeEmail?: string;
}
/**
* Filter options for asset listing
*/
export interface AssetFilter {
assetType?: string;
assetCondition?: AssetCondition;
searchTerm?: string;
assignmentStatus?: 'assigned' | 'unassigned' | 'all';
}
/**
* Sort options for asset listing
*/
export interface AssetSort {
field: keyof Asset;
direction: 'asc' | 'desc';
}
/**
* Pagination options
*/
export interface PaginationOptions {
page: number;
limit: number;
}
/**
* Paginated response wrapper
*/
export interface PaginatedResponse<T> {
data: T[];
total: number;
page: number;
limit: number;
totalPages: number;
}
/**
* Bulk import response interface
*/
export interface BulkImportResponse {
success: boolean;
message: string;
results: {
successful: number;
failed: number;
total: number;
errors: ImportError[];
createdAssets: Asset[];
};
}
/**
* Import error interface for bulk operations
*/
export interface ImportError {
index: number;
asset: AssetCreateRequest;
error: string;
field?: string;
}
// ============================================================================
// TYPE GUARDS AND VALIDATION HELPERS
// ============================================================================
/**
* Type guard to check if an object is a valid Asset
*/
export function isAsset(obj: any): obj is Asset {
return AssetSchema.safeParse(obj).success;
}
/**
* Type guard to check if an object is a valid SystemDetails
*/
export function isSystemDetails(obj: any): obj is SystemDetails {
return SystemDetailsSchema.safeParse(obj).success;
}
/**
* Type guard to check if an object is a valid AssetAssignment
*/
export function isAssetAssignment(obj: any): obj is AssetAssignment {
return AssetAssignmentSchema.safeParse(obj).success;
}
/**
* Validates and transforms API response data to ensure type safety
*/
export function validateAssetApiResponse(data: any): Asset {
// For API responses, we expect the ID to be present
const schemaWithRequiredId = AssetSchema.required({ assetID: true });
const result = schemaWithRequiredId.safeParse(data);
if (!result.success) {
console.error('Asset validation failed:', result.error);
throw new Error(`Invalid asset data: ${result.error.message}`);
}
return result.data as Asset;
}
/**
* Validates and transforms System Details API response data
*/
export function validateSystemDetailsApiResponse(data: any): SystemDetails {
// For API responses, we expect the ID to be present
const schemaWithRequiredId = SystemDetailsSchema.required({ systemDetailsID: true });
const result = schemaWithRequiredId.safeParse(data);
if (!result.success) {
console.error('System Details validation failed:', result.error);
throw new Error(`Invalid system details data: ${result.error.message}`);
}
return result.data as SystemDetails;
}
/**
* Validates and transforms Asset Assignment API response data
*/
export function validateAssetAssignmentApiResponse(data: any): AssetAssignment {
// For API responses, we expect the ID to be present
const result = AssetAssignmentSchema.safeParse(data);
if (!result.success) {
console.error('Asset Assignment validation failed:', result.error);
throw new Error(`Invalid asset assignment data: ${result.error.message}`);
}
return result.data as AssetAssignment;
}
// ============================================================================
// FORM VALIDATION HELPERS
// ============================================================================
/**
* Helper to get validation errors for Asset forms
*/
export function getAssetValidationErrors(data: any): Record<string, string> {
const result = AssetSchema.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 System Details forms
*/
export function getSystemDetailsValidationErrors(data: any): Record<string, string> {
const result = SystemDetailsSchema.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 Assignment forms
*/
export function getAssetAssignmentValidationErrors(data: any): Record<string, string> {
const result = AssetAssignmentSchema.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;
}