import { Asset } from '@/types/asset-management'; import { AssetAssignment } from '@/types/asset-management'; import { RequestedAssetItem, AssetAvailabilityStatus } from '@/types/asset-request'; /** * Calculate available quantity for a specific asset type * @param assetType - The type of asset to check * @param allAssets - All assets in the system * @param activeAssignments - All active (not returned) assignments * @returns Available quantity */ export function calculateAvailableQuantity( assetType: string, allAssets: Asset[], activeAssignments: AssetAssignment[] ): number { // Count total assets of this type const totalAssets = allAssets.filter(asset => asset.assetType === assetType).length; // Count how many are currently assigned (active assignments only) const assignedCount = activeAssignments.filter(assignment => { const asset = allAssets.find(a => a.assetID === assignment.assetID); return asset && asset.assetType === assetType; }).length; // Calculate available const available = totalAssets - assignedCount; return Math.max(0, available); // Ensure non-negative } /** * Calculate availability status for multiple requested asset items * @param requestedAssets - List of requested asset items * @param allAssets - All assets in the system * @param activeAssignments - All active (not returned) assignments * @returns Array of availability status for each requested asset type */ export function calculateAvailabilityStatus( requestedAssets: RequestedAssetItem[], allAssets: Asset[], activeAssignments: AssetAssignment[] ): AssetAvailabilityStatus[] { return requestedAssets.map(requestedItem => { const available = calculateAvailableQuantity( requestedItem.assetType, allAssets, activeAssignments ); return { assetType: requestedItem.assetType, requested: requestedItem.quantity, available, canFulfill: available >= requestedItem.quantity }; }); } /** * Check if all requested assets can be fulfilled * @param requestedAssets - List of requested asset items * @param allAssets - All assets in the system * @param activeAssignments - All active (not returned) assignments * @returns True if all requested assets can be fulfilled */ export function canFulfillRequest( requestedAssets: RequestedAssetItem[], allAssets: Asset[], activeAssignments: AssetAssignment[] ): boolean { const availabilityStatus = calculateAvailabilityStatus( requestedAssets, allAssets, activeAssignments ); return availabilityStatus.every(status => status.canFulfill); } /** * Get available assets of a specific type (not currently assigned) * @param assetType - The type of asset to filter * @param allAssets - All assets in the system * @param activeAssignments - All active (not returned) assignments * @returns Array of available assets */ export function getAvailableAssetsByType( assetType: string, allAssets: Asset[], activeAssignments: AssetAssignment[] ): Asset[] { // Get all assets of this type const assetsOfType = allAssets.filter(asset => asset.assetType === assetType); // Get IDs of assets that are currently assigned const assignedAssetIds = new Set( activeAssignments.map(assignment => assignment.assetID) ); // Filter out assigned assets return assetsOfType.filter(asset => !assignedAssetIds.has(asset.assetID)); } /** * Check if inventory is low for a specific asset type * @param assetType - The type of asset to check * @param allAssets - All assets in the system * @param activeAssignments - All active (not returned) assignments * @param threshold - Threshold percentage (default 20%) * @returns True if inventory is below threshold */ export function isLowStock( assetType: string, allAssets: Asset[], activeAssignments: AssetAssignment[], threshold: number = 0.2 ): boolean { const totalAssets = allAssets.filter(asset => asset.assetType === assetType).length; // If no assets exist, it's definitely low stock if (totalAssets === 0) return true; const available = calculateAvailableQuantity(assetType, allAssets, activeAssignments); const availabilityRatio = available / totalAssets; return availabilityRatio <= threshold; } /** * Get inventory summary for all asset types * @param allAssets - All assets in the system * @param activeAssignments - All active (not returned) assignments * @returns Map of asset type to inventory summary */ export function getInventorySummary( allAssets: Asset[], activeAssignments: AssetAssignment[] ): Map { const summary = new Map(); // Get unique asset types const assetTypes = new Set(allAssets.map(asset => asset.assetType)); assetTypes.forEach(assetType => { const total = allAssets.filter(asset => asset.assetType === assetType).length; const available = calculateAvailableQuantity(assetType, allAssets, activeAssignments); const assigned = total - available; const lowStock = isLowStock(assetType, allAssets, activeAssignments); summary.set(assetType, { total, assigned, available, lowStock }); }); return summary; } /** * Validate that requested assets can be assigned without over-assignment * @param requestedAssets - List of requested asset items * @param allAssets - All assets in the system * @param activeAssignments - All active (not returned) assignments * @returns Validation result with error messages if any */ export function validateAssignmentAvailability( requestedAssets: RequestedAssetItem[], allAssets: Asset[], activeAssignments: AssetAssignment[] ): { valid: boolean; errors: string[] } { const errors: string[] = []; requestedAssets.forEach(requestedItem => { const available = calculateAvailableQuantity( requestedItem.assetType, allAssets, activeAssignments ); if (available < requestedItem.quantity) { errors.push( `Insufficient ${requestedItem.assetType}: requested ${requestedItem.quantity}, only ${available} available` ); } }); return { valid: errors.length === 0, errors }; } /** * Audit log entry for inventory changes */ export interface InventoryAuditLog { timestamp: string; action: 'assignment' | 'return' | 'adjustment'; assetType: string; assetId: number; assetCode: string; employeeId: number; employeeName: string; performedBy: number; previousState: 'available' | 'assigned'; newState: 'available' | 'assigned'; requestId?: number; remarks?: string; } /** * Create an audit log entry for an inventory change * @param params - Audit log parameters * @returns Audit log entry */ export function createInventoryAuditLog(params: { action: 'assignment' | 'return' | 'adjustment'; assetType: string; assetId: number; assetCode: string; employeeId: number; employeeName: string; performedBy: number; previousState: 'available' | 'assigned'; newState: 'available' | 'assigned'; requestId?: number; remarks?: string; }): InventoryAuditLog { return { timestamp: new Date().toISOString(), ...params }; } /** * Log inventory change to console (can be extended to send to backend) * @param log - Audit log entry */ export function logInventoryChange(log: InventoryAuditLog): void { console.log('[INVENTORY AUDIT]', { timestamp: log.timestamp, action: log.action, asset: `${log.assetType} (${log.assetCode})`, employee: `${log.employeeName} (ID: ${log.employeeId})`, stateChange: `${log.previousState} → ${log.newState}`, requestId: log.requestId, performedBy: log.performedBy, remarks: log.remarks }); } /** * Validate assignment request against current inventory * @param assignedAssets - Assets being assigned * @param allAssets - All assets in the system * @param activeAssignments - All active assignments * @returns Validation result with detailed errors */ export function validateAssignmentRequest( assignedAssets: Array<{ assetID: number; assetCode: string; assetType: string }>, allAssets: Asset[], activeAssignments: AssetAssignment[] ): { valid: boolean; errors: string[]; warnings: string[] } { const errors: string[] = []; const warnings: string[] = []; // Get currently assigned asset IDs const assignedAssetIds = new Set(activeAssignments.map(a => a.assetID)); // Validate each asset assignedAssets.forEach(assignedAsset => { // Check if asset exists const asset = allAssets.find(a => a.assetID === assignedAsset.assetID); if (!asset) { errors.push(`Asset ${assignedAsset.assetCode} (ID: ${assignedAsset.assetID}) does not exist`); return; } // Check if asset is already assigned if (assignedAssetIds.has(assignedAsset.assetID)) { errors.push(`Asset ${assignedAsset.assetCode} is already assigned to another employee`); return; } // Check asset condition if (asset.assetCondition === 'Damaged' || asset.assetCondition === 'Under Repair') { warnings.push( `Asset ${assignedAsset.assetCode} is in ${asset.assetCondition} condition. ` + 'Consider selecting a different asset.' ); } // Verify asset type matches if (asset.assetType !== assignedAsset.assetType) { errors.push( `Asset type mismatch for ${assignedAsset.assetCode}: ` + `expected ${assignedAsset.assetType}, found ${asset.assetType}` ); } }); return { valid: errors.length === 0, errors, warnings }; } /** * Calculate inventory impact of an assignment * @param assignedAssets - Assets being assigned * @param allAssets - All assets in the system * @param activeAssignments - All active assignments before the new assignment * @returns Inventory impact summary */ export function calculateInventoryImpact( assignedAssets: Array<{ assetType: string }>, allAssets: Asset[], activeAssignments: AssetAssignment[] ): Map { const impact = new Map(); // Group assigned assets by type const assignmentsByType = new Map(); assignedAssets.forEach(asset => { const count = assignmentsByType.get(asset.assetType) || 0; assignmentsByType.set(asset.assetType, count + 1); }); // Calculate impact for each asset type assignmentsByType.forEach((assignedCount, assetType) => { const totalAssets = allAssets.filter(a => a.assetType === assetType).length; const beforeAvailable = calculateAvailableQuantity(assetType, allAssets, activeAssignments); const afterAvailable = beforeAvailable - assignedCount; const percentageUsed = totalAssets > 0 ? ((totalAssets - afterAvailable) / totalAssets) * 100 : 0; impact.set(assetType, { before: beforeAvailable, after: afterAvailable, percentageUsed: Math.round(percentageUsed) }); }); return impact; }