Start integrating the onboarding flow with backend 7
This commit is contained in:
275
frontend/src/api/services/alert_processor.ts
Normal file
275
frontend/src/api/services/alert_processor.ts
Normal file
@@ -0,0 +1,275 @@
|
||||
/**
|
||||
* Alert Processor service API implementation
|
||||
* Note: Alert Processor is a background service that doesn't expose direct HTTP APIs
|
||||
* This service provides utilities and types for working with alert processing
|
||||
*/
|
||||
|
||||
import { apiClient } from '../client/apiClient';
|
||||
import type {
|
||||
AlertMessage,
|
||||
AlertResponse,
|
||||
AlertUpdateRequest,
|
||||
AlertFilters,
|
||||
AlertQueryParams,
|
||||
AlertDashboardData,
|
||||
NotificationSettings,
|
||||
ChannelRoutingConfig,
|
||||
WebhookConfig,
|
||||
WebhookPayload,
|
||||
AlertProcessingStatus,
|
||||
ProcessingMetrics,
|
||||
SSEAlertMessage,
|
||||
PaginatedResponse,
|
||||
} from '../types/alert_processor';
|
||||
|
||||
class AlertProcessorService {
|
||||
private readonly baseUrl = '/alerts';
|
||||
private readonly notificationUrl = '/notifications';
|
||||
private readonly webhookUrl = '/webhooks';
|
||||
|
||||
// Alert Management (these would be exposed via other services like inventory, production, etc.)
|
||||
async getAlerts(
|
||||
tenantId: string,
|
||||
queryParams?: AlertQueryParams
|
||||
): Promise<PaginatedResponse<AlertResponse>> {
|
||||
const params = new URLSearchParams();
|
||||
|
||||
if (queryParams?.severity?.length) {
|
||||
queryParams.severity.forEach(s => params.append('severity', s));
|
||||
}
|
||||
if (queryParams?.type?.length) {
|
||||
queryParams.type.forEach(t => params.append('type', t));
|
||||
}
|
||||
if (queryParams?.service?.length) {
|
||||
queryParams.service.forEach(s => params.append('service', s));
|
||||
}
|
||||
if (queryParams?.item_type?.length) {
|
||||
queryParams.item_type.forEach(it => params.append('item_type', it));
|
||||
}
|
||||
if (queryParams?.date_from) params.append('date_from', queryParams.date_from);
|
||||
if (queryParams?.date_to) params.append('date_to', queryParams.date_to);
|
||||
if (queryParams?.status) params.append('status', queryParams.status);
|
||||
if (queryParams?.search) params.append('search', queryParams.search);
|
||||
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<AlertResponse>>(
|
||||
`${this.baseUrl}/tenants/${tenantId}${queryString}`
|
||||
);
|
||||
}
|
||||
|
||||
async getAlert(tenantId: string, alertId: string): Promise<AlertResponse> {
|
||||
return apiClient.get<AlertResponse>(
|
||||
`${this.baseUrl}/tenants/${tenantId}/${alertId}`
|
||||
);
|
||||
}
|
||||
|
||||
async updateAlert(
|
||||
tenantId: string,
|
||||
alertId: string,
|
||||
updateData: AlertUpdateRequest
|
||||
): Promise<AlertResponse> {
|
||||
return apiClient.put<AlertResponse>(
|
||||
`${this.baseUrl}/tenants/${tenantId}/${alertId}`,
|
||||
updateData
|
||||
);
|
||||
}
|
||||
|
||||
async dismissAlert(tenantId: string, alertId: string): Promise<AlertResponse> {
|
||||
return apiClient.put<AlertResponse>(
|
||||
`${this.baseUrl}/tenants/${tenantId}/${alertId}`,
|
||||
{ status: 'dismissed' }
|
||||
);
|
||||
}
|
||||
|
||||
async acknowledgeAlert(
|
||||
tenantId: string,
|
||||
alertId: string,
|
||||
notes?: string
|
||||
): Promise<AlertResponse> {
|
||||
return apiClient.put<AlertResponse>(
|
||||
`${this.baseUrl}/tenants/${tenantId}/${alertId}`,
|
||||
{ status: 'acknowledged', notes }
|
||||
);
|
||||
}
|
||||
|
||||
async resolveAlert(
|
||||
tenantId: string,
|
||||
alertId: string,
|
||||
notes?: string
|
||||
): Promise<AlertResponse> {
|
||||
return apiClient.put<AlertResponse>(
|
||||
`${this.baseUrl}/tenants/${tenantId}/${alertId}`,
|
||||
{ status: 'resolved', notes }
|
||||
);
|
||||
}
|
||||
|
||||
// Dashboard Data
|
||||
async getDashboardData(tenantId: string): Promise<AlertDashboardData> {
|
||||
return apiClient.get<AlertDashboardData>(
|
||||
`${this.baseUrl}/tenants/${tenantId}/dashboard`
|
||||
);
|
||||
}
|
||||
|
||||
// Notification Settings
|
||||
async getNotificationSettings(tenantId: string): Promise<NotificationSettings> {
|
||||
return apiClient.get<NotificationSettings>(
|
||||
`${this.notificationUrl}/tenants/${tenantId}/settings`
|
||||
);
|
||||
}
|
||||
|
||||
async updateNotificationSettings(
|
||||
tenantId: string,
|
||||
settings: Partial<NotificationSettings>
|
||||
): Promise<NotificationSettings> {
|
||||
return apiClient.put<NotificationSettings>(
|
||||
`${this.notificationUrl}/tenants/${tenantId}/settings`,
|
||||
settings
|
||||
);
|
||||
}
|
||||
|
||||
async getChannelRoutingConfig(): Promise<ChannelRoutingConfig> {
|
||||
return apiClient.get<ChannelRoutingConfig>(`${this.notificationUrl}/routing-config`);
|
||||
}
|
||||
|
||||
// Webhook Management
|
||||
async getWebhooks(tenantId: string): Promise<WebhookConfig[]> {
|
||||
return apiClient.get<WebhookConfig[]>(`${this.webhookUrl}/tenants/${tenantId}`);
|
||||
}
|
||||
|
||||
async createWebhook(
|
||||
tenantId: string,
|
||||
webhook: Omit<WebhookConfig, 'tenant_id'>
|
||||
): Promise<WebhookConfig> {
|
||||
return apiClient.post<WebhookConfig>(
|
||||
`${this.webhookUrl}/tenants/${tenantId}`,
|
||||
{ ...webhook, tenant_id: tenantId }
|
||||
);
|
||||
}
|
||||
|
||||
async updateWebhook(
|
||||
tenantId: string,
|
||||
webhookId: string,
|
||||
webhook: Partial<WebhookConfig>
|
||||
): Promise<WebhookConfig> {
|
||||
return apiClient.put<WebhookConfig>(
|
||||
`${this.webhookUrl}/tenants/${tenantId}/${webhookId}`,
|
||||
webhook
|
||||
);
|
||||
}
|
||||
|
||||
async deleteWebhook(tenantId: string, webhookId: string): Promise<{ message: string }> {
|
||||
return apiClient.delete<{ message: string }>(
|
||||
`${this.webhookUrl}/tenants/${tenantId}/${webhookId}`
|
||||
);
|
||||
}
|
||||
|
||||
async testWebhook(
|
||||
tenantId: string,
|
||||
webhookId: string
|
||||
): Promise<{ success: boolean; message: string }> {
|
||||
return apiClient.post<{ success: boolean; message: string }>(
|
||||
`${this.webhookUrl}/tenants/${tenantId}/${webhookId}/test`
|
||||
);
|
||||
}
|
||||
|
||||
// Processing Status and Metrics
|
||||
async getProcessingStatus(
|
||||
tenantId: string,
|
||||
alertId: string
|
||||
): Promise<AlertProcessingStatus> {
|
||||
return apiClient.get<AlertProcessingStatus>(
|
||||
`${this.baseUrl}/tenants/${tenantId}/${alertId}/processing-status`
|
||||
);
|
||||
}
|
||||
|
||||
async getProcessingMetrics(tenantId: string): Promise<ProcessingMetrics> {
|
||||
return apiClient.get<ProcessingMetrics>(
|
||||
`${this.baseUrl}/tenants/${tenantId}/processing-metrics`
|
||||
);
|
||||
}
|
||||
|
||||
// SSE (Server-Sent Events) connection helpers
|
||||
getSSEUrl(tenantId: string): string {
|
||||
const baseUrl = apiClient.getAxiosInstance().defaults.baseURL;
|
||||
return `${baseUrl}/sse/tenants/${tenantId}/alerts`;
|
||||
}
|
||||
|
||||
createSSEConnection(tenantId: string, token?: string): EventSource {
|
||||
const sseUrl = this.getSSEUrl(tenantId);
|
||||
const urlWithToken = token ? `${sseUrl}?token=${token}` : sseUrl;
|
||||
|
||||
return new EventSource(urlWithToken);
|
||||
}
|
||||
|
||||
// Utility methods for working with alerts
|
||||
static formatAlertMessage(alert: AlertMessage): string {
|
||||
return `[${alert.severity.toUpperCase()}] ${alert.title}: ${alert.message}`;
|
||||
}
|
||||
|
||||
static getAlertIcon(alert: AlertMessage): string {
|
||||
const iconMap: Record<string, string> = {
|
||||
inventory_low: '📦',
|
||||
quality_issue: '⚠️',
|
||||
delivery_delay: '🚚',
|
||||
production_delay: '🏭',
|
||||
equipment_failure: '🔧',
|
||||
food_safety: '🦠',
|
||||
temperature_alert: '🌡️',
|
||||
expiry_warning: '⏰',
|
||||
forecast_accuracy: '📊',
|
||||
demand_spike: '📈',
|
||||
supplier_issue: '🏢',
|
||||
cost_optimization: '💰',
|
||||
revenue_opportunity: '💡',
|
||||
};
|
||||
return iconMap[alert.type] || '🔔';
|
||||
}
|
||||
|
||||
static getSeverityColor(severity: string): string {
|
||||
const colorMap: Record<string, string> = {
|
||||
urgent: '#dc2626', // red-600
|
||||
high: '#ea580c', // orange-600
|
||||
medium: '#d97706', // amber-600
|
||||
low: '#65a30d', // lime-600
|
||||
};
|
||||
return colorMap[severity] || '#6b7280'; // gray-500
|
||||
}
|
||||
|
||||
// Message queuing helpers (for RabbitMQ integration)
|
||||
static createAlertMessage(
|
||||
tenantId: string,
|
||||
alert: Omit<AlertMessage, 'id' | 'tenant_id' | 'timestamp'>
|
||||
): AlertMessage {
|
||||
return {
|
||||
id: `alert_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
||||
tenant_id: tenantId,
|
||||
timestamp: new Date().toISOString(),
|
||||
...alert,
|
||||
};
|
||||
}
|
||||
|
||||
static validateWebhookSignature(
|
||||
payload: string,
|
||||
signature: string,
|
||||
secret: string
|
||||
): boolean {
|
||||
// This would typically use crypto.createHmac for HMAC-SHA256 verification
|
||||
// Implementation depends on the specific signature algorithm used
|
||||
const crypto = window.crypto || (window as any).msCrypto;
|
||||
if (!crypto?.subtle) {
|
||||
console.warn('WebCrypto API not available for signature verification');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Simplified example - actual implementation would use proper HMAC verification
|
||||
return signature.length > 0 && secret.length > 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Create and export singleton instance
|
||||
export const alertProcessorService = new AlertProcessorService();
|
||||
export default alertProcessorService;
|
||||
336
frontend/src/api/services/suppliers.ts
Normal file
336
frontend/src/api/services/suppliers.ts
Normal file
@@ -0,0 +1,336 @@
|
||||
/**
|
||||
* 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;
|
||||
132
frontend/src/api/services/training.ts
Normal file
132
frontend/src/api/services/training.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* Training service API implementation
|
||||
* Handles all training-related backend communications
|
||||
*/
|
||||
|
||||
import { apiClient } from '../client/apiClient';
|
||||
import type {
|
||||
TrainingJobRequest,
|
||||
TrainingJobResponse,
|
||||
TrainingJobStatus,
|
||||
SingleProductTrainingRequest,
|
||||
ActiveModelResponse,
|
||||
ModelMetricsResponse,
|
||||
TrainedModelResponse,
|
||||
TenantStatistics,
|
||||
ModelPerformanceResponse,
|
||||
ModelsQueryParams,
|
||||
PaginatedResponse,
|
||||
} from '../types/training';
|
||||
|
||||
class TrainingService {
|
||||
private readonly baseUrl = '/tenants';
|
||||
|
||||
// Training Jobs
|
||||
async createTrainingJob(
|
||||
tenantId: string,
|
||||
request: TrainingJobRequest
|
||||
): Promise<TrainingJobResponse> {
|
||||
return apiClient.post<TrainingJobResponse>(
|
||||
`${this.baseUrl}/${tenantId}/training/jobs`,
|
||||
request
|
||||
);
|
||||
}
|
||||
|
||||
async trainSingleProduct(
|
||||
tenantId: string,
|
||||
inventoryProductId: string,
|
||||
request: SingleProductTrainingRequest
|
||||
): Promise<TrainingJobResponse> {
|
||||
return apiClient.post<TrainingJobResponse>(
|
||||
`${this.baseUrl}/${tenantId}/training/products/${inventoryProductId}`,
|
||||
request
|
||||
);
|
||||
}
|
||||
|
||||
async getTrainingJobStatus(
|
||||
tenantId: string,
|
||||
jobId: string
|
||||
): Promise<TrainingJobStatus> {
|
||||
return apiClient.get<TrainingJobStatus>(
|
||||
`${this.baseUrl}/${tenantId}/training/jobs/${jobId}/status`
|
||||
);
|
||||
}
|
||||
|
||||
// Models Management
|
||||
async getActiveModel(
|
||||
tenantId: string,
|
||||
inventoryProductId: string
|
||||
): Promise<ActiveModelResponse> {
|
||||
return apiClient.get<ActiveModelResponse>(
|
||||
`${this.baseUrl}/${tenantId}/models/${inventoryProductId}/active`
|
||||
);
|
||||
}
|
||||
|
||||
async getModelMetrics(
|
||||
tenantId: string,
|
||||
modelId: string
|
||||
): Promise<ModelMetricsResponse> {
|
||||
return apiClient.get<ModelMetricsResponse>(
|
||||
`${this.baseUrl}/${tenantId}/models/${modelId}/metrics`
|
||||
);
|
||||
}
|
||||
|
||||
async getModels(
|
||||
tenantId: string,
|
||||
queryParams?: ModelsQueryParams
|
||||
): Promise<PaginatedResponse<TrainedModelResponse>> {
|
||||
const params = new URLSearchParams();
|
||||
if (queryParams?.status) params.append('status', queryParams.status);
|
||||
if (queryParams?.model_type) params.append('model_type', queryParams.model_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<TrainedModelResponse>>(
|
||||
`${this.baseUrl}/${tenantId}/models${queryString}`
|
||||
);
|
||||
}
|
||||
|
||||
async getModelPerformance(
|
||||
tenantId: string,
|
||||
modelId: string
|
||||
): Promise<ModelPerformanceResponse> {
|
||||
return apiClient.get<ModelPerformanceResponse>(
|
||||
`${this.baseUrl}/${tenantId}/models/${modelId}/performance`
|
||||
);
|
||||
}
|
||||
|
||||
// Statistics and Analytics
|
||||
async getTenantStatistics(tenantId: string): Promise<TenantStatistics> {
|
||||
return apiClient.get<TenantStatistics>(
|
||||
`${this.baseUrl}/${tenantId}/statistics`
|
||||
);
|
||||
}
|
||||
|
||||
// Admin endpoints (requires admin role)
|
||||
async deleteAllTenantModels(tenantId: string): Promise<{ message: string }> {
|
||||
return apiClient.delete<{ message: string }>(`/models/tenant/${tenantId}`);
|
||||
}
|
||||
|
||||
// WebSocket connection helper (for real-time training updates)
|
||||
getTrainingWebSocketUrl(tenantId: string, jobId: string): string {
|
||||
const baseWsUrl = apiClient.getAxiosInstance().defaults.baseURL?.replace(/^http/, 'ws');
|
||||
return `${baseWsUrl}/ws/tenants/${tenantId}/training/jobs/${jobId}/live`;
|
||||
}
|
||||
|
||||
// Helper method to construct WebSocket connection
|
||||
createWebSocketConnection(
|
||||
tenantId: string,
|
||||
jobId: string,
|
||||
token?: string
|
||||
): WebSocket {
|
||||
const wsUrl = this.getTrainingWebSocketUrl(tenantId, jobId);
|
||||
const urlWithToken = token ? `${wsUrl}?token=${token}` : wsUrl;
|
||||
|
||||
return new WebSocket(urlWithToken);
|
||||
}
|
||||
}
|
||||
|
||||
// Create and export singleton instance
|
||||
export const trainingService = new TrainingService();
|
||||
export default trainingService;
|
||||
Reference in New Issue
Block a user