336 lines
11 KiB
TypeScript
336 lines
11 KiB
TypeScript
|
|
/**
|
||
|
|
* Suppliers service API implementation
|
||
|
|
* Handles all supplier-related backend communications
|
||
|
|
*/
|
||
|
|
|
||
|
|
import { apiClient } from '../client/apiClient';
|
||
|
|
import type {
|
||
|
|
SupplierCreate,
|
||
|
|
SupplierUpdate,
|
||
|
|
SupplierResponse,
|
||
|
|
SupplierSummary,
|
||
|
|
SupplierApproval,
|
||
|
|
SupplierQueryParams,
|
||
|
|
SupplierStatistics,
|
||
|
|
TopSuppliersResponse,
|
||
|
|
PurchaseOrderCreate,
|
||
|
|
PurchaseOrderUpdate,
|
||
|
|
PurchaseOrderResponse,
|
||
|
|
PurchaseOrderApproval,
|
||
|
|
PurchaseOrderQueryParams,
|
||
|
|
DeliveryCreate,
|
||
|
|
DeliveryUpdate,
|
||
|
|
DeliveryResponse,
|
||
|
|
DeliveryReceiptConfirmation,
|
||
|
|
DeliveryQueryParams,
|
||
|
|
PerformanceCalculationRequest,
|
||
|
|
PerformanceMetrics,
|
||
|
|
PerformanceAlert,
|
||
|
|
PaginatedResponse,
|
||
|
|
ApiResponse,
|
||
|
|
} from '../types/suppliers';
|
||
|
|
|
||
|
|
class SuppliersService {
|
||
|
|
private readonly baseUrl = '/tenants';
|
||
|
|
private readonly purchaseOrdersUrl = '/purchase-orders';
|
||
|
|
private readonly deliveriesUrl = '/deliveries';
|
||
|
|
private readonly performanceUrl = '/performance';
|
||
|
|
|
||
|
|
// Supplier Management
|
||
|
|
async createSupplier(
|
||
|
|
tenantId: string,
|
||
|
|
supplierData: SupplierCreate
|
||
|
|
): Promise<SupplierResponse> {
|
||
|
|
return apiClient.post<SupplierResponse>(
|
||
|
|
`${this.baseUrl}/${tenantId}/suppliers`,
|
||
|
|
supplierData
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
async getSuppliers(
|
||
|
|
tenantId: string,
|
||
|
|
queryParams?: SupplierQueryParams
|
||
|
|
): Promise<PaginatedResponse<SupplierSummary>> {
|
||
|
|
const params = new URLSearchParams();
|
||
|
|
|
||
|
|
if (queryParams?.search_term) params.append('search_term', queryParams.search_term);
|
||
|
|
if (queryParams?.supplier_type) params.append('supplier_type', queryParams.supplier_type);
|
||
|
|
if (queryParams?.status) params.append('status', queryParams.status);
|
||
|
|
if (queryParams?.limit) params.append('limit', queryParams.limit.toString());
|
||
|
|
if (queryParams?.offset) params.append('offset', queryParams.offset.toString());
|
||
|
|
if (queryParams?.sort_by) params.append('sort_by', queryParams.sort_by);
|
||
|
|
if (queryParams?.sort_order) params.append('sort_order', queryParams.sort_order);
|
||
|
|
|
||
|
|
const queryString = params.toString() ? `?${params.toString()}` : '';
|
||
|
|
return apiClient.get<PaginatedResponse<SupplierSummary>>(
|
||
|
|
`${this.baseUrl}/${tenantId}/suppliers${queryString}`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
async getSupplier(tenantId: string, supplierId: string): Promise<SupplierResponse> {
|
||
|
|
return apiClient.get<SupplierResponse>(
|
||
|
|
`${this.baseUrl}/${tenantId}/suppliers/${supplierId}`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
async updateSupplier(
|
||
|
|
tenantId: string,
|
||
|
|
supplierId: string,
|
||
|
|
updateData: SupplierUpdate
|
||
|
|
): Promise<SupplierResponse> {
|
||
|
|
return apiClient.put<SupplierResponse>(
|
||
|
|
`${this.baseUrl}/${tenantId}/suppliers/${supplierId}`,
|
||
|
|
updateData
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
async deleteSupplier(
|
||
|
|
tenantId: string,
|
||
|
|
supplierId: string
|
||
|
|
): Promise<{ message: string }> {
|
||
|
|
return apiClient.delete<{ message: string }>(
|
||
|
|
`${this.baseUrl}/${tenantId}/suppliers/${supplierId}`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Specialized Supplier Endpoints
|
||
|
|
async getSupplierStatistics(tenantId: string): Promise<SupplierStatistics> {
|
||
|
|
return apiClient.get<SupplierStatistics>(
|
||
|
|
`${this.baseUrl}/${tenantId}/suppliers/statistics`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
async getActiveSuppliers(
|
||
|
|
tenantId: string,
|
||
|
|
queryParams?: Omit<SupplierQueryParams, 'status'>
|
||
|
|
): Promise<PaginatedResponse<SupplierSummary>> {
|
||
|
|
return this.getSuppliers(tenantId, { ...queryParams, status: 'active' });
|
||
|
|
}
|
||
|
|
|
||
|
|
async getTopSuppliers(tenantId: string): Promise<TopSuppliersResponse> {
|
||
|
|
return apiClient.get<TopSuppliersResponse>(
|
||
|
|
`${this.baseUrl}/${tenantId}/suppliers/top`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
async getPendingApprovalSuppliers(
|
||
|
|
tenantId: string
|
||
|
|
): Promise<PaginatedResponse<SupplierSummary>> {
|
||
|
|
return this.getSuppliers(tenantId, { status: 'pending_approval' });
|
||
|
|
}
|
||
|
|
|
||
|
|
async getSuppliersByType(
|
||
|
|
tenantId: string,
|
||
|
|
supplierType: string,
|
||
|
|
queryParams?: Omit<SupplierQueryParams, 'supplier_type'>
|
||
|
|
): Promise<PaginatedResponse<SupplierSummary>> {
|
||
|
|
return apiClient.get<PaginatedResponse<SupplierSummary>>(
|
||
|
|
`${this.baseUrl}/${tenantId}/suppliers/types/${supplierType}`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Supplier Approval Workflow
|
||
|
|
async approveSupplier(
|
||
|
|
tenantId: string,
|
||
|
|
supplierId: string,
|
||
|
|
approval: SupplierApproval
|
||
|
|
): Promise<SupplierResponse> {
|
||
|
|
return apiClient.post<SupplierResponse>(
|
||
|
|
`${this.baseUrl}/${tenantId}/suppliers/${supplierId}/approve`,
|
||
|
|
approval
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Purchase Orders
|
||
|
|
async createPurchaseOrder(orderData: PurchaseOrderCreate): Promise<PurchaseOrderResponse> {
|
||
|
|
return apiClient.post<PurchaseOrderResponse>(this.purchaseOrdersUrl, orderData);
|
||
|
|
}
|
||
|
|
|
||
|
|
async getPurchaseOrders(
|
||
|
|
queryParams?: PurchaseOrderQueryParams
|
||
|
|
): Promise<PaginatedResponse<PurchaseOrderResponse>> {
|
||
|
|
const params = new URLSearchParams();
|
||
|
|
|
||
|
|
if (queryParams?.supplier_id) params.append('supplier_id', queryParams.supplier_id);
|
||
|
|
if (queryParams?.status) params.append('status', queryParams.status);
|
||
|
|
if (queryParams?.priority) params.append('priority', queryParams.priority);
|
||
|
|
if (queryParams?.date_from) params.append('date_from', queryParams.date_from);
|
||
|
|
if (queryParams?.date_to) params.append('date_to', queryParams.date_to);
|
||
|
|
if (queryParams?.limit) params.append('limit', queryParams.limit.toString());
|
||
|
|
if (queryParams?.offset) params.append('offset', queryParams.offset.toString());
|
||
|
|
if (queryParams?.sort_by) params.append('sort_by', queryParams.sort_by);
|
||
|
|
if (queryParams?.sort_order) params.append('sort_order', queryParams.sort_order);
|
||
|
|
|
||
|
|
const queryString = params.toString() ? `?${params.toString()}` : '';
|
||
|
|
return apiClient.get<PaginatedResponse<PurchaseOrderResponse>>(
|
||
|
|
`${this.purchaseOrdersUrl}${queryString}`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
async getPurchaseOrder(orderId: string): Promise<PurchaseOrderResponse> {
|
||
|
|
return apiClient.get<PurchaseOrderResponse>(`${this.purchaseOrdersUrl}/${orderId}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
async updatePurchaseOrder(
|
||
|
|
orderId: string,
|
||
|
|
updateData: PurchaseOrderUpdate
|
||
|
|
): Promise<PurchaseOrderResponse> {
|
||
|
|
return apiClient.put<PurchaseOrderResponse>(
|
||
|
|
`${this.purchaseOrdersUrl}/${orderId}`,
|
||
|
|
updateData
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
async approvePurchaseOrder(
|
||
|
|
orderId: string,
|
||
|
|
approval: PurchaseOrderApproval
|
||
|
|
): Promise<PurchaseOrderResponse> {
|
||
|
|
return apiClient.post<PurchaseOrderResponse>(
|
||
|
|
`${this.purchaseOrdersUrl}/${orderId}/approve`,
|
||
|
|
approval
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Deliveries
|
||
|
|
async createDelivery(deliveryData: DeliveryCreate): Promise<DeliveryResponse> {
|
||
|
|
return apiClient.post<DeliveryResponse>(this.deliveriesUrl, deliveryData);
|
||
|
|
}
|
||
|
|
|
||
|
|
async getDeliveries(
|
||
|
|
queryParams?: DeliveryQueryParams
|
||
|
|
): Promise<PaginatedResponse<DeliveryResponse>> {
|
||
|
|
const params = new URLSearchParams();
|
||
|
|
|
||
|
|
if (queryParams?.supplier_id) params.append('supplier_id', queryParams.supplier_id);
|
||
|
|
if (queryParams?.purchase_order_id) {
|
||
|
|
params.append('purchase_order_id', queryParams.purchase_order_id);
|
||
|
|
}
|
||
|
|
if (queryParams?.status) params.append('status', queryParams.status);
|
||
|
|
if (queryParams?.scheduled_date_from) {
|
||
|
|
params.append('scheduled_date_from', queryParams.scheduled_date_from);
|
||
|
|
}
|
||
|
|
if (queryParams?.scheduled_date_to) {
|
||
|
|
params.append('scheduled_date_to', queryParams.scheduled_date_to);
|
||
|
|
}
|
||
|
|
if (queryParams?.limit) params.append('limit', queryParams.limit.toString());
|
||
|
|
if (queryParams?.offset) params.append('offset', queryParams.offset.toString());
|
||
|
|
if (queryParams?.sort_by) params.append('sort_by', queryParams.sort_by);
|
||
|
|
if (queryParams?.sort_order) params.append('sort_order', queryParams.sort_order);
|
||
|
|
|
||
|
|
const queryString = params.toString() ? `?${params.toString()}` : '';
|
||
|
|
return apiClient.get<PaginatedResponse<DeliveryResponse>>(
|
||
|
|
`${this.deliveriesUrl}${queryString}`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
async getDelivery(deliveryId: string): Promise<DeliveryResponse> {
|
||
|
|
return apiClient.get<DeliveryResponse>(`${this.deliveriesUrl}/${deliveryId}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
async updateDelivery(
|
||
|
|
deliveryId: string,
|
||
|
|
updateData: DeliveryUpdate
|
||
|
|
): Promise<DeliveryResponse> {
|
||
|
|
return apiClient.put<DeliveryResponse>(
|
||
|
|
`${this.deliveriesUrl}/${deliveryId}`,
|
||
|
|
updateData
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
async confirmDeliveryReceipt(
|
||
|
|
deliveryId: string,
|
||
|
|
confirmation: DeliveryReceiptConfirmation
|
||
|
|
): Promise<DeliveryResponse> {
|
||
|
|
return apiClient.post<DeliveryResponse>(
|
||
|
|
`${this.deliveriesUrl}/${deliveryId}/confirm-receipt`,
|
||
|
|
confirmation
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Performance Tracking
|
||
|
|
async calculateSupplierPerformance(
|
||
|
|
tenantId: string,
|
||
|
|
supplierId: string,
|
||
|
|
request?: PerformanceCalculationRequest
|
||
|
|
): Promise<{ message: string; calculation_id: string }> {
|
||
|
|
const params = new URLSearchParams();
|
||
|
|
if (request?.period) params.append('period', request.period);
|
||
|
|
if (request?.period_start) params.append('period_start', request.period_start);
|
||
|
|
if (request?.period_end) params.append('period_end', request.period_end);
|
||
|
|
|
||
|
|
const queryString = params.toString() ? `?${params.toString()}` : '';
|
||
|
|
return apiClient.post<{ message: string; calculation_id: string }>(
|
||
|
|
`${this.performanceUrl}/tenants/${tenantId}/suppliers/${supplierId}/calculate${queryString}`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
async getSupplierPerformanceMetrics(
|
||
|
|
tenantId: string,
|
||
|
|
supplierId: string
|
||
|
|
): Promise<PerformanceMetrics> {
|
||
|
|
return apiClient.get<PerformanceMetrics>(
|
||
|
|
`${this.performanceUrl}/tenants/${tenantId}/suppliers/${supplierId}/metrics`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
async evaluatePerformanceAlerts(
|
||
|
|
tenantId: string
|
||
|
|
): Promise<{ alerts_generated: number; message: string }> {
|
||
|
|
return apiClient.post<{ alerts_generated: number; message: string }>(
|
||
|
|
`${this.performanceUrl}/tenants/${tenantId}/alerts/evaluate`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
async getPerformanceAlerts(
|
||
|
|
tenantId: string,
|
||
|
|
supplierId?: string
|
||
|
|
): Promise<PerformanceAlert[]> {
|
||
|
|
const url = supplierId
|
||
|
|
? `${this.performanceUrl}/tenants/${tenantId}/suppliers/${supplierId}/alerts`
|
||
|
|
: `${this.performanceUrl}/tenants/${tenantId}/alerts`;
|
||
|
|
|
||
|
|
return apiClient.get<PerformanceAlert[]>(url);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Utility methods
|
||
|
|
calculateOrderTotal(
|
||
|
|
items: { ordered_quantity: number; unit_price: number }[],
|
||
|
|
taxAmount: number = 0,
|
||
|
|
shippingCost: number = 0,
|
||
|
|
discountAmount: number = 0
|
||
|
|
): number {
|
||
|
|
const subtotal = items.reduce(
|
||
|
|
(sum, item) => sum + (item.ordered_quantity * item.unit_price),
|
||
|
|
0
|
||
|
|
);
|
||
|
|
return subtotal + taxAmount + shippingCost - discountAmount;
|
||
|
|
}
|
||
|
|
|
||
|
|
formatSupplierCode(name: string, sequence?: number): string {
|
||
|
|
const cleanName = name.replace(/[^a-zA-Z0-9]/g, '').toUpperCase();
|
||
|
|
const prefix = cleanName.substring(0, 3).padEnd(3, 'X');
|
||
|
|
const suffix = sequence ? sequence.toString().padStart(3, '0') : '001';
|
||
|
|
return `${prefix}${suffix}`;
|
||
|
|
}
|
||
|
|
|
||
|
|
validateTaxId(taxId: string, country: string = 'ES'): boolean {
|
||
|
|
// Simplified validation - real implementation would have proper country-specific validation
|
||
|
|
if (country === 'ES') {
|
||
|
|
// Spanish VAT format: ES + letter + 8 digits or ES + 8 digits + letter
|
||
|
|
const spanishVatRegex = /^ES[A-Z]\d{8}$|^ES\d{8}[A-Z]$/;
|
||
|
|
return spanishVatRegex.test(taxId.toUpperCase());
|
||
|
|
}
|
||
|
|
return taxId.length > 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
formatCurrency(amount: number, currency: string = 'EUR'): string {
|
||
|
|
return new Intl.NumberFormat('es-ES', {
|
||
|
|
style: 'currency',
|
||
|
|
currency: currency,
|
||
|
|
}).format(amount);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create and export singleton instance
|
||
|
|
export const suppliersService = new SuppliersService();
|
||
|
|
export default suppliersService;
|