392 lines
11 KiB
TypeScript
392 lines
11 KiB
TypeScript
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<string, number>;
|
|
by_company: Record<string, number>;
|
|
by_type: Record<string, number>;
|
|
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<AssetListResponse> {
|
|
// 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<AssetListResponse>(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<AssetListResponse> {
|
|
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<AssetListResponse>(endpoint);
|
|
}
|
|
|
|
/**
|
|
* Get detailed information about a specific asset
|
|
*/
|
|
async getAssetDetails(assetName: string): Promise<Asset> {
|
|
const endpoint = `${API_CONFIG.ENDPOINTS.GET_ASSET_DETAILS}?asset_name=${encodeURIComponent(assetName)}`;
|
|
return apiService.apiCall<Asset>(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<CreateAssetData>
|
|
): 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<AssetFilterOptions> {
|
|
return apiService.apiCall<AssetFilterOptions>(API_CONFIG.ENDPOINTS.GET_ASSET_FILTERS);
|
|
}
|
|
|
|
/**
|
|
* Get asset statistics
|
|
*/
|
|
async getAssetStats(): Promise<AssetStats> {
|
|
return apiService.apiCall<AssetStats>(API_CONFIG.ENDPOINTS.GET_ASSET_STATS);
|
|
}
|
|
|
|
/**
|
|
* Search assets by keyword
|
|
*/
|
|
async searchAssets(searchTerm: string, limit: number = 10): Promise<Asset[]> {
|
|
const endpoint = `${API_CONFIG.ENDPOINTS.SEARCH_ASSETS}?search_term=${encodeURIComponent(searchTerm)}&limit=${limit}`;
|
|
return apiService.apiCall<Asset[]>(endpoint);
|
|
}
|
|
|
|
/**
|
|
* Return which of the given values exist as Asset.custom_serial_number (exact match).
|
|
*/
|
|
async getExistingSerialNumbers(values: string[]): Promise<Set<string>> {
|
|
const unique = Array.from(
|
|
new Set(values.map((value) => value.trim()).filter(Boolean))
|
|
);
|
|
if (unique.length === 0) {
|
|
return new Set();
|
|
}
|
|
|
|
const matched = new Set<string>();
|
|
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;
|
|
|