// ================================================================ // frontend/src/api/services/suppliers.ts // ================================================================ /** * Suppliers Service - Complete backend alignment * * Backend API structure (3-tier architecture): * - ATOMIC: suppliers.py, purchase_orders.py, deliveries.py * - OPERATIONS: supplier_operations.py (approval, statistics, performance) * - ANALYTICS: analytics.py (performance metrics, alerts) * * Last Updated: 2025-10-05 * Status: ✅ Complete - Zero drift with backend */ 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'; // =================================================================== // ATOMIC: Suppliers CRUD // Backend: services/suppliers/app/api/suppliers.py // =================================================================== async createSupplier( tenantId: string, supplierData: SupplierCreate ): Promise { return apiClient.post( `${this.baseUrl}/${tenantId}/suppliers/suppliers`, supplierData ); } async getSuppliers( tenantId: string, queryParams?: SupplierQueryParams ): Promise> { 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>( `${this.baseUrl}/${tenantId}/suppliers/suppliers${queryString}` ); } async getSupplier(tenantId: string, supplierId: string): Promise { return apiClient.get( `${this.baseUrl}/${tenantId}/suppliers/suppliers/${supplierId}` ); } async updateSupplier( tenantId: string, supplierId: string, updateData: SupplierUpdate ): Promise { return apiClient.put( `${this.baseUrl}/${tenantId}/suppliers/suppliers/${supplierId}`, updateData ); } async deleteSupplier( tenantId: string, supplierId: string ): Promise<{ message: string }> { return apiClient.delete<{ message: string }>( `${this.baseUrl}/${tenantId}/suppliers/suppliers/${supplierId}` ); } // =================================================================== // ATOMIC: Purchase Orders CRUD // Backend: services/suppliers/app/api/purchase_orders.py // =================================================================== async createPurchaseOrder( tenantId: string, orderData: PurchaseOrderCreate ): Promise { return apiClient.post( `${this.baseUrl}/${tenantId}/suppliers/purchase-orders`, orderData ); } async getPurchaseOrders( tenantId: string, queryParams?: PurchaseOrderQueryParams ): Promise> { 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>( `${this.baseUrl}/${tenantId}/suppliers/purchase-orders${queryString}` ); } async getPurchaseOrder(tenantId: string, orderId: string): Promise { return apiClient.get( `${this.baseUrl}/${tenantId}/suppliers/purchase-orders/${orderId}` ); } async updatePurchaseOrder( tenantId: string, orderId: string, updateData: PurchaseOrderUpdate ): Promise { return apiClient.put( `${this.baseUrl}/${tenantId}/suppliers/purchase-orders/${orderId}`, updateData ); } async approvePurchaseOrder( tenantId: string, orderId: string, approval: PurchaseOrderApproval ): Promise { return apiClient.post( `${this.baseUrl}/${tenantId}/suppliers/purchase-orders/${orderId}/approve`, approval ); } // =================================================================== // ATOMIC: Deliveries CRUD // Backend: services/suppliers/app/api/deliveries.py // =================================================================== async createDelivery( tenantId: string, deliveryData: DeliveryCreate ): Promise { return apiClient.post( `${this.baseUrl}/${tenantId}/suppliers/deliveries`, deliveryData ); } async getDeliveries( tenantId: string, queryParams?: DeliveryQueryParams ): Promise> { 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>( `${this.baseUrl}/${tenantId}/suppliers/deliveries${queryString}` ); } async getDelivery(tenantId: string, deliveryId: string): Promise { return apiClient.get( `${this.baseUrl}/${tenantId}/suppliers/deliveries/${deliveryId}` ); } async updateDelivery( tenantId: string, deliveryId: string, updateData: DeliveryUpdate ): Promise { return apiClient.put( `${this.baseUrl}/${tenantId}/suppliers/deliveries/${deliveryId}`, updateData ); } async confirmDeliveryReceipt( tenantId: string, deliveryId: string, confirmation: DeliveryReceiptConfirmation ): Promise { return apiClient.post( `${this.baseUrl}/${tenantId}/suppliers/deliveries/${deliveryId}/confirm-receipt`, confirmation ); } // =================================================================== // OPERATIONS: Supplier Management // Backend: services/suppliers/app/api/supplier_operations.py // =================================================================== async getSupplierStatistics(tenantId: string): Promise { return apiClient.get( `${this.baseUrl}/${tenantId}/suppliers/operations/statistics` ); } async getActiveSuppliers( tenantId: string, queryParams?: Omit ): Promise> { 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?.limit) params.append('limit', queryParams.limit.toString()); if (queryParams?.offset) params.append('offset', queryParams.offset.toString()); const queryString = params.toString() ? `?${params.toString()}` : ''; return apiClient.get>( `${this.baseUrl}/${tenantId}/suppliers/operations/active${queryString}` ); } async getTopSuppliers(tenantId: string): Promise { return apiClient.get( `${this.baseUrl}/${tenantId}/suppliers/operations/top` ); } async getPendingApprovalSuppliers( tenantId: string ): Promise> { return apiClient.get>( `${this.baseUrl}/${tenantId}/suppliers/operations/pending-review` ); } async getSuppliersByType( tenantId: string, supplierType: string, queryParams?: Omit ): Promise> { const params = new URLSearchParams(); if (queryParams?.search_term) params.append('search_term', queryParams.search_term); 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()); const queryString = params.toString() ? `?${params.toString()}` : ''; return apiClient.get>( `${this.baseUrl}/${tenantId}/suppliers/types/${supplierType}${queryString}` ); } async approveSupplier( tenantId: string, supplierId: string, approval: SupplierApproval ): Promise { return apiClient.post( `${this.baseUrl}/${tenantId}/suppliers/${supplierId}/approve`, approval ); } // =================================================================== // ANALYTICS: Performance Metrics // Backend: services/suppliers/app/api/analytics.py // =================================================================== 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.baseUrl}/${tenantId}/suppliers/analytics/performance/${supplierId}/calculate${queryString}` ); } async getSupplierPerformanceMetrics( tenantId: string, supplierId: string ): Promise { return apiClient.get( `${this.baseUrl}/${tenantId}/suppliers/analytics/performance/${supplierId}/metrics` ); } async evaluatePerformanceAlerts( tenantId: string ): Promise<{ alerts_generated: number; message: string }> { return apiClient.post<{ alerts_generated: number; message: string }>( `${this.baseUrl}/${tenantId}/suppliers/analytics/performance/alerts/evaluate` ); } async getPerformanceAlerts( tenantId: string, supplierId?: string ): Promise { const url = supplierId ? `${this.baseUrl}/${tenantId}/suppliers/analytics/performance/${supplierId}/alerts` : `${this.baseUrl}/${tenantId}/suppliers/analytics/performance/alerts`; return apiClient.get(url); } // =================================================================== // UTILITY METHODS (Client-side helpers) // =================================================================== 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;