pmtool / src /services /assetAssignmentApi.ts
devarshia5's picture
Upload 487 files
d97b8f9 verified
Raw
History Blame Contribute Delete
23.8 kB
import apiClient from '@/lib/api/axios-instance';
import { AxiosError } from 'axios';
import {
AssetAssignment,
AssetAssignmentCreateRequest,
AssetAssignmentUpdateRequest,
validateAssetAssignmentApiResponse
} from '@/types/asset-management';
import { assetApi } from './assetApi';
import { employeeApi } from './employeeApi';
// Helper to create a safe asset assignment object with default values for null/undefined fields
const createSafeAssetAssignment = (data: any): AssetAssignment => {
return {
assignmentID: data.assignmentID || 0,
assetID: data.assetID || 0,
employeeID: data.employeeID || 0,
employeeName: data.employeeName || '',
assetCode: data.assetCode || '',
assignedDate: data.assignedDate || new Date().toISOString(),
returnedDate: data.returnedDate || null,
remarks: data.remarks || '',
};
};
// Helper to ensure dates are in ISO 8601 format
const formatDateToISO = (dateString: string): string => {
if (!dateString) return new Date().toISOString();
// If already in ISO format, return as is
if (dateString.includes('T') && dateString.includes('Z')) {
return dateString;
}
// If it's a date-only string, add time component
if (dateString.match(/^\d{4}-\d{2}-\d{2}$/)) {
return `${dateString}T12:00:00.000Z`;
}
// Try to parse and convert to ISO
try {
return new Date(dateString).toISOString();
} catch (error) {
console.warn('AssetAssignmentApi: Invalid date format, using current date:', dateString);
return new Date().toISOString();
}
};
// Helper to handle API errors with user-friendly messages
const handleApiError = (error: unknown, operation: string): never => {
console.error(`AssetAssignmentApi: Error during ${operation}:`, error);
if (error instanceof AxiosError) {
const status = error.response?.status;
const message = error.response?.data?.message || error.message;
switch (status) {
case 400:
throw new Error(`Invalid request: ${message}`);
case 401:
throw new Error('You are not authorized to perform this action. Please log in again.');
case 403:
throw new Error('You do not have permission to perform this action.');
case 404:
throw new Error('The requested assignment was not found.');
case 409:
throw new Error('This assignment conflicts with existing data or the asset is already assigned.');
case 422:
throw new Error(`Validation failed: ${message}`);
case 500:
throw new Error('Server error occurred. Please try again later.');
case 503:
throw new Error('Service is temporarily unavailable. Please try again later.');
default:
throw new Error(`${operation} failed: ${message}`);
}
}
if (error instanceof Error) {
throw new Error(`${operation} failed: ${error.message}`);
}
throw new Error(`${operation} failed: Unknown error occurred`);
};
// Helper for retry logic
const retryOperation = async <T>(
operation: () => Promise<T>,
maxRetries: number = 3,
delay: number = 1000
): Promise<T> => {
let lastError: unknown;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error;
// Don't retry on client errors (4xx) except for 408 (timeout) and 429 (rate limit)
if (error instanceof AxiosError) {
const status = error.response?.status;
if (status && status >= 400 && status < 500 && status !== 408 && status !== 429) {
throw error;
}
}
if (attempt === maxRetries) {
throw lastError;
}
// Exponential backoff
const waitTime = delay * Math.pow(2, attempt - 1);
console.log(`AssetAssignmentApi: Retry attempt ${attempt}/${maxRetries} in ${waitTime}ms`);
await new Promise(resolve => setTimeout(resolve, waitTime));
}
}
throw lastError;
};
class AssetAssignmentApi {
private baseUrl = `/api/AssetAssignments`;
async getAll(): Promise<AssetAssignment[]> {
try {
console.log('AssetAssignmentApi: Fetching all asset assignments from', this.baseUrl);
const response = await apiClient.get(this.baseUrl);
console.log('AssetAssignmentApi: Response status', response.status);
if (!response.data) {
console.warn('AssetAssignmentApi: No data in response');
return [];
}
if (!Array.isArray(response.data)) {
console.warn('AssetAssignmentApi: Response is not an array', response.data);
return [];
}
console.log(`AssetAssignmentApi: Found ${response.data.length} asset assignments`);
// Map each item to ensure it has all required fields and validate
return response.data.map(item => {
try {
return validateAssetAssignmentApiResponse(item);
} catch (error) {
console.warn('AssetAssignmentApi: Invalid asset assignment data, using safe fallback:', error);
return createSafeAssetAssignment(item);
}
});
} catch (error) {
console.error('AssetAssignmentApi: Error fetching asset assignments:', error);
// Return empty array instead of throwing to prevent UI crashes
return [];
}
}
async getById(id: number): Promise<AssetAssignment | null> {
try {
console.log(`AssetAssignmentApi: Fetching asset assignment ${id}`);
const response = await apiClient.get(`${this.baseUrl}/${id}`);
console.log(`AssetAssignmentApi: Response status for asset assignment ${id}`, response.status);
if (response.data) {
try {
return validateAssetAssignmentApiResponse(response.data);
} catch (error) {
console.warn(`AssetAssignmentApi: Invalid asset assignment data for ID ${id}, using safe fallback:`, error);
return createSafeAssetAssignment(response.data);
}
}
return null;
} catch (error) {
console.error(`AssetAssignmentApi: Error fetching asset assignment ${id}:`, error);
return null;
}
}
async getByAssetId(assetId: number): Promise<AssetAssignment[]> {
try {
console.log(`AssetAssignmentApi: Fetching asset assignments for asset ${assetId}`);
const response = await apiClient.get(`${this.baseUrl}/by-asset/${assetId}`);
console.log(`AssetAssignmentApi: Response status for asset ${assetId} assignments`, response.status);
if (!response.data) {
console.warn(`AssetAssignmentApi: No asset assignments found for asset ${assetId}`);
return [];
}
if (!Array.isArray(response.data)) {
console.warn(`AssetAssignmentApi: Response is not an array for asset ${assetId}`, response.data);
return [];
}
console.log(`AssetAssignmentApi: Found ${response.data.length} asset assignments for asset ${assetId}`);
// Map each item to ensure it has all required fields and validate
return response.data.map(item => {
try {
return validateAssetAssignmentApiResponse(item);
} catch (error) {
console.warn(`AssetAssignmentApi: Invalid asset assignment data for asset ${assetId}, using safe fallback:`, error);
return createSafeAssetAssignment(item);
}
});
} catch (error) {
console.error(`AssetAssignmentApi: Error fetching asset assignments for asset ${assetId}:`, error);
// Return empty array instead of throwing to prevent UI crashes
return [];
}
}
async getByEmployeeId(employeeId: number): Promise<AssetAssignment[]> {
try {
console.log(`AssetAssignmentApi: Fetching asset assignments for employee ${employeeId}`);
const response = await apiClient.get(`${this.baseUrl}/by-employee/${employeeId}`);
console.log(`AssetAssignmentApi: Response status for employee ${employeeId} assignments`, response.status);
if (!response.data) {
console.warn(`AssetAssignmentApi: No asset assignments found for employee ${employeeId}`);
return [];
}
if (!Array.isArray(response.data)) {
console.warn(`AssetAssignmentApi: Response is not an array for employee ${employeeId}`, response.data);
return [];
}
console.log(`AssetAssignmentApi: Found ${response.data.length} asset assignments for employee ${employeeId}`);
// Map each item to ensure it has all required fields and validate
return response.data.map(item => {
try {
return validateAssetAssignmentApiResponse(item);
} catch (error) {
console.warn(`AssetAssignmentApi: Invalid asset assignment data for employee ${employeeId}, using safe fallback:`, error);
return createSafeAssetAssignment(item);
}
});
} catch (error) {
console.error(`AssetAssignmentApi: Error fetching asset assignments for employee ${employeeId}:`, error);
// Return empty array instead of throwing to prevent UI crashes
return [];
}
}
async create(assetAssignment: AssetAssignmentCreateRequest): Promise<AssetAssignment> {
return retryOperation(async () => {
try {
// Validate that the referenced asset exists
const asset = await assetApi.getById(assetAssignment.assetID);
if (!asset) {
throw new Error(`Asset with ID ${assetAssignment.assetID} does not exist. Please select a valid asset.`);
}
// Validate that the referenced employee exists
const employeeResponse = await employeeApi.getById(assetAssignment.employeeID);
if (!employeeResponse.success || !employeeResponse.data) {
throw new Error(`Employee with ID ${assetAssignment.employeeID} does not exist. Please select a valid employee.`);
}
// Format dates to ISO 8601
const assetAssignmentData = {
...assetAssignment,
assignedDate: formatDateToISO(assetAssignment.assignedDate),
returnedDate: assetAssignment.returnedDate ? formatDateToISO(assetAssignment.returnedDate) : null,
};
console.log('AssetAssignmentApi: Creating asset assignment with data', assetAssignmentData);
const response = await apiClient.post(this.baseUrl, assetAssignmentData);
console.log('AssetAssignmentApi: Create response status', response.status);
try {
return validateAssetAssignmentApiResponse(response.data);
} catch (error) {
console.warn('AssetAssignmentApi: Invalid response data from create, using safe fallback:', error);
return createSafeAssetAssignment(response.data);
}
} catch (error) {
handleApiError(error, 'create asset assignment');
}
}, 2); // Fewer retries for create operations
}
async update(id: number, assetAssignment: AssetAssignmentUpdateRequest): Promise<AssetAssignment> {
try {
// Validate that the referenced asset exists
const asset = await assetApi.getById(assetAssignment.assetID);
if (!asset) {
const errorMessage = `Asset with ID ${assetAssignment.assetID} does not exist`;
console.error('AssetAssignmentApi:', errorMessage);
throw new Error(errorMessage);
}
// Validate that the referenced employee exists
const employeeResponse = await employeeApi.getById(assetAssignment.employeeID);
if (!employeeResponse.success || !employeeResponse.data) {
const errorMessage = `Employee with ID ${assetAssignment.employeeID} does not exist`;
console.error('AssetAssignmentApi:', errorMessage);
throw new Error(errorMessage);
}
// Format dates to ISO 8601
const assetAssignmentData = {
...assetAssignment,
assignedDate: formatDateToISO(assetAssignment.assignedDate),
returnedDate: assetAssignment.returnedDate ? formatDateToISO(assetAssignment.returnedDate) : null,
};
console.log(`AssetAssignmentApi: Updating asset assignment ${id} with data`, assetAssignmentData);
const response = await apiClient.put(`${this.baseUrl}/${id}`, assetAssignmentData);
console.log(`AssetAssignmentApi: Update response status for asset assignment ${id}`, response.status);
try {
return validateAssetAssignmentApiResponse(response.data);
} catch (error) {
console.warn(`AssetAssignmentApi: Invalid response data from update for ID ${id}, using safe fallback:`, error);
return createSafeAssetAssignment(response.data);
}
} catch (error) {
console.error(`AssetAssignmentApi: Error updating asset assignment ${id}:`, error);
throw error;
}
}
async delete(id: number): Promise<void> {
try {
console.log(`AssetAssignmentApi: Deleting asset assignment ${id}`);
await apiClient.delete(`${this.baseUrl}/${id}`);
console.log(`AssetAssignmentApi: Asset assignment ${id} deleted successfully`);
} catch (error) {
console.error(`AssetAssignmentApi: Error deleting asset assignment ${id}:`, error);
throw error;
}
}
async getAssignmentHistory(assetId: number): Promise<{
success: boolean;
data: Array<AssetAssignment & {
assignmentStatus: string;
assignmentDuration: number | null;
returnReason: string | null;
employee: {
id: number;
empCode: string;
firstName: string;
middleName: string;
lastName: string;
email: string;
mobile: string;
type: string;
academics: string;
financialData: string;
status: string;
description: string;
userId: number;
passwordHash: string | null;
roleId: number;
createdBy: number;
createdAt: string;
createdByName: string | null;
updatedBy: number;
updatedAt: string;
updatedByName: string;
};
}>;
totalAssignments: number;
activeAssignments: number;
completedAssignments: number;
totalDaysAssigned: number;
message: string;
}> {
return retryOperation(async () => {
try {
console.log(`AssetAssignmentApi: Fetching assignment history for asset ${assetId}`);
// Use the history by-asset endpoint
const response = await apiClient.get(`${this.baseUrl}/history/by-asset/${assetId}`);
console.log(`AssetAssignmentApi: Response status for asset ${assetId} assignment history`, response.status);
if (!response.data) {
console.warn(`AssetAssignmentApi: No assignment history found for asset ${assetId}`);
return {
success: false,
data: [],
totalAssignments: 0,
activeAssignments: 0,
completedAssignments: 0,
totalDaysAssigned: 0,
message: `No assignment history found for asset ${assetId}`
};
}
// Check if response already has the expected structure (from your curl example)
if (response.data.success !== undefined && response.data.data !== undefined) {
console.log(`AssetAssignmentApi: Found assignment history for asset ${assetId}:`, response.data);
return response.data;
}
// If it's just an array, transform it to match the expected structure
const assignments = Array.isArray(response.data) ? response.data : [];
const activeAssignments = assignments.filter((assignment: any) => !assignment.returnedDate).length;
const completedAssignments = assignments.filter((assignment: any) => assignment.returnedDate).length;
// Calculate total days assigned for completed assignments
const totalDaysAssigned = assignments.reduce((total: number, assignment: any) => {
if (assignment.returnedDate && assignment.assignedDate) {
const assignedDate = new Date(assignment.assignedDate);
const returnedDate = new Date(assignment.returnedDate);
const diffTime = Math.abs(returnedDate.getTime() - assignedDate.getTime());
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
return total + diffDays;
}
return total;
}, 0);
console.log(`AssetAssignmentApi: Found ${assignments.length} assignments for asset ${assetId}`);
return {
success: true,
data: assignments.map((assignment: any) => ({
...assignment,
assignmentStatus: assignment.returnedDate ? 'Returned' : 'Active',
assignmentDuration: assignment.returnedDate && assignment.assignedDate
? Math.ceil((new Date(assignment.returnedDate).getTime() - new Date(assignment.assignedDate).getTime()) / (1000 * 60 * 60 * 24))
: null,
returnReason: assignment.returnReason || null
})),
totalAssignments: assignments.length,
activeAssignments,
completedAssignments,
totalDaysAssigned,
message: 'Assignment history retrieved successfully'
};
} catch (error) {
if (error instanceof AxiosError && error.response?.status === 404) {
console.log(`AssetAssignmentApi: No assignment history found for asset ${assetId}`);
return {
success: false,
data: [],
totalAssignments: 0,
activeAssignments: 0,
completedAssignments: 0,
totalDaysAssigned: 0,
message: `No assignment history found for asset ${assetId}`
};
}
handleApiError(error, `fetch assignment history for asset ${assetId}`);
}
});
}
async getActiveAssignments(): Promise<{
success: boolean;
data: Array<AssetAssignment & {
assignmentStatus: string;
daysSinceAssigned: number;
employee: {
id: number;
empCode: string;
firstName: string;
middleName: string;
lastName: string;
email: string;
mobile: string;
type: string;
academics: string;
financialData: string;
status: string;
description: string;
userId: number;
passwordHash: string | null;
roleId: number;
createdBy: number;
createdAt: string;
createdByName: string | null;
updatedBy: number;
updatedAt: string;
updatedByName: string;
};
asset?: {
assetID: number;
assetCode: string;
assetType: string;
brandModel: string;
serialNumber: string;
assetCondition: string;
};
}>;
totalActiveAssignments: number;
overdueAssignments: number;
assignmentsByDepartment: Record<string, number>;
message: string;
}> {
return retryOperation(async () => {
try {
console.log('AssetAssignmentApi: Fetching all active assignments');
const response = await apiClient.get(`${this.baseUrl}/active`);
console.log('AssetAssignmentApi: Response status for active assignments', response.status);
if (!response.data) {
console.warn('AssetAssignmentApi: No active assignments found');
return {
success: false,
data: [],
totalActiveAssignments: 0,
overdueAssignments: 0,
assignmentsByDepartment: {},
message: 'No active assignments found'
};
}
console.log('AssetAssignmentApi: Found active assignments:', response.data);
// Return the response as-is since it should match the expected structure
return response.data;
} catch (error) {
if (error instanceof AxiosError && error.response?.status === 404) {
console.log('AssetAssignmentApi: No active assignments found');
return {
success: false,
data: [],
totalActiveAssignments: 0,
overdueAssignments: 0,
assignmentsByDepartment: {},
message: 'No active assignments found'
};
}
handleApiError(error, 'fetch active assignments');
}
});
}
async bulkReturn(request: {
assignmentIds: number[];
returnedDate: string;
returnReason?: string;
remarks?: string;
performedBy?: {
employeeID: number;
name: string;
};
}): Promise<{
success: boolean;
data: {
processedAssignments: number;
successfulReturns: number;
failedReturns: number;
results: Array<{
assignmentID: number;
status: 'success' | 'failed';
message: string;
returnedAssignment?: AssetAssignment;
error?: string;
}>;
};
summary: {
totalRequested: number;
successful: number;
failed: number;
assetsReturned: number;
employeesAffected: number;
};
message: string;
}> {
return retryOperation(async () => {
try {
// Validate input
if (!Array.isArray(request.assignmentIds) || request.assignmentIds.length === 0) {
throw new Error('Assignment IDs array is required and cannot be empty');
}
if (request.assignmentIds.length > 100) {
throw new Error('Cannot return more than 100 assignments at once');
}
// Format the request data
const requestData = {
assignmentIds: request.assignmentIds,
returnedDate: formatDateToISO(request.returnedDate),
returnReason: request.returnReason || 'Bulk return operation',
remarks: request.remarks || 'Bulk returned via system',
performedBy: request.performedBy
};
console.log(`AssetAssignmentApi: Bulk returning ${request.assignmentIds.length} assignments`, requestData);
const response = await apiClient.post(`${this.baseUrl}/bulk-return`, requestData);
console.log('AssetAssignmentApi: Bulk return response status', response.status);
console.log('AssetAssignmentApi: Bulk return response data', response.data);
// Validate response structure
if (!response.data || typeof response.data !== 'object') {
throw new Error('Invalid response format from bulk return API');
}
const result = response.data;
// Ensure required fields exist
if (typeof result.success !== 'boolean') {
throw new Error('Invalid response: missing success field');
}
console.log(`AssetAssignmentApi: Bulk return completed - ${result.data?.successfulReturns || 0} successful, ${result.data?.failedReturns || 0} failed`);
return result;
} catch (error) {
handleApiError(error, 'bulk return assignments');
}
}, 1); // No retries for bulk operations to avoid duplicate operations
}
}
export const assetAssignmentApi = new AssetAssignmentApi();