import apiService from './apiService'; import API_CONFIG from '../config/api'; // Asset Interfaces export interface Asset { name: string; asset_name: string; company: string; docstatus?: number; // 0 = Draft, 1 = Submitted, 2 = Cancelled custom_serial_number?: string; custom_technical_department?: string; location?: string; custom_building?: string; custom_room_number?: string; custom_manufacturer?: string; department?: string; custom_asset_type?: string; custom_manufacturing_year?: string; custom_model?: string; custom_class?: string; custom_device_status?: string; custom_down_time?: number; asset_owner_company?: string; custom_up_time?: number; custom_total_hours?: number; custom_modality?: string; custom_attach_image?: string; custom_site_contractor?: string; custom_site?: string; custom_total_amount?: number; creation?: string; modified?: string; owner?: string; modified_by?: string; status?: string; workflow_state?: string; custom_delete_status?: string; custom_category?: string; custom_recalled?: string; calculate_depreciation?: boolean; gross_purchase_amount?: number; available_for_use_date?:string; finance_books?: AssetFinanceBookRow[]; custom_spare_parts?: Array<{ item_code?: string; item_name?: string; qty?: number; rate?: number; amount?: number; uom?: string; work_order?: string; }>; custom_total_spare_parts_amount?: number; // Checkbox fields custom_warranty?: boolean; custom_extended_warranty?: boolean; custom__service_contract?: boolean; custom_covering_spare_parts?: boolean; custom_spare_parts_labour?: boolean; custom_covering_labour?: boolean; custom_ppm_only?: boolean; custom_support_plan?: string; // Service agreement fields custom_service_agreement?: string; custom_service_coverage?: string; custom_start_date?: string; custom_end_date?: string; } export interface AssetListResponse { assets: Asset[]; total_count: number; limit: number; offset: number; has_more: boolean; } export interface AssetFilters { company?: string; location?: string; department?: string; custom_asset_type?: string; custom_manufacturer?: string; custom_device_status?: string; [key: string]: any; } export interface AssetFilterOptions { companies: string[]; locations: string[]; departments: string[]; asset_types: string[]; manufacturers: string[]; device_statuses: string[]; } export interface AssetStats { total_assets: number; by_status: Record; by_company: Record; by_type: Record; total_amount: number; } // Add child row type export interface AssetFinanceBookRow { finance_book?: string; depreciation_method?: string; total_number_of_depreciations?: number; frequency_of_depreciation?: number; depreciation_start_date?: string; // YYYY-MM-DD } export interface CreateAssetData { asset_name: string; company: string; custom_serial_number?: string; custom_technical_department?: string; location?: string; custom_building?: string; custom_room_number?: string; custom_manufacturer?: string; department?: string; custom_asset_type?: string; custom_manufacturing_year?: string; custom_model?: string; custom_class?: string; custom_device_status?: string; custom_down_time?: number; asset_owner_company?: string; custom_up_time?: number; custom_total_hours?: number; custom_modality?: string; custom_attach_image?: string; custom_site_contractor?: string; custom_site?: string; custom_total_amount?: number; calculate_depreciation?: boolean; finance_books?: AssetFinanceBookRow[]; // Checkbox fields custom_warranty?: boolean; custom_extended_warranty?: boolean; custom__service_contract?: boolean; custom_covering_spare_parts?: boolean; custom_spare_parts_labour?: boolean; custom_covering_labour?: boolean; custom_ppm_only?: boolean; custom_support_plan?: string; // Spare parts custom_spare_parts?: Array<{ item_code?: string; item_name?: string; qty?: number; rate?: number; amount?: number; uom?: string; work_order?: string; }>; custom_total_spare_parts_amount?: number; [key: string]: any; } class AssetService { /** * Get list of assets with optional filters and pagination */ // async getAssets( // filters?: AssetFilters, // fields?: string[], // limit: number = 20, // offset: number = 0, // orderBy?: string // ): Promise { // const params = new URLSearchParams(); // if (filters) { // params.append('filters', JSON.stringify(filters)); // } // if (fields && fields.length > 0) { // params.append('fields', JSON.stringify(fields)); // } // params.append('limit', limit.toString()); // params.append('offset', offset.toString()); // if (orderBy) { // params.append('order_by', orderBy); // } // const endpoint = `${API_CONFIG.ENDPOINTS.GET_ASSETS}?${params.toString()}`; // return apiService.apiCall(endpoint); // } /** * Get list of assets with optional filters and pagination * Uses Frappe's built-in REST API which auto-applies User Permissions */ async getAssets( filters?: AssetFilters, fields?: string[], limit: number = 20, offset: number = 0, orderBy?: string ): Promise { const params = new URLSearchParams(); if (filters && Object.keys(filters).length > 0) { const { toFrappeFilterArray } = await import('../utils/listFilterUtils'); const filterArray = toFrappeFilterArray(filters); if (filterArray.length > 0) params.append('filters', JSON.stringify(filterArray)); } if (fields && fields.length > 0) { params.append('fields', JSON.stringify(fields)); } params.append('limit', limit.toString()); params.append('offset', offset.toString()); if (orderBy) { params.append('order_by', orderBy); } const endpoint = `${API_CONFIG.ENDPOINTS.GET_ASSETS}?${params.toString()}`; return apiService.apiCall(endpoint); } /** * Get detailed information about a specific asset */ async getAssetDetails(assetName: string): Promise { const endpoint = `${API_CONFIG.ENDPOINTS.GET_ASSET_DETAILS}?asset_name=${encodeURIComponent(assetName)}`; return apiService.apiCall(endpoint); } /** * Create a new asset */ async createAsset(assetData: CreateAssetData): Promise<{ success: boolean; asset: Asset; message: string }> { return apiService.apiCall(API_CONFIG.ENDPOINTS.CREATE_ASSET, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ asset_data: assetData }) }); } /** * Update an existing asset */ async updateAsset( assetName: string, assetData: Partial ): Promise<{ success: boolean; asset: Asset; message: string }> { return apiService.apiCall(API_CONFIG.ENDPOINTS.UPDATE_ASSET, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ asset_name: assetName, asset_data: assetData }) }); } /** * Delete an asset */ async deleteAsset(assetName: string): Promise<{ success: boolean; message: string }> { return apiService.apiCall(API_CONFIG.ENDPOINTS.DELETE_ASSET, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ asset_name: assetName }) }); } /** * Get available filter options */ async getAssetFilters(): Promise { return apiService.apiCall(API_CONFIG.ENDPOINTS.GET_ASSET_FILTERS); } /** * Get asset statistics */ async getAssetStats(): Promise { return apiService.apiCall(API_CONFIG.ENDPOINTS.GET_ASSET_STATS); } /** * Search assets by keyword */ async searchAssets(searchTerm: string, limit: number = 10): Promise { const endpoint = `${API_CONFIG.ENDPOINTS.SEARCH_ASSETS}?search_term=${encodeURIComponent(searchTerm)}&limit=${limit}`; return apiService.apiCall(endpoint); } /** * Return which of the given values exist as Asset.custom_serial_number (exact match). */ async getExistingSerialNumbers(values: string[]): Promise> { const unique = Array.from( new Set(values.map((value) => value.trim()).filter(Boolean)) ); if (unique.length === 0) { return new Set(); } const matched = new Set(); const chunkSize = 500; for (let offset = 0; offset < unique.length; offset += chunkSize) { const chunk = unique.slice(offset, offset + chunkSize); const queryParams = new URLSearchParams(); queryParams.append('fields', JSON.stringify(['custom_serial_number'])); queryParams.append('filters', JSON.stringify([['custom_serial_number', 'in', chunk]])); queryParams.append('limit_page_length', '0'); const response = await fetch( `${API_CONFIG.BASE_URL}/api/resource/Asset?${queryParams.toString()}`, { method: 'GET', headers: { Accept: 'application/json', 'Content-Type': 'application/json', ...(typeof window !== 'undefined' && (window as Window & { csrf_token?: string }).csrf_token ? { 'X-Frappe-CSRF-Token': (window as Window & { csrf_token?: string }).csrf_token! } : {}), }, credentials: 'include', } ); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const result = await response.json(); for (const row of result.data || []) { const serial = (row.custom_serial_number || '').trim(); if (serial) { matched.add(serial); } } } return matched; } /** * Submit an asset document (changes docstatus from 0 to 1) */ // async submitAsset(assetName: string): Promise<{ message: string }> { // return apiService.apiCall('/api/method/frappe.client.submit', { // method: 'POST', // headers: { // 'Content-Type': 'application/json', // }, // body: JSON.stringify({ // doc: { // doctype: 'Asset', // name: assetName // } // }) // }); // } async submitAsset(assetName: string): Promise<{ success: boolean; asset: Asset; message: string }> { return apiService.apiCall(API_CONFIG.ENDPOINTS.SUBMIT_ASSET || '/api/method/asset_lite.api.asset_api.submit_asset', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ asset_name: assetName }) }); } } // Create and export singleton instance const assetService = new AssetService(); export default assetService;