Spaces:
Sleeping
Sleeping
| 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<string, { total: number; assigned: number; available: number; lowStock: boolean }> { | |
| const summary = new Map<string, { total: number; assigned: number; available: number; lowStock: boolean }>(); | |
| // 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<string, { before: number; after: number; percentageUsed: number }> { | |
| const impact = new Map<string, { before: number; after: number; percentageUsed: number }>(); | |
| // Group assigned assets by type | |
| const assignmentsByType = new Map<string, number>(); | |
| 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; | |
| } | |