Files
bakery-ia/frontend/src/api/services/suppliers.ts
2025-10-06 15:27:01 +02:00

406 lines
14 KiB
TypeScript

// ================================================================
// 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<SupplierResponse> {
return apiClient.post<SupplierResponse>(
`${this.baseUrl}/${tenantId}/suppliers/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/suppliers${queryString}`
);
}
async getSupplier(tenantId: string, supplierId: string): Promise<SupplierResponse> {
return apiClient.get<SupplierResponse>(
`${this.baseUrl}/${tenantId}/suppliers/suppliers/${supplierId}`
);
}
async updateSupplier(
tenantId: string,
supplierId: string,
updateData: SupplierUpdate
): Promise<SupplierResponse> {
return apiClient.put<SupplierResponse>(
`${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<PurchaseOrderResponse> {
return apiClient.post<PurchaseOrderResponse>(
`${this.baseUrl}/${tenantId}/suppliers/purchase-orders`,
orderData
);
}
async getPurchaseOrders(
tenantId: string,
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.baseUrl}/${tenantId}/suppliers/purchase-orders${queryString}`
);
}
async getPurchaseOrder(tenantId: string, orderId: string): Promise<PurchaseOrderResponse> {
return apiClient.get<PurchaseOrderResponse>(
`${this.baseUrl}/${tenantId}/suppliers/purchase-orders/${orderId}`
);
}
async updatePurchaseOrder(
tenantId: string,
orderId: string,
updateData: PurchaseOrderUpdate
): Promise<PurchaseOrderResponse> {
return apiClient.put<PurchaseOrderResponse>(
`${this.baseUrl}/${tenantId}/suppliers/purchase-orders/${orderId}`,
updateData
);
}
async approvePurchaseOrder(
tenantId: string,
orderId: string,
approval: PurchaseOrderApproval
): Promise<PurchaseOrderResponse> {
return apiClient.post<PurchaseOrderResponse>(
`${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<DeliveryResponse> {
return apiClient.post<DeliveryResponse>(
`${this.baseUrl}/${tenantId}/suppliers/deliveries`,
deliveryData
);
}
async getDeliveries(
tenantId: string,
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.baseUrl}/${tenantId}/suppliers/deliveries${queryString}`
);
}
async getDelivery(tenantId: string, deliveryId: string): Promise<DeliveryResponse> {
return apiClient.get<DeliveryResponse>(
`${this.baseUrl}/${tenantId}/suppliers/deliveries/${deliveryId}`
);
}
async updateDelivery(
tenantId: string,
deliveryId: string,
updateData: DeliveryUpdate
): Promise<DeliveryResponse> {
return apiClient.put<DeliveryResponse>(
`${this.baseUrl}/${tenantId}/suppliers/deliveries/${deliveryId}`,
updateData
);
}
async confirmDeliveryReceipt(
tenantId: string,
deliveryId: string,
confirmation: DeliveryReceiptConfirmation
): Promise<DeliveryResponse> {
return apiClient.post<DeliveryResponse>(
`${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<SupplierStatistics> {
return apiClient.get<SupplierStatistics>(
`${this.baseUrl}/${tenantId}/suppliers/operations/statistics`
);
}
async getActiveSuppliers(
tenantId: string,
queryParams?: Omit<SupplierQueryParams, 'status'>
): 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?.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<PaginatedResponse<SupplierSummary>>(
`${this.baseUrl}/${tenantId}/suppliers/operations/active${queryString}`
);
}
async getTopSuppliers(tenantId: string): Promise<TopSuppliersResponse> {
return apiClient.get<TopSuppliersResponse>(
`${this.baseUrl}/${tenantId}/suppliers/operations/top`
);
}
async getPendingApprovalSuppliers(
tenantId: string
): Promise<PaginatedResponse<SupplierSummary>> {
return apiClient.get<PaginatedResponse<SupplierSummary>>(
`${this.baseUrl}/${tenantId}/suppliers/operations/pending-review`
);
}
async getSuppliersByType(
tenantId: string,
supplierType: string,
queryParams?: Omit<SupplierQueryParams, 'supplier_type'>
): Promise<PaginatedResponse<SupplierSummary>> {
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<PaginatedResponse<SupplierSummary>>(
`${this.baseUrl}/${tenantId}/suppliers/types/${supplierType}${queryString}`
);
}
async approveSupplier(
tenantId: string,
supplierId: string,
approval: SupplierApproval
): Promise<SupplierResponse> {
return apiClient.post<SupplierResponse>(
`${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<PerformanceMetrics> {
return apiClient.get<PerformanceMetrics>(
`${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<PerformanceAlert[]> {
const url = supplierId
? `${this.baseUrl}/${tenantId}/suppliers/analytics/performance/${supplierId}/alerts`
: `${this.baseUrl}/${tenantId}/suppliers/analytics/performance/alerts`;
return apiClient.get<PerformanceAlert[]>(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;