Spaces:
Sleeping
Sleeping
File size: 11,564 Bytes
d97b8f9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 | 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;
}
|