Start integrating the onboarding flow with backend 7

This commit is contained in:
Urtzi Alfaro
2025-09-05 22:46:28 +02:00
parent 069954981a
commit 548a2ddd11
28 changed files with 5544 additions and 1014 deletions

View File

@@ -0,0 +1,545 @@
/**
* Alert Processor React Query hooks
* Provides data fetching, caching, and state management for alert processing operations
*/
import { useMutation, useQuery, useQueryClient, UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';
import { alertProcessorService } from '../services/alert_processor';
import { ApiError } from '../client/apiClient';
import type {
AlertResponse,
AlertUpdateRequest,
AlertQueryParams,
AlertDashboardData,
NotificationSettings,
ChannelRoutingConfig,
WebhookConfig,
AlertProcessingStatus,
ProcessingMetrics,
PaginatedResponse,
} from '../types/alert_processor';
// Query Keys Factory
export const alertProcessorKeys = {
all: ['alert-processor'] as const,
alerts: {
all: () => [...alertProcessorKeys.all, 'alerts'] as const,
lists: () => [...alertProcessorKeys.alerts.all(), 'list'] as const,
list: (tenantId: string, params?: AlertQueryParams) =>
[...alertProcessorKeys.alerts.lists(), tenantId, params] as const,
details: () => [...alertProcessorKeys.alerts.all(), 'detail'] as const,
detail: (tenantId: string, alertId: string) =>
[...alertProcessorKeys.alerts.details(), tenantId, alertId] as const,
dashboard: (tenantId: string) =>
[...alertProcessorKeys.alerts.all(), 'dashboard', tenantId] as const,
processingStatus: (tenantId: string, alertId: string) =>
[...alertProcessorKeys.alerts.all(), 'processing-status', tenantId, alertId] as const,
},
notifications: {
all: () => [...alertProcessorKeys.all, 'notifications'] as const,
settings: (tenantId: string) =>
[...alertProcessorKeys.notifications.all(), 'settings', tenantId] as const,
routing: () =>
[...alertProcessorKeys.notifications.all(), 'routing-config'] as const,
},
webhooks: {
all: () => [...alertProcessorKeys.all, 'webhooks'] as const,
list: (tenantId: string) =>
[...alertProcessorKeys.webhooks.all(), tenantId] as const,
},
metrics: {
all: () => [...alertProcessorKeys.all, 'metrics'] as const,
processing: (tenantId: string) =>
[...alertProcessorKeys.metrics.all(), 'processing', tenantId] as const,
},
} as const;
// Alert Queries
export const useAlerts = (
tenantId: string,
queryParams?: AlertQueryParams,
options?: Omit<UseQueryOptions<PaginatedResponse<AlertResponse>, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<PaginatedResponse<AlertResponse>, ApiError>({
queryKey: alertProcessorKeys.alerts.list(tenantId, queryParams),
queryFn: () => alertProcessorService.getAlerts(tenantId, queryParams),
enabled: !!tenantId,
staleTime: 30 * 1000, // 30 seconds
...options,
});
};
export const useAlert = (
tenantId: string,
alertId: string,
options?: Omit<UseQueryOptions<AlertResponse, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<AlertResponse, ApiError>({
queryKey: alertProcessorKeys.alerts.detail(tenantId, alertId),
queryFn: () => alertProcessorService.getAlert(tenantId, alertId),
enabled: !!tenantId && !!alertId,
staleTime: 1 * 60 * 1000, // 1 minute
...options,
});
};
export const useAlertDashboardData = (
tenantId: string,
options?: Omit<UseQueryOptions<AlertDashboardData, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<AlertDashboardData, ApiError>({
queryKey: alertProcessorKeys.alerts.dashboard(tenantId),
queryFn: () => alertProcessorService.getDashboardData(tenantId),
enabled: !!tenantId,
staleTime: 30 * 1000, // 30 seconds
refetchInterval: 1 * 60 * 1000, // Refresh every minute
...options,
});
};
export const useAlertProcessingStatus = (
tenantId: string,
alertId: string,
options?: Omit<UseQueryOptions<AlertProcessingStatus, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<AlertProcessingStatus, ApiError>({
queryKey: alertProcessorKeys.alerts.processingStatus(tenantId, alertId),
queryFn: () => alertProcessorService.getProcessingStatus(tenantId, alertId),
enabled: !!tenantId && !!alertId,
staleTime: 10 * 1000, // 10 seconds
refetchInterval: 30 * 1000, // Poll every 30 seconds
...options,
});
};
// Notification Queries
export const useNotificationSettings = (
tenantId: string,
options?: Omit<UseQueryOptions<NotificationSettings, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<NotificationSettings, ApiError>({
queryKey: alertProcessorKeys.notifications.settings(tenantId),
queryFn: () => alertProcessorService.getNotificationSettings(tenantId),
enabled: !!tenantId,
staleTime: 5 * 60 * 1000, // 5 minutes
...options,
});
};
export const useChannelRoutingConfig = (
options?: Omit<UseQueryOptions<ChannelRoutingConfig, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<ChannelRoutingConfig, ApiError>({
queryKey: alertProcessorKeys.notifications.routing(),
queryFn: () => alertProcessorService.getChannelRoutingConfig(),
staleTime: 10 * 60 * 1000, // 10 minutes
...options,
});
};
// Webhook Queries
export const useWebhooks = (
tenantId: string,
options?: Omit<UseQueryOptions<WebhookConfig[], ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<WebhookConfig[], ApiError>({
queryKey: alertProcessorKeys.webhooks.list(tenantId),
queryFn: () => alertProcessorService.getWebhooks(tenantId),
enabled: !!tenantId,
staleTime: 2 * 60 * 1000, // 2 minutes
...options,
});
};
// Metrics Queries
export const useProcessingMetrics = (
tenantId: string,
options?: Omit<UseQueryOptions<ProcessingMetrics, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<ProcessingMetrics, ApiError>({
queryKey: alertProcessorKeys.metrics.processing(tenantId),
queryFn: () => alertProcessorService.getProcessingMetrics(tenantId),
enabled: !!tenantId,
staleTime: 1 * 60 * 1000, // 1 minute
...options,
});
};
// Alert Mutations
export const useUpdateAlert = (
options?: UseMutationOptions<
AlertResponse,
ApiError,
{ tenantId: string; alertId: string; updateData: AlertUpdateRequest }
>
) => {
const queryClient = useQueryClient();
return useMutation<
AlertResponse,
ApiError,
{ tenantId: string; alertId: string; updateData: AlertUpdateRequest }
>({
mutationFn: ({ tenantId, alertId, updateData }) =>
alertProcessorService.updateAlert(tenantId, alertId, updateData),
onSuccess: (data, { tenantId, alertId }) => {
// Update the alert in cache
queryClient.setQueryData(
alertProcessorKeys.alerts.detail(tenantId, alertId),
data
);
// Invalidate alerts list to reflect the change
queryClient.invalidateQueries({
queryKey: alertProcessorKeys.alerts.lists()
});
// Invalidate dashboard data
queryClient.invalidateQueries({
queryKey: alertProcessorKeys.alerts.dashboard(tenantId)
});
},
...options,
});
};
export const useDismissAlert = (
options?: UseMutationOptions<
AlertResponse,
ApiError,
{ tenantId: string; alertId: string }
>
) => {
const queryClient = useQueryClient();
return useMutation<
AlertResponse,
ApiError,
{ tenantId: string; alertId: string }
>({
mutationFn: ({ tenantId, alertId }) =>
alertProcessorService.dismissAlert(tenantId, alertId),
onSuccess: (data, { tenantId, alertId }) => {
// Update the alert in cache
queryClient.setQueryData(
alertProcessorKeys.alerts.detail(tenantId, alertId),
data
);
// Invalidate related queries
queryClient.invalidateQueries({
queryKey: alertProcessorKeys.alerts.lists()
});
queryClient.invalidateQueries({
queryKey: alertProcessorKeys.alerts.dashboard(tenantId)
});
},
...options,
});
};
export const useAcknowledgeAlert = (
options?: UseMutationOptions<
AlertResponse,
ApiError,
{ tenantId: string; alertId: string; notes?: string }
>
) => {
const queryClient = useQueryClient();
return useMutation<
AlertResponse,
ApiError,
{ tenantId: string; alertId: string; notes?: string }
>({
mutationFn: ({ tenantId, alertId, notes }) =>
alertProcessorService.acknowledgeAlert(tenantId, alertId, notes),
onSuccess: (data, { tenantId, alertId }) => {
// Update the alert in cache
queryClient.setQueryData(
alertProcessorKeys.alerts.detail(tenantId, alertId),
data
);
// Invalidate related queries
queryClient.invalidateQueries({
queryKey: alertProcessorKeys.alerts.lists()
});
queryClient.invalidateQueries({
queryKey: alertProcessorKeys.alerts.dashboard(tenantId)
});
},
...options,
});
};
export const useResolveAlert = (
options?: UseMutationOptions<
AlertResponse,
ApiError,
{ tenantId: string; alertId: string; notes?: string }
>
) => {
const queryClient = useQueryClient();
return useMutation<
AlertResponse,
ApiError,
{ tenantId: string; alertId: string; notes?: string }
>({
mutationFn: ({ tenantId, alertId, notes }) =>
alertProcessorService.resolveAlert(tenantId, alertId, notes),
onSuccess: (data, { tenantId, alertId }) => {
// Update the alert in cache
queryClient.setQueryData(
alertProcessorKeys.alerts.detail(tenantId, alertId),
data
);
// Invalidate related queries
queryClient.invalidateQueries({
queryKey: alertProcessorKeys.alerts.lists()
});
queryClient.invalidateQueries({
queryKey: alertProcessorKeys.alerts.dashboard(tenantId)
});
},
...options,
});
};
// Notification Settings Mutations
export const useUpdateNotificationSettings = (
options?: UseMutationOptions<
NotificationSettings,
ApiError,
{ tenantId: string; settings: Partial<NotificationSettings> }
>
) => {
const queryClient = useQueryClient();
return useMutation<
NotificationSettings,
ApiError,
{ tenantId: string; settings: Partial<NotificationSettings> }
>({
mutationFn: ({ tenantId, settings }) =>
alertProcessorService.updateNotificationSettings(tenantId, settings),
onSuccess: (data, { tenantId }) => {
// Update settings in cache
queryClient.setQueryData(
alertProcessorKeys.notifications.settings(tenantId),
data
);
},
...options,
});
};
// Webhook Mutations
export const useCreateWebhook = (
options?: UseMutationOptions<
WebhookConfig,
ApiError,
{ tenantId: string; webhook: Omit<WebhookConfig, 'tenant_id'> }
>
) => {
const queryClient = useQueryClient();
return useMutation<
WebhookConfig,
ApiError,
{ tenantId: string; webhook: Omit<WebhookConfig, 'tenant_id'> }
>({
mutationFn: ({ tenantId, webhook }) =>
alertProcessorService.createWebhook(tenantId, webhook),
onSuccess: (data, { tenantId }) => {
// Add the new webhook to the list
queryClient.setQueryData(
alertProcessorKeys.webhooks.list(tenantId),
(oldData: WebhookConfig[] | undefined) => [...(oldData || []), data]
);
},
...options,
});
};
export const useUpdateWebhook = (
options?: UseMutationOptions<
WebhookConfig,
ApiError,
{ tenantId: string; webhookId: string; webhook: Partial<WebhookConfig> }
>
) => {
const queryClient = useQueryClient();
return useMutation<
WebhookConfig,
ApiError,
{ tenantId: string; webhookId: string; webhook: Partial<WebhookConfig> }
>({
mutationFn: ({ tenantId, webhookId, webhook }) =>
alertProcessorService.updateWebhook(tenantId, webhookId, webhook),
onSuccess: (data, { tenantId }) => {
// Update the webhook in the list
queryClient.setQueryData(
alertProcessorKeys.webhooks.list(tenantId),
(oldData: WebhookConfig[] | undefined) =>
oldData?.map(hook => hook.webhook_url === data.webhook_url ? data : hook) || []
);
},
...options,
});
};
export const useDeleteWebhook = (
options?: UseMutationOptions<
{ message: string },
ApiError,
{ tenantId: string; webhookId: string }
>
) => {
const queryClient = useQueryClient();
return useMutation<
{ message: string },
ApiError,
{ tenantId: string; webhookId: string }
>({
mutationFn: ({ tenantId, webhookId }) =>
alertProcessorService.deleteWebhook(tenantId, webhookId),
onSuccess: (_, { tenantId, webhookId }) => {
// Remove the webhook from the list
queryClient.setQueryData(
alertProcessorKeys.webhooks.list(tenantId),
(oldData: WebhookConfig[] | undefined) =>
oldData?.filter(hook => hook.webhook_url !== webhookId) || []
);
},
...options,
});
};
export const useTestWebhook = (
options?: UseMutationOptions<
{ success: boolean; message: string },
ApiError,
{ tenantId: string; webhookId: string }
>
) => {
return useMutation<
{ success: boolean; message: string },
ApiError,
{ tenantId: string; webhookId: string }
>({
mutationFn: ({ tenantId, webhookId }) =>
alertProcessorService.testWebhook(tenantId, webhookId),
...options,
});
};
// SSE Hook for Real-time Alert Updates
export const useAlertSSE = (
tenantId: string,
token?: string,
options?: {
onAlert?: (alert: AlertResponse) => void;
onRecommendation?: (recommendation: AlertResponse) => void;
onAlertUpdate?: (update: AlertResponse) => void;
onSystemStatus?: (status: any) => void;
}
) => {
const queryClient = useQueryClient();
return useQuery({
queryKey: ['alert-sse', tenantId],
queryFn: () => {
return new Promise((resolve, reject) => {
try {
const eventSource = alertProcessorService.createSSEConnection(tenantId, token);
eventSource.onopen = () => {
console.log('Alert SSE connected');
};
eventSource.onmessage = (event) => {
try {
const message = JSON.parse(event.data);
// Invalidate dashboard data on new alerts/updates
queryClient.invalidateQueries({
queryKey: alertProcessorKeys.alerts.dashboard(tenantId)
});
// Invalidate alerts list
queryClient.invalidateQueries({
queryKey: alertProcessorKeys.alerts.lists()
});
// Call appropriate callback based on message type
switch (message.type) {
case 'alert':
options?.onAlert?.(message.data);
break;
case 'recommendation':
options?.onRecommendation?.(message.data);
break;
case 'alert_update':
options?.onAlertUpdate?.(message.data);
// Update specific alert in cache
if (message.data.id) {
queryClient.setQueryData(
alertProcessorKeys.alerts.detail(tenantId, message.data.id),
message.data
);
}
break;
case 'system_status':
options?.onSystemStatus?.(message.data);
break;
}
} catch (error) {
console.error('Error parsing SSE message:', error);
}
};
eventSource.onerror = (error) => {
console.error('Alert SSE error:', error);
reject(error);
};
// Return cleanup function
return () => {
eventSource.close();
};
} catch (error) {
reject(error);
}
});
},
enabled: !!tenantId,
refetchOnWindowFocus: false,
retry: false,
staleTime: Infinity,
});
};
// Utility Hooks
export const useActiveAlertsCount = (tenantId: string) => {
const { data: dashboardData } = useAlertDashboardData(tenantId);
return dashboardData?.active_alerts?.length || 0;
};
export const useAlertsByPriority = (tenantId: string) => {
const { data: dashboardData } = useAlertDashboardData(tenantId);
return dashboardData?.severity_counts || {};
};
export const useUnreadAlertsCount = (tenantId: string) => {
const { data: alerts } = useAlerts(tenantId, {
status: 'active',
limit: 1000 // Get all active alerts to count unread ones
});
return alerts?.data?.filter(alert => alert.status === 'active')?.length || 0;
};

View File

@@ -0,0 +1,212 @@
/**
* Data Import React Query hooks
* Provides data fetching, caching, and state management for data import operations
*/
import { useMutation, useQuery, UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';
import { dataImportService } from '../services/dataImport';
import { ApiError } from '../client/apiClient';
import type {
ImportValidationResponse,
ImportProcessResponse,
ImportStatusResponse,
} from '../types/dataImport';
// Query Keys Factory
export const dataImportKeys = {
all: ['data-import'] as const,
status: (tenantId: string, importId: string) =>
[...dataImportKeys.all, 'status', tenantId, importId] as const,
} as const;
// Status Query
export const useImportStatus = (
tenantId: string,
importId: string,
options?: Omit<UseQueryOptions<ImportStatusResponse, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<ImportStatusResponse, ApiError>({
queryKey: dataImportKeys.status(tenantId, importId),
queryFn: () => dataImportService.getImportStatus(tenantId, importId),
enabled: !!tenantId && !!importId,
refetchInterval: 5000, // Poll every 5 seconds for active imports
staleTime: 1000, // Consider data stale after 1 second
...options,
});
};
// Validation Mutations
export const useValidateJsonData = (
options?: UseMutationOptions<
ImportValidationResponse,
ApiError,
{ tenantId: string; data: any }
>
) => {
return useMutation<
ImportValidationResponse,
ApiError,
{ tenantId: string; data: any }
>({
mutationFn: ({ tenantId, data }) => dataImportService.validateJsonData(tenantId, data),
...options,
});
};
export const useValidateCsvFile = (
options?: UseMutationOptions<
ImportValidationResponse,
ApiError,
{ tenantId: string; file: File }
>
) => {
return useMutation<
ImportValidationResponse,
ApiError,
{ tenantId: string; file: File }
>({
mutationFn: ({ tenantId, file }) => dataImportService.validateCsvFile(tenantId, file),
...options,
});
};
// Import Mutations
export const useImportJsonData = (
options?: UseMutationOptions<
ImportProcessResponse,
ApiError,
{ tenantId: string; data: any; options?: { skip_validation?: boolean; chunk_size?: number } }
>
) => {
return useMutation<
ImportProcessResponse,
ApiError,
{ tenantId: string; data: any; options?: { skip_validation?: boolean; chunk_size?: number } }
>({
mutationFn: ({ tenantId, data, options: importOptions }) =>
dataImportService.importJsonData(tenantId, data, importOptions),
...options,
});
};
export const useImportCsvFile = (
options?: UseMutationOptions<
ImportProcessResponse,
ApiError,
{ tenantId: string; file: File; options?: { skip_validation?: boolean; chunk_size?: number } }
>
) => {
return useMutation<
ImportProcessResponse,
ApiError,
{ tenantId: string; file: File; options?: { skip_validation?: boolean; chunk_size?: number } }
>({
mutationFn: ({ tenantId, file, options: importOptions }) =>
dataImportService.importCsvFile(tenantId, file, importOptions),
...options,
});
};
// Combined validation and import hook for easier use
export const useValidateAndImportFile = () => {
const validateCsv = useValidateCsvFile();
const validateJson = useValidateJsonData();
const importCsv = useImportCsvFile();
const importJson = useImportJsonData();
const processFile = async (
tenantId: string,
file: File,
options?: {
skipValidation?: boolean;
chunkSize?: number;
onProgress?: (stage: string, progress: number, message: string) => void;
}
): Promise<{
validationResult?: ImportValidationResponse;
importResult?: ImportProcessResponse;
success: boolean;
error?: string;
}> => {
try {
let validationResult: ImportValidationResponse | undefined;
// Step 1: Validation (unless skipped)
if (!options?.skipValidation) {
options?.onProgress?.('validating', 20, 'Validando estructura del archivo...');
const fileExtension = file.name.split('.').pop()?.toLowerCase();
if (fileExtension === 'csv') {
validationResult = await validateCsv.mutateAsync({ tenantId, file });
} else if (fileExtension === 'json') {
const jsonData = await file.text().then(text => JSON.parse(text));
validationResult = await validateJson.mutateAsync({ tenantId, data: jsonData });
} else {
throw new Error('Formato de archivo no soportado. Use CSV o JSON.');
}
options?.onProgress?.('validating', 50, 'Verificando integridad de datos...');
if (!validationResult.valid) {
throw new Error(`Archivo inválido: ${validationResult.errors?.join(', ')}`);
}
}
// Step 2: Import
options?.onProgress?.('importing', 70, 'Importando datos...');
const importOptions = {
skip_validation: options?.skipValidation || false,
chunk_size: options?.chunkSize,
};
let importResult: ImportProcessResponse;
const fileExtension = file.name.split('.').pop()?.toLowerCase();
if (fileExtension === 'csv') {
importResult = await importCsv.mutateAsync({
tenantId,
file,
options: importOptions
});
} else if (fileExtension === 'json') {
const jsonData = await file.text().then(text => JSON.parse(text));
importResult = await importJson.mutateAsync({
tenantId,
data: jsonData,
options: importOptions
});
} else {
throw new Error('Formato de archivo no soportado. Use CSV o JSON.');
}
options?.onProgress?.('completed', 100, 'Importación completada');
return {
validationResult,
importResult,
success: true,
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Error procesando archivo';
options?.onProgress?.('error', 0, errorMessage);
return {
success: false,
error: errorMessage,
};
}
};
return {
processFile,
validateCsv,
validateJson,
importCsv,
importJson,
isValidating: validateCsv.isPending || validateJson.isPending,
isImporting: importCsv.isPending || importJson.isPending,
isLoading: validateCsv.isPending || validateJson.isPending || importCsv.isPending || importJson.isPending,
error: validateCsv.error || validateJson.error || importCsv.error || importJson.error,
};
};

View File

@@ -0,0 +1,650 @@
/**
* Suppliers React Query hooks
* Provides data fetching, caching, and state management for supplier operations
*/
import { useMutation, useQuery, useQueryClient, UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';
import { suppliersService } from '../services/suppliers';
import { ApiError } 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,
} from '../types/suppliers';
// Query Keys Factory
export const suppliersKeys = {
all: ['suppliers'] as const,
suppliers: {
all: () => [...suppliersKeys.all, 'suppliers'] as const,
lists: () => [...suppliersKeys.suppliers.all(), 'list'] as const,
list: (tenantId: string, params?: SupplierQueryParams) =>
[...suppliersKeys.suppliers.lists(), tenantId, params] as const,
details: () => [...suppliersKeys.suppliers.all(), 'detail'] as const,
detail: (tenantId: string, supplierId: string) =>
[...suppliersKeys.suppliers.details(), tenantId, supplierId] as const,
statistics: (tenantId: string) =>
[...suppliersKeys.suppliers.all(), 'statistics', tenantId] as const,
top: (tenantId: string) =>
[...suppliersKeys.suppliers.all(), 'top', tenantId] as const,
byType: (tenantId: string, supplierType: string) =>
[...suppliersKeys.suppliers.all(), 'by-type', tenantId, supplierType] as const,
},
purchaseOrders: {
all: () => [...suppliersKeys.all, 'purchase-orders'] as const,
lists: () => [...suppliersKeys.purchaseOrders.all(), 'list'] as const,
list: (params?: PurchaseOrderQueryParams) =>
[...suppliersKeys.purchaseOrders.lists(), params] as const,
details: () => [...suppliersKeys.purchaseOrders.all(), 'detail'] as const,
detail: (orderId: string) =>
[...suppliersKeys.purchaseOrders.details(), orderId] as const,
},
deliveries: {
all: () => [...suppliersKeys.all, 'deliveries'] as const,
lists: () => [...suppliersKeys.deliveries.all(), 'list'] as const,
list: (params?: DeliveryQueryParams) =>
[...suppliersKeys.deliveries.lists(), params] as const,
details: () => [...suppliersKeys.deliveries.all(), 'detail'] as const,
detail: (deliveryId: string) =>
[...suppliersKeys.deliveries.details(), deliveryId] as const,
},
performance: {
all: () => [...suppliersKeys.all, 'performance'] as const,
metrics: (tenantId: string, supplierId: string) =>
[...suppliersKeys.performance.all(), 'metrics', tenantId, supplierId] as const,
alerts: (tenantId: string, supplierId?: string) =>
[...suppliersKeys.performance.all(), 'alerts', tenantId, supplierId] as const,
},
} as const;
// Supplier Queries
export const useSuppliers = (
tenantId: string,
queryParams?: SupplierQueryParams,
options?: Omit<UseQueryOptions<PaginatedResponse<SupplierSummary>, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<PaginatedResponse<SupplierSummary>, ApiError>({
queryKey: suppliersKeys.suppliers.list(tenantId, queryParams),
queryFn: () => suppliersService.getSuppliers(tenantId, queryParams),
enabled: !!tenantId,
staleTime: 2 * 60 * 1000, // 2 minutes
...options,
});
};
export const useSupplier = (
tenantId: string,
supplierId: string,
options?: Omit<UseQueryOptions<SupplierResponse, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<SupplierResponse, ApiError>({
queryKey: suppliersKeys.suppliers.detail(tenantId, supplierId),
queryFn: () => suppliersService.getSupplier(tenantId, supplierId),
enabled: !!tenantId && !!supplierId,
staleTime: 5 * 60 * 1000, // 5 minutes
...options,
});
};
export const useSupplierStatistics = (
tenantId: string,
options?: Omit<UseQueryOptions<SupplierStatistics, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<SupplierStatistics, ApiError>({
queryKey: suppliersKeys.suppliers.statistics(tenantId),
queryFn: () => suppliersService.getSupplierStatistics(tenantId),
enabled: !!tenantId,
staleTime: 5 * 60 * 1000, // 5 minutes
...options,
});
};
export const useActiveSuppliers = (
tenantId: string,
queryParams?: Omit<SupplierQueryParams, 'status'>,
options?: Omit<UseQueryOptions<PaginatedResponse<SupplierSummary>, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<PaginatedResponse<SupplierSummary>, ApiError>({
queryKey: suppliersKeys.suppliers.list(tenantId, { ...queryParams, status: 'active' }),
queryFn: () => suppliersService.getActiveSuppliers(tenantId, queryParams),
enabled: !!tenantId,
staleTime: 2 * 60 * 1000, // 2 minutes
...options,
});
};
export const useTopSuppliers = (
tenantId: string,
options?: Omit<UseQueryOptions<TopSuppliersResponse, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<TopSuppliersResponse, ApiError>({
queryKey: suppliersKeys.suppliers.top(tenantId),
queryFn: () => suppliersService.getTopSuppliers(tenantId),
enabled: !!tenantId,
staleTime: 10 * 60 * 1000, // 10 minutes
...options,
});
};
export const usePendingApprovalSuppliers = (
tenantId: string,
options?: Omit<UseQueryOptions<PaginatedResponse<SupplierSummary>, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<PaginatedResponse<SupplierSummary>, ApiError>({
queryKey: suppliersKeys.suppliers.list(tenantId, { status: 'pending_approval' }),
queryFn: () => suppliersService.getPendingApprovalSuppliers(tenantId),
enabled: !!tenantId,
staleTime: 1 * 60 * 1000, // 1 minute
...options,
});
};
export const useSuppliersByType = (
tenantId: string,
supplierType: string,
queryParams?: Omit<SupplierQueryParams, 'supplier_type'>,
options?: Omit<UseQueryOptions<PaginatedResponse<SupplierSummary>, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<PaginatedResponse<SupplierSummary>, ApiError>({
queryKey: suppliersKeys.suppliers.byType(tenantId, supplierType),
queryFn: () => suppliersService.getSuppliersByType(tenantId, supplierType, queryParams),
enabled: !!tenantId && !!supplierType,
staleTime: 2 * 60 * 1000, // 2 minutes
...options,
});
};
// Purchase Order Queries
export const usePurchaseOrders = (
queryParams?: PurchaseOrderQueryParams,
options?: Omit<UseQueryOptions<PaginatedResponse<PurchaseOrderResponse>, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<PaginatedResponse<PurchaseOrderResponse>, ApiError>({
queryKey: suppliersKeys.purchaseOrders.list(queryParams),
queryFn: () => suppliersService.getPurchaseOrders(queryParams),
staleTime: 1 * 60 * 1000, // 1 minute
...options,
});
};
export const usePurchaseOrder = (
orderId: string,
options?: Omit<UseQueryOptions<PurchaseOrderResponse, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<PurchaseOrderResponse, ApiError>({
queryKey: suppliersKeys.purchaseOrders.detail(orderId),
queryFn: () => suppliersService.getPurchaseOrder(orderId),
enabled: !!orderId,
staleTime: 2 * 60 * 1000, // 2 minutes
...options,
});
};
// Delivery Queries
export const useDeliveries = (
queryParams?: DeliveryQueryParams,
options?: Omit<UseQueryOptions<PaginatedResponse<DeliveryResponse>, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<PaginatedResponse<DeliveryResponse>, ApiError>({
queryKey: suppliersKeys.deliveries.list(queryParams),
queryFn: () => suppliersService.getDeliveries(queryParams),
staleTime: 1 * 60 * 1000, // 1 minute
...options,
});
};
export const useDelivery = (
deliveryId: string,
options?: Omit<UseQueryOptions<DeliveryResponse, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<DeliveryResponse, ApiError>({
queryKey: suppliersKeys.deliveries.detail(deliveryId),
queryFn: () => suppliersService.getDelivery(deliveryId),
enabled: !!deliveryId,
staleTime: 30 * 1000, // 30 seconds
...options,
});
};
// Performance Queries
export const useSupplierPerformanceMetrics = (
tenantId: string,
supplierId: string,
options?: Omit<UseQueryOptions<PerformanceMetrics, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<PerformanceMetrics, ApiError>({
queryKey: suppliersKeys.performance.metrics(tenantId, supplierId),
queryFn: () => suppliersService.getSupplierPerformanceMetrics(tenantId, supplierId),
enabled: !!tenantId && !!supplierId,
staleTime: 10 * 60 * 1000, // 10 minutes
...options,
});
};
export const usePerformanceAlerts = (
tenantId: string,
supplierId?: string,
options?: Omit<UseQueryOptions<PerformanceAlert[], ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<PerformanceAlert[], ApiError>({
queryKey: suppliersKeys.performance.alerts(tenantId, supplierId),
queryFn: () => suppliersService.getPerformanceAlerts(tenantId, supplierId),
enabled: !!tenantId,
staleTime: 2 * 60 * 1000, // 2 minutes
...options,
});
};
// Supplier Mutations
export const useCreateSupplier = (
options?: UseMutationOptions<
SupplierResponse,
ApiError,
{ tenantId: string; supplierData: SupplierCreate }
>
) => {
const queryClient = useQueryClient();
return useMutation<
SupplierResponse,
ApiError,
{ tenantId: string; supplierData: SupplierCreate }
>({
mutationFn: ({ tenantId, supplierData }) =>
suppliersService.createSupplier(tenantId, supplierData),
onSuccess: (data, { tenantId }) => {
// Add to cache
queryClient.setQueryData(
suppliersKeys.suppliers.detail(tenantId, data.id),
data
);
// Invalidate lists and statistics
queryClient.invalidateQueries({
queryKey: suppliersKeys.suppliers.lists()
});
queryClient.invalidateQueries({
queryKey: suppliersKeys.suppliers.statistics(tenantId)
});
},
...options,
});
};
export const useUpdateSupplier = (
options?: UseMutationOptions<
SupplierResponse,
ApiError,
{ tenantId: string; supplierId: string; updateData: SupplierUpdate }
>
) => {
const queryClient = useQueryClient();
return useMutation<
SupplierResponse,
ApiError,
{ tenantId: string; supplierId: string; updateData: SupplierUpdate }
>({
mutationFn: ({ tenantId, supplierId, updateData }) =>
suppliersService.updateSupplier(tenantId, supplierId, updateData),
onSuccess: (data, { tenantId, supplierId }) => {
// Update cache
queryClient.setQueryData(
suppliersKeys.suppliers.detail(tenantId, supplierId),
data
);
// Invalidate lists
queryClient.invalidateQueries({
queryKey: suppliersKeys.suppliers.lists()
});
},
...options,
});
};
export const useDeleteSupplier = (
options?: UseMutationOptions<
{ message: string },
ApiError,
{ tenantId: string; supplierId: string }
>
) => {
const queryClient = useQueryClient();
return useMutation<
{ message: string },
ApiError,
{ tenantId: string; supplierId: string }
>({
mutationFn: ({ tenantId, supplierId }) =>
suppliersService.deleteSupplier(tenantId, supplierId),
onSuccess: (_, { tenantId, supplierId }) => {
// Remove from cache
queryClient.removeQueries({
queryKey: suppliersKeys.suppliers.detail(tenantId, supplierId)
});
// Invalidate lists and statistics
queryClient.invalidateQueries({
queryKey: suppliersKeys.suppliers.lists()
});
queryClient.invalidateQueries({
queryKey: suppliersKeys.suppliers.statistics(tenantId)
});
},
...options,
});
};
export const useApproveSupplier = (
options?: UseMutationOptions<
SupplierResponse,
ApiError,
{ tenantId: string; supplierId: string; approval: SupplierApproval }
>
) => {
const queryClient = useQueryClient();
return useMutation<
SupplierResponse,
ApiError,
{ tenantId: string; supplierId: string; approval: SupplierApproval }
>({
mutationFn: ({ tenantId, supplierId, approval }) =>
suppliersService.approveSupplier(tenantId, supplierId, approval),
onSuccess: (data, { tenantId, supplierId }) => {
// Update cache
queryClient.setQueryData(
suppliersKeys.suppliers.detail(tenantId, supplierId),
data
);
// Invalidate lists and statistics
queryClient.invalidateQueries({
queryKey: suppliersKeys.suppliers.lists()
});
queryClient.invalidateQueries({
queryKey: suppliersKeys.suppliers.statistics(tenantId)
});
},
...options,
});
};
// Purchase Order Mutations
export const useCreatePurchaseOrder = (
options?: UseMutationOptions<PurchaseOrderResponse, ApiError, PurchaseOrderCreate>
) => {
const queryClient = useQueryClient();
return useMutation<PurchaseOrderResponse, ApiError, PurchaseOrderCreate>({
mutationFn: (orderData) => suppliersService.createPurchaseOrder(orderData),
onSuccess: (data) => {
// Add to cache
queryClient.setQueryData(
suppliersKeys.purchaseOrders.detail(data.id),
data
);
// Invalidate lists
queryClient.invalidateQueries({
queryKey: suppliersKeys.purchaseOrders.lists()
});
},
...options,
});
};
export const useUpdatePurchaseOrder = (
options?: UseMutationOptions<
PurchaseOrderResponse,
ApiError,
{ orderId: string; updateData: PurchaseOrderUpdate }
>
) => {
const queryClient = useQueryClient();
return useMutation<
PurchaseOrderResponse,
ApiError,
{ orderId: string; updateData: PurchaseOrderUpdate }
>({
mutationFn: ({ orderId, updateData }) =>
suppliersService.updatePurchaseOrder(orderId, updateData),
onSuccess: (data, { orderId }) => {
// Update cache
queryClient.setQueryData(
suppliersKeys.purchaseOrders.detail(orderId),
data
);
// Invalidate lists
queryClient.invalidateQueries({
queryKey: suppliersKeys.purchaseOrders.lists()
});
},
...options,
});
};
export const useApprovePurchaseOrder = (
options?: UseMutationOptions<
PurchaseOrderResponse,
ApiError,
{ orderId: string; approval: PurchaseOrderApproval }
>
) => {
const queryClient = useQueryClient();
return useMutation<
PurchaseOrderResponse,
ApiError,
{ orderId: string; approval: PurchaseOrderApproval }
>({
mutationFn: ({ orderId, approval }) =>
suppliersService.approvePurchaseOrder(orderId, approval),
onSuccess: (data, { orderId }) => {
// Update cache
queryClient.setQueryData(
suppliersKeys.purchaseOrders.detail(orderId),
data
);
// Invalidate lists
queryClient.invalidateQueries({
queryKey: suppliersKeys.purchaseOrders.lists()
});
},
...options,
});
};
// Delivery Mutations
export const useCreateDelivery = (
options?: UseMutationOptions<DeliveryResponse, ApiError, DeliveryCreate>
) => {
const queryClient = useQueryClient();
return useMutation<DeliveryResponse, ApiError, DeliveryCreate>({
mutationFn: (deliveryData) => suppliersService.createDelivery(deliveryData),
onSuccess: (data) => {
// Add to cache
queryClient.setQueryData(
suppliersKeys.deliveries.detail(data.id),
data
);
// Invalidate lists
queryClient.invalidateQueries({
queryKey: suppliersKeys.deliveries.lists()
});
},
...options,
});
};
export const useUpdateDelivery = (
options?: UseMutationOptions<
DeliveryResponse,
ApiError,
{ deliveryId: string; updateData: DeliveryUpdate }
>
) => {
const queryClient = useQueryClient();
return useMutation<
DeliveryResponse,
ApiError,
{ deliveryId: string; updateData: DeliveryUpdate }
>({
mutationFn: ({ deliveryId, updateData }) =>
suppliersService.updateDelivery(deliveryId, updateData),
onSuccess: (data, { deliveryId }) => {
// Update cache
queryClient.setQueryData(
suppliersKeys.deliveries.detail(deliveryId),
data
);
// Invalidate lists
queryClient.invalidateQueries({
queryKey: suppliersKeys.deliveries.lists()
});
},
...options,
});
};
export const useConfirmDeliveryReceipt = (
options?: UseMutationOptions<
DeliveryResponse,
ApiError,
{ deliveryId: string; confirmation: DeliveryReceiptConfirmation }
>
) => {
const queryClient = useQueryClient();
return useMutation<
DeliveryResponse,
ApiError,
{ deliveryId: string; confirmation: DeliveryReceiptConfirmation }
>({
mutationFn: ({ deliveryId, confirmation }) =>
suppliersService.confirmDeliveryReceipt(deliveryId, confirmation),
onSuccess: (data, { deliveryId }) => {
// Update cache
queryClient.setQueryData(
suppliersKeys.deliveries.detail(deliveryId),
data
);
// Invalidate lists and performance metrics
queryClient.invalidateQueries({
queryKey: suppliersKeys.deliveries.lists()
});
queryClient.invalidateQueries({
queryKey: suppliersKeys.performance.all()
});
},
...options,
});
};
// Performance Mutations
export const useCalculateSupplierPerformance = (
options?: UseMutationOptions<
{ message: string; calculation_id: string },
ApiError,
{ tenantId: string; supplierId: string; request?: PerformanceCalculationRequest }
>
) => {
const queryClient = useQueryClient();
return useMutation<
{ message: string; calculation_id: string },
ApiError,
{ tenantId: string; supplierId: string; request?: PerformanceCalculationRequest }
>({
mutationFn: ({ tenantId, supplierId, request }) =>
suppliersService.calculateSupplierPerformance(tenantId, supplierId, request),
onSuccess: (_, { tenantId, supplierId }) => {
// Invalidate performance metrics after calculation
queryClient.invalidateQueries({
queryKey: suppliersKeys.performance.metrics(tenantId, supplierId)
});
queryClient.invalidateQueries({
queryKey: suppliersKeys.performance.alerts(tenantId)
});
},
...options,
});
};
export const useEvaluatePerformanceAlerts = (
options?: UseMutationOptions<
{ alerts_generated: number; message: string },
ApiError,
{ tenantId: string }
>
) => {
const queryClient = useQueryClient();
return useMutation<
{ alerts_generated: number; message: string },
ApiError,
{ tenantId: string }
>({
mutationFn: ({ tenantId }) => suppliersService.evaluatePerformanceAlerts(tenantId),
onSuccess: (_, { tenantId }) => {
// Invalidate performance alerts
queryClient.invalidateQueries({
queryKey: suppliersKeys.performance.alerts(tenantId)
});
},
...options,
});
};
// Utility Hooks
export const useSuppliersByStatus = (tenantId: string, status: string) => {
return useSuppliers(tenantId, { status: status as any });
};
export const useSuppliersCount = (tenantId: string) => {
const { data: statistics } = useSupplierStatistics(tenantId);
return statistics?.total_suppliers || 0;
};
export const useActiveSuppliersCount = (tenantId: string) => {
const { data: statistics } = useSupplierStatistics(tenantId);
return statistics?.active_suppliers || 0;
};
export const usePendingOrdersCount = (supplierId?: string) => {
const { data: orders } = usePurchaseOrders({
supplier_id: supplierId,
status: 'pending_approval',
limit: 1000
});
return orders?.data?.length || 0;
};

View File

@@ -0,0 +1,350 @@
/**
* Training React Query hooks
* Provides data fetching, caching, and state management for training operations
*/
import { useMutation, useQuery, useQueryClient, UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';
import { trainingService } from '../services/training';
import { ApiError } from '../client/apiClient';
import type {
TrainingJobRequest,
TrainingJobResponse,
TrainingJobStatus,
SingleProductTrainingRequest,
ActiveModelResponse,
ModelMetricsResponse,
TrainedModelResponse,
TenantStatistics,
ModelPerformanceResponse,
ModelsQueryParams,
PaginatedResponse,
} from '../types/training';
// Query Keys Factory
export const trainingKeys = {
all: ['training'] as const,
jobs: {
all: () => [...trainingKeys.all, 'jobs'] as const,
status: (tenantId: string, jobId: string) =>
[...trainingKeys.jobs.all(), 'status', tenantId, jobId] as const,
},
models: {
all: () => [...trainingKeys.all, 'models'] as const,
lists: () => [...trainingKeys.models.all(), 'list'] as const,
list: (tenantId: string, params?: ModelsQueryParams) =>
[...trainingKeys.models.lists(), tenantId, params] as const,
details: () => [...trainingKeys.models.all(), 'detail'] as const,
detail: (tenantId: string, modelId: string) =>
[...trainingKeys.models.details(), tenantId, modelId] as const,
active: (tenantId: string, inventoryProductId: string) =>
[...trainingKeys.models.all(), 'active', tenantId, inventoryProductId] as const,
metrics: (tenantId: string, modelId: string) =>
[...trainingKeys.models.all(), 'metrics', tenantId, modelId] as const,
performance: (tenantId: string, modelId: string) =>
[...trainingKeys.models.all(), 'performance', tenantId, modelId] as const,
},
statistics: (tenantId: string) =>
[...trainingKeys.all, 'statistics', tenantId] as const,
} as const;
// Training Job Queries
export const useTrainingJobStatus = (
tenantId: string,
jobId: string,
options?: Omit<UseQueryOptions<TrainingJobStatus, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<TrainingJobStatus, ApiError>({
queryKey: trainingKeys.jobs.status(tenantId, jobId),
queryFn: () => trainingService.getTrainingJobStatus(tenantId, jobId),
enabled: !!tenantId && !!jobId,
refetchInterval: 5000, // Poll every 5 seconds while training
staleTime: 1000, // Consider data stale after 1 second
...options,
});
};
// Model Queries
export const useActiveModel = (
tenantId: string,
inventoryProductId: string,
options?: Omit<UseQueryOptions<ActiveModelResponse, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<ActiveModelResponse, ApiError>({
queryKey: trainingKeys.models.active(tenantId, inventoryProductId),
queryFn: () => trainingService.getActiveModel(tenantId, inventoryProductId),
enabled: !!tenantId && !!inventoryProductId,
staleTime: 5 * 60 * 1000, // 5 minutes
...options,
});
};
export const useModels = (
tenantId: string,
queryParams?: ModelsQueryParams,
options?: Omit<UseQueryOptions<PaginatedResponse<TrainedModelResponse>, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<PaginatedResponse<TrainedModelResponse>, ApiError>({
queryKey: trainingKeys.models.list(tenantId, queryParams),
queryFn: () => trainingService.getModels(tenantId, queryParams),
enabled: !!tenantId,
staleTime: 2 * 60 * 1000, // 2 minutes
...options,
});
};
export const useModelMetrics = (
tenantId: string,
modelId: string,
options?: Omit<UseQueryOptions<ModelMetricsResponse, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<ModelMetricsResponse, ApiError>({
queryKey: trainingKeys.models.metrics(tenantId, modelId),
queryFn: () => trainingService.getModelMetrics(tenantId, modelId),
enabled: !!tenantId && !!modelId,
staleTime: 10 * 60 * 1000, // 10 minutes
...options,
});
};
export const useModelPerformance = (
tenantId: string,
modelId: string,
options?: Omit<UseQueryOptions<ModelPerformanceResponse, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<ModelPerformanceResponse, ApiError>({
queryKey: trainingKeys.models.performance(tenantId, modelId),
queryFn: () => trainingService.getModelPerformance(tenantId, modelId),
enabled: !!tenantId && !!modelId,
staleTime: 10 * 60 * 1000, // 10 minutes
...options,
});
};
// Statistics Queries
export const useTenantTrainingStatistics = (
tenantId: string,
options?: Omit<UseQueryOptions<TenantStatistics, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<TenantStatistics, ApiError>({
queryKey: trainingKeys.statistics(tenantId),
queryFn: () => trainingService.getTenantStatistics(tenantId),
enabled: !!tenantId,
staleTime: 5 * 60 * 1000, // 5 minutes
...options,
});
};
// Training Job Mutations
export const useCreateTrainingJob = (
options?: UseMutationOptions<
TrainingJobResponse,
ApiError,
{ tenantId: string; request: TrainingJobRequest }
>
) => {
const queryClient = useQueryClient();
return useMutation<
TrainingJobResponse,
ApiError,
{ tenantId: string; request: TrainingJobRequest }
>({
mutationFn: ({ tenantId, request }) => trainingService.createTrainingJob(tenantId, request),
onSuccess: (data, { tenantId }) => {
// Add the job status to cache
queryClient.setQueryData(
trainingKeys.jobs.status(tenantId, data.job_id),
{
job_id: data.job_id,
status: data.status,
progress: 0,
message: data.message,
}
);
// Invalidate statistics to reflect the new training job
queryClient.invalidateQueries({ queryKey: trainingKeys.statistics(tenantId) });
},
...options,
});
};
export const useTrainSingleProduct = (
options?: UseMutationOptions<
TrainingJobResponse,
ApiError,
{ tenantId: string; inventoryProductId: string; request: SingleProductTrainingRequest }
>
) => {
const queryClient = useQueryClient();
return useMutation<
TrainingJobResponse,
ApiError,
{ tenantId: string; inventoryProductId: string; request: SingleProductTrainingRequest }
>({
mutationFn: ({ tenantId, inventoryProductId, request }) =>
trainingService.trainSingleProduct(tenantId, inventoryProductId, request),
onSuccess: (data, { tenantId, inventoryProductId }) => {
// Add the job status to cache
queryClient.setQueryData(
trainingKeys.jobs.status(tenantId, data.job_id),
{
job_id: data.job_id,
status: data.status,
progress: 0,
message: data.message,
}
);
// Invalidate active model for this product
queryClient.invalidateQueries({
queryKey: trainingKeys.models.active(tenantId, inventoryProductId)
});
// Invalidate statistics
queryClient.invalidateQueries({ queryKey: trainingKeys.statistics(tenantId) });
},
...options,
});
};
// Admin Mutations
export const useDeleteAllTenantModels = (
options?: UseMutationOptions<{ message: string }, ApiError, { tenantId: string }>
) => {
const queryClient = useQueryClient();
return useMutation<{ message: string }, ApiError, { tenantId: string }>({
mutationFn: ({ tenantId }) => trainingService.deleteAllTenantModels(tenantId),
onSuccess: (_, { tenantId }) => {
// Invalidate all model-related queries for this tenant
queryClient.invalidateQueries({ queryKey: trainingKeys.models.all() });
queryClient.invalidateQueries({ queryKey: trainingKeys.statistics(tenantId) });
},
...options,
});
};
// WebSocket Hook for Real-time Training Updates
export const useTrainingWebSocket = (
tenantId: string,
jobId: string,
token?: string,
options?: {
onProgress?: (data: any) => void;
onCompleted?: (data: any) => void;
onError?: (error: any) => void;
onStarted?: (data: any) => void;
onCancelled?: (data: any) => void;
}
) => {
const queryClient = useQueryClient();
return useQuery({
queryKey: ['training-websocket', tenantId, jobId],
queryFn: () => {
return new Promise((resolve, reject) => {
try {
const ws = trainingService.createWebSocketConnection(tenantId, jobId, token);
ws.onopen = () => {
console.log('Training WebSocket connected');
};
ws.onmessage = (event) => {
try {
const message = JSON.parse(event.data);
// Update job status in cache
queryClient.setQueryData(
trainingKeys.jobs.status(tenantId, jobId),
(oldData: TrainingJobStatus | undefined) => ({
...oldData,
job_id: jobId,
status: message.status || oldData?.status || 'running',
progress: message.progress?.percentage || oldData?.progress || 0,
message: message.message || oldData?.message || '',
current_step: message.progress?.current_step || oldData?.current_step,
estimated_time_remaining: message.progress?.estimated_time_remaining || oldData?.estimated_time_remaining,
})
);
// Call appropriate callback based on message type
switch (message.type) {
case 'progress':
options?.onProgress?.(message);
break;
case 'completed':
options?.onCompleted?.(message);
// Invalidate models and statistics
queryClient.invalidateQueries({ queryKey: trainingKeys.models.all() });
queryClient.invalidateQueries({ queryKey: trainingKeys.statistics(tenantId) });
resolve(message);
break;
case 'error':
options?.onError?.(message);
reject(new Error(message.error));
break;
case 'started':
options?.onStarted?.(message);
break;
case 'cancelled':
options?.onCancelled?.(message);
resolve(message);
break;
}
} catch (error) {
console.error('Error parsing WebSocket message:', error);
reject(error);
}
};
ws.onerror = (error) => {
console.error('Training WebSocket error:', error);
reject(error);
};
ws.onclose = () => {
console.log('Training WebSocket disconnected');
};
// Return cleanup function
return () => {
ws.close();
};
} catch (error) {
reject(error);
}
});
},
enabled: !!tenantId && !!jobId,
refetchOnWindowFocus: false,
retry: false,
staleTime: Infinity,
});
};
// Utility Hooks
export const useIsTrainingInProgress = (tenantId: string, jobId?: string) => {
const { data: jobStatus } = useTrainingJobStatus(tenantId, jobId || '', {
enabled: !!jobId,
});
return jobStatus?.status === 'running' || jobStatus?.status === 'pending';
};
export const useTrainingProgress = (tenantId: string, jobId?: string) => {
const { data: jobStatus } = useTrainingJobStatus(tenantId, jobId || '', {
enabled: !!jobId,
});
return {
progress: jobStatus?.progress || 0,
currentStep: jobStatus?.current_step,
estimatedTimeRemaining: jobStatus?.estimated_time_remaining,
isComplete: jobStatus?.status === 'completed',
isFailed: jobStatus?.status === 'failed',
isRunning: jobStatus?.status === 'running',
};
};

View File

@@ -20,6 +20,11 @@ export { classificationService } from './services/classification';
export { inventoryDashboardService } from './services/inventoryDashboard';
export { foodSafetyService } from './services/foodSafety';
// New API Services
export { trainingService } from './services/training';
export { alertProcessorService } from './services/alert_processor';
export { suppliersService } from './services/suppliers';
// Types - Auth
export type {
User,
@@ -146,6 +151,89 @@ export type {
FoodSafetyDashboard,
} from './types/foodSafety';
// Types - Training
export type {
TrainingJobRequest,
TrainingJobResponse,
TrainingJobStatus,
SingleProductTrainingRequest,
TrainingResults,
TrainingMetrics,
ActiveModelResponse,
ModelMetricsResponse,
TrainedModelResponse,
TenantStatistics as TrainingTenantStatistics,
ModelPerformanceResponse,
TrainingProgressMessage,
TrainingCompletedMessage,
TrainingErrorMessage,
TrainingWebSocketMessage,
ModelsQueryParams,
} from './types/training';
export { TrainingStatus } from './types/training';
// Types - Alert Processor
export type {
AlertMessage,
AlertResponse,
AlertUpdateRequest,
AlertQueryParams,
AlertDashboardData,
NotificationSettings,
ChannelRoutingConfig,
WebhookConfig,
AlertProcessingStatus,
ProcessingMetrics,
AlertAction,
BusinessHours,
} from './types/alert_processor';
export {
AlertItemType,
AlertType,
AlertSeverity,
AlertService,
NotificationChannel,
} from './types/alert_processor';
// Types - Suppliers
export type {
SupplierCreate,
SupplierUpdate,
SupplierResponse,
SupplierSummary,
SupplierApproval,
SupplierQueryParams,
SupplierStatistics,
TopSuppliersResponse,
PurchaseOrderCreate,
PurchaseOrderUpdate,
PurchaseOrderResponse,
PurchaseOrderApproval,
PurchaseOrderQueryParams,
DeliveryCreate,
DeliveryUpdate,
DeliveryResponse,
DeliveryReceiptConfirmation,
DeliveryQueryParams,
PerformanceCalculationRequest,
PerformanceMetrics,
PerformanceAlert,
PurchaseOrderItem,
DeliveryItem,
} from './types/suppliers';
export {
SupplierType,
SupplierStatus,
PaymentTerms,
PurchaseOrderStatus,
DeliveryStatus,
OrderPriority,
PerformanceMetricType,
} from './types/suppliers';
// Hooks - Auth
export {
useAuthProfile,
@@ -289,6 +377,94 @@ export {
foodSafetyKeys,
} from './hooks/foodSafety';
// Hooks - Data Import
export {
useImportStatus,
useValidateJsonData,
useValidateCsvFile,
useImportJsonData,
useImportCsvFile,
useValidateAndImportFile,
dataImportKeys,
} from './hooks/dataImport';
// Hooks - Training
export {
useTrainingJobStatus,
useActiveModel,
useModels,
useModelMetrics,
useModelPerformance,
useTenantTrainingStatistics,
useCreateTrainingJob,
useTrainSingleProduct,
useDeleteAllTenantModels,
useTrainingWebSocket,
useIsTrainingInProgress,
useTrainingProgress,
trainingKeys,
} from './hooks/training';
// Hooks - Alert Processor
export {
useAlerts,
useAlert,
useAlertDashboardData,
useAlertProcessingStatus,
useNotificationSettings,
useChannelRoutingConfig,
useWebhooks,
useProcessingMetrics,
useUpdateAlert,
useDismissAlert,
useAcknowledgeAlert,
useResolveAlert,
useUpdateNotificationSettings,
useCreateWebhook,
useUpdateWebhook,
useDeleteWebhook,
useTestWebhook,
useAlertSSE,
useActiveAlertsCount,
useAlertsByPriority,
useUnreadAlertsCount,
alertProcessorKeys,
} from './hooks/alert_processor';
// Hooks - Suppliers
export {
useSuppliers,
useSupplier,
useSupplierStatistics,
useActiveSuppliers,
useTopSuppliers,
usePendingApprovalSuppliers,
useSuppliersByType,
usePurchaseOrders,
usePurchaseOrder,
useDeliveries,
useDelivery,
useSupplierPerformanceMetrics,
usePerformanceAlerts,
useCreateSupplier,
useUpdateSupplier,
useDeleteSupplier,
useApproveSupplier,
useCreatePurchaseOrder,
useUpdatePurchaseOrder,
useApprovePurchaseOrder,
useCreateDelivery,
useUpdateDelivery,
useConfirmDeliveryReceipt,
useCalculateSupplierPerformance,
useEvaluatePerformanceAlerts,
useSuppliersByStatus,
useSuppliersCount,
useActiveSuppliersCount,
usePendingOrdersCount,
suppliersKeys,
} from './hooks/suppliers';
// Query Key Factories (for advanced usage)
export {
authKeys,
@@ -300,4 +476,8 @@ export {
classificationKeys,
inventoryDashboardKeys,
foodSafetyKeys,
trainingKeys,
alertProcessorKeys,
suppliersKeys,
dataImportKeys,
};

View 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;

View 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;

View 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;

View File

@@ -0,0 +1,265 @@
/**
* Alert Processor service TypeScript type definitions
* Mirrored from backend alert processing schemas
*/
// Enums
export enum AlertItemType {
ALERT = 'alert',
RECOMMENDATION = 'recommendation',
}
export enum AlertType {
INVENTORY_LOW = 'inventory_low',
QUALITY_ISSUE = 'quality_issue',
DELIVERY_DELAY = 'delivery_delay',
PRODUCTION_DELAY = 'production_delay',
EQUIPMENT_FAILURE = 'equipment_failure',
FOOD_SAFETY = 'food_safety',
TEMPERATURE_ALERT = 'temperature_alert',
EXPIRY_WARNING = 'expiry_warning',
FORECAST_ACCURACY = 'forecast_accuracy',
DEMAND_SPIKE = 'demand_spike',
SUPPLIER_ISSUE = 'supplier_issue',
COST_OPTIMIZATION = 'cost_optimization',
REVENUE_OPPORTUNITY = 'revenue_opportunity',
}
export enum AlertSeverity {
URGENT = 'urgent',
HIGH = 'high',
MEDIUM = 'medium',
LOW = 'low',
}
export enum AlertService {
INVENTORY = 'inventory',
PRODUCTION = 'production',
SUPPLIERS = 'suppliers',
FORECASTING = 'forecasting',
QUALITY = 'quality',
FINANCE = 'finance',
OPERATIONS = 'operations',
}
export enum NotificationChannel {
WHATSAPP = 'whatsapp',
EMAIL = 'email',
PUSH = 'push',
DASHBOARD = 'dashboard',
SMS = 'sms',
}
// Core alert data structures
export interface AlertAction {
action: string;
label: string;
endpoint?: string;
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
payload?: Record<string, any>;
}
export interface AlertMessage {
id: string;
tenant_id: string;
item_type: AlertItemType;
type: AlertType;
severity: AlertSeverity;
service: AlertService;
title: string;
message: string;
actions: AlertAction[];
metadata: Record<string, any>;
timestamp: string; // ISO 8601 date string
}
// Channel routing configuration
export interface ChannelRoutingConfig {
urgent: NotificationChannel[];
high: NotificationChannel[];
medium: NotificationChannel[];
low: NotificationChannel[];
recommendations: NotificationChannel[];
}
export interface BusinessHours {
start_hour: number; // 0-23
end_hour: number; // 0-23
days: number[]; // 0-6, Sunday=0
timezone?: string; // e.g., 'Europe/Madrid'
}
export interface NotificationSettings {
tenant_id: string;
channels_enabled: NotificationChannel[];
business_hours: BusinessHours;
emergency_contacts: {
whatsapp?: string;
email?: string;
sms?: string;
};
channel_preferences: {
[key in AlertSeverity]?: NotificationChannel[];
};
}
// Processing status and metrics
export interface ProcessingMetrics {
total_processed: number;
successful: number;
failed: number;
retries: number;
average_processing_time_ms: number;
}
export interface AlertProcessingStatus {
alert_id: string;
tenant_id: string;
status: 'pending' | 'processing' | 'completed' | 'failed' | 'retrying';
created_at: string;
processed_at?: string;
error_message?: string;
retry_count: number;
channels_sent: NotificationChannel[];
delivery_status: {
[channel in NotificationChannel]?: {
status: 'pending' | 'sent' | 'delivered' | 'failed';
sent_at?: string;
delivered_at?: string;
error?: string;
};
};
}
// Queue message structures (for RabbitMQ integration)
export interface QueueMessage {
id: string;
routing_key: string;
exchange: string;
payload: AlertMessage;
headers?: Record<string, any>;
properties?: {
delivery_mode?: number;
priority?: number;
correlation_id?: string;
reply_to?: string;
expiration?: string;
message_id?: string;
timestamp?: number;
type?: string;
user_id?: string;
app_id?: string;
};
}
// SSE (Server-Sent Events) message types for real-time updates
export interface SSEAlertMessage {
type: 'alert' | 'recommendation' | 'alert_update' | 'system_status';
data: AlertMessage | AlertProcessingStatus | SystemStatusMessage;
timestamp: string;
}
export interface SystemStatusMessage {
service: 'alert_processor';
status: 'healthy' | 'degraded' | 'down';
message?: string;
metrics: ProcessingMetrics;
}
// Dashboard integration types
export interface AlertDashboardData {
active_alerts: AlertMessage[];
recent_recommendations: AlertMessage[];
severity_counts: {
[key in AlertSeverity]: number;
};
service_breakdown: {
[key in AlertService]: number;
};
processing_stats: ProcessingMetrics;
}
export interface AlertFilters {
severity?: AlertSeverity[];
type?: AlertType[];
service?: AlertService[];
item_type?: AlertItemType[];
date_from?: string; // ISO 8601 date string
date_to?: string; // ISO 8601 date string
status?: 'active' | 'acknowledged' | 'resolved' | 'dismissed';
search?: string;
}
export interface AlertQueryParams extends AlertFilters {
limit?: number;
offset?: number;
sort_by?: 'timestamp' | 'severity' | 'type';
sort_order?: 'asc' | 'desc';
}
// Alert lifecycle management
export interface AlertUpdateRequest {
status?: 'acknowledged' | 'resolved' | 'dismissed';
notes?: string;
assigned_to?: string;
priority_override?: AlertSeverity;
}
export interface AlertResponse extends AlertMessage {
status: 'active' | 'acknowledged' | 'resolved' | 'dismissed';
created_at: string;
updated_at?: string;
acknowledged_at?: string;
acknowledged_by?: string;
resolved_at?: string;
resolved_by?: string;
notes?: string;
assigned_to?: string;
}
// Webhook integration for external systems
export interface WebhookConfig {
tenant_id: string;
webhook_url: string;
secret_token: string;
enabled: boolean;
event_types: (AlertType | 'all')[];
severity_filter: AlertSeverity[];
headers?: Record<string, string>;
retry_config: {
max_retries: number;
retry_delay_ms: number;
backoff_multiplier: number;
};
}
export interface WebhookPayload {
event_type: 'alert_created' | 'alert_updated' | 'alert_resolved';
alert: AlertResponse;
tenant_id: string;
webhook_id: string;
timestamp: string;
signature: string; // HMAC signature for verification
}
// API Response Wrappers
export interface PaginatedResponse<T> {
data: T[];
total: number;
limit: number;
offset: number;
has_next: boolean;
has_previous: boolean;
}
export interface ApiResponse<T> {
success: boolean;
data: T;
message?: string;
errors?: string[];
}
// Export all types
export type {
// Add any additional export aliases if needed
};

View File

@@ -0,0 +1,431 @@
/**
* Suppliers service TypeScript type definitions
* Mirrored from backend API schemas
*/
// Enums
export enum SupplierType {
INGREDIENTS = 'ingredients',
PACKAGING = 'packaging',
EQUIPMENT = 'equipment',
SERVICES = 'services',
UTILITIES = 'utilities',
MULTI = 'multi',
}
export enum SupplierStatus {
ACTIVE = 'active',
INACTIVE = 'inactive',
PENDING_APPROVAL = 'pending_approval',
SUSPENDED = 'suspended',
BLACKLISTED = 'blacklisted',
}
export enum PaymentTerms {
CASH_ON_DELIVERY = 'cod',
NET_15 = 'net_15',
NET_30 = 'net_30',
NET_45 = 'net_45',
NET_60 = 'net_60',
PREPAID = 'prepaid',
}
export enum PurchaseOrderStatus {
DRAFT = 'draft',
PENDING_APPROVAL = 'pending_approval',
APPROVED = 'approved',
SENT_TO_SUPPLIER = 'sent_to_supplier',
CONFIRMED = 'confirmed',
PARTIALLY_RECEIVED = 'partially_received',
COMPLETED = 'completed',
CANCELLED = 'cancelled',
}
export enum DeliveryStatus {
SCHEDULED = 'scheduled',
IN_TRANSIT = 'in_transit',
OUT_FOR_DELIVERY = 'out_for_delivery',
DELIVERED = 'delivered',
PARTIALLY_DELIVERED = 'partially_delivered',
FAILED_DELIVERY = 'failed_delivery',
}
export enum OrderPriority {
NORMAL = 'normal',
HIGH = 'high',
URGENT = 'urgent',
}
export enum PerformanceMetricType {
DELIVERY_PERFORMANCE = 'delivery_performance',
QUALITY_SCORE = 'quality_score',
PRICE_COMPETITIVENESS = 'price_competitiveness',
ORDER_ACCURACY = 'order_accuracy',
}
export enum AlertSeverity {
CRITICAL = 'critical',
HIGH = 'high',
MEDIUM = 'medium',
LOW = 'low',
}
// Supplier Management Types
export interface SupplierCreate {
name: string;
supplier_code?: string;
tax_id?: string;
supplier_type: SupplierType;
contact_person?: string;
email?: string;
phone?: string;
address_line1?: string;
address_line2?: string;
city?: string;
state?: string;
postal_code?: string;
country?: string;
payment_terms?: PaymentTerms;
credit_limit?: number;
currency?: string;
standard_lead_time?: number; // in days
minimum_order_amount?: number;
certifications?: Record<string, any>;
business_hours?: Record<string, any>;
specializations?: Record<string, any>;
notes?: string;
}
export interface SupplierUpdate extends Partial<SupplierCreate> {
status?: SupplierStatus;
}
export interface SupplierResponse {
id: string;
tenant_id: string;
name: string;
supplier_code: string;
tax_id?: string;
supplier_type: SupplierType;
status: SupplierStatus;
contact_person?: string;
email?: string;
phone?: string;
address_line1?: string;
address_line2?: string;
city?: string;
state?: string;
postal_code?: string;
country?: string;
payment_terms: PaymentTerms;
credit_limit: number;
currency: string;
standard_lead_time: number;
minimum_order_amount: number;
certifications: Record<string, any>;
business_hours: Record<string, any>;
specializations: Record<string, any>;
notes?: string;
created_at: string; // ISO 8601 date string
updated_at: string; // ISO 8601 date string
created_by?: string;
updated_by?: string;
}
export interface SupplierSummary {
id: string;
name: string;
supplier_code: string;
supplier_type: SupplierType;
status: SupplierStatus;
contact_person?: string;
email?: string;
phone?: string;
city?: string;
country?: string;
payment_terms: PaymentTerms;
created_at: string;
}
// Approval Workflow
export interface SupplierApproval {
action: 'approve' | 'reject';
notes?: string;
}
// Purchase Orders
export interface PurchaseOrderItem {
inventory_product_id: string;
product_code: string;
product_name?: string;
ordered_quantity: number;
unit_of_measure: string;
unit_price: number;
total_price?: number; // calculated field
quality_requirements?: string;
notes?: string;
}
export interface PurchaseOrderCreate {
supplier_id: string;
reference_number?: string;
priority: OrderPriority;
required_delivery_date?: string; // ISO 8601 date string
delivery_address?: string;
tax_amount?: number;
shipping_cost?: number;
discount_amount?: number;
notes?: string;
items: PurchaseOrderItem[];
}
export interface PurchaseOrderUpdate extends Partial<PurchaseOrderCreate> {
status?: PurchaseOrderStatus;
}
export interface PurchaseOrderResponse {
id: string;
tenant_id: string;
supplier_id: string;
supplier_name: string;
reference_number: string;
status: PurchaseOrderStatus;
priority: OrderPriority;
subtotal: number;
tax_amount: number;
shipping_cost: number;
discount_amount: number;
total_amount: number;
currency: string;
order_date: string; // ISO 8601 date string
required_delivery_date?: string;
delivery_address?: string;
approved_at?: string;
approved_by?: string;
sent_at?: string;
confirmed_at?: string;
notes?: string;
items: PurchaseOrderItem[];
created_at: string;
updated_at: string;
created_by: string;
updated_by?: string;
}
export interface PurchaseOrderApproval {
action: 'approve' | 'reject';
notes?: string;
}
// Deliveries
export interface DeliveryItem {
purchase_order_item_id: string;
product_code: string;
ordered_quantity: number;
delivered_quantity: number;
quality_rating?: number; // 1-5 scale
quality_notes?: string;
expiry_date?: string; // ISO 8601 date string
batch_number?: string;
temperature_on_arrival?: number;
condition_notes?: string;
}
export interface DeliveryCreate {
purchase_order_id: string;
scheduled_date?: string; // ISO 8601 date string
delivery_window_start?: string;
delivery_window_end?: string;
delivery_address?: string;
carrier_name?: string;
tracking_number?: string;
special_instructions?: string;
items: DeliveryItem[];
}
export interface DeliveryUpdate extends Partial<DeliveryCreate> {
status?: DeliveryStatus;
actual_delivery_date?: string;
received_by?: string;
delivery_notes?: string;
}
export interface DeliveryResponse {
id: string;
tenant_id: string;
purchase_order_id: string;
supplier_id: string;
supplier_name: string;
reference_number: string;
status: DeliveryStatus;
scheduled_date?: string;
actual_delivery_date?: string;
delivery_window_start?: string;
delivery_window_end?: string;
delivery_address?: string;
carrier_name?: string;
tracking_number?: string;
special_instructions?: string;
received_by?: string;
delivery_notes?: string;
items: DeliveryItem[];
created_at: string;
updated_at: string;
}
export interface DeliveryReceiptConfirmation {
received_by: string;
receipt_date: string; // ISO 8601 date string
general_notes?: string;
items: {
delivery_item_id: string;
accepted_quantity: number;
rejected_quantity?: number;
quality_rating: number; // 1-5 scale
quality_notes?: string;
condition_issues?: string[];
}[];
}
// Performance Tracking
export interface PerformanceCalculationRequest {
period?: 'week' | 'month' | 'quarter' | 'year' | 'custom';
period_start?: string; // ISO 8601 date string
period_end?: string; // ISO 8601 date string
}
export interface PerformanceMetrics {
supplier_id: string;
tenant_id: string;
calculation_period: {
start_date: string;
end_date: string;
};
delivery_performance: {
score: number; // 0-100
on_time_deliveries: number;
total_deliveries: number;
average_delay_days: number;
};
quality_score: {
score: number; // 0-100
total_ratings: number;
average_rating: number; // 1-5 scale
rejection_rate: number; // 0-1
};
price_competitiveness: {
score: number; // 0-100
average_price_vs_market: number;
cost_savings: number;
};
order_accuracy: {
score: number; // 0-100
accurate_orders: number;
total_orders: number;
error_types: Record<string, number>;
};
overall_score: number; // 0-100
calculated_at: string; // ISO 8601 date string
}
export interface PerformanceAlert {
id: string;
supplier_id: string;
tenant_id: string;
metric_type: PerformanceMetricType;
severity: AlertSeverity;
threshold_value: number;
actual_value: number;
message: string;
created_at: string;
resolved_at?: string;
}
// Statistics and Analytics
export interface SupplierStatistics {
total_suppliers: number;
active_suppliers: number;
suppliers_by_type: Record<SupplierType, number>;
suppliers_by_status: Record<SupplierStatus, number>;
average_performance_score: number;
total_purchase_orders: number;
total_spend_current_period: number;
top_suppliers_by_spend: {
supplier_id: string;
supplier_name: string;
total_spend: number;
}[];
}
export interface TopSuppliersResponse {
suppliers: {
supplier_id: string;
supplier_name: string;
supplier_type: SupplierType;
total_orders: number;
total_spend: number;
performance_score: number;
last_order_date: string;
}[];
period: {
start_date: string;
end_date: string;
};
}
// Query Parameters
export interface SupplierQueryParams {
search_term?: string;
supplier_type?: SupplierType;
status?: SupplierStatus;
limit?: number;
offset?: number;
sort_by?: 'name' | 'created_at' | 'supplier_type' | 'status';
sort_order?: 'asc' | 'desc';
}
export interface PurchaseOrderQueryParams {
supplier_id?: string;
status?: PurchaseOrderStatus;
priority?: OrderPriority;
date_from?: string; // ISO 8601 date string
date_to?: string; // ISO 8601 date string
limit?: number;
offset?: number;
sort_by?: 'order_date' | 'total_amount' | 'status' | 'required_delivery_date';
sort_order?: 'asc' | 'desc';
}
export interface DeliveryQueryParams {
supplier_id?: string;
purchase_order_id?: string;
status?: DeliveryStatus;
scheduled_date_from?: string;
scheduled_date_to?: string;
limit?: number;
offset?: number;
sort_by?: 'scheduled_date' | 'actual_delivery_date' | 'status';
sort_order?: 'asc' | 'desc';
}
// API Response Wrappers
export interface PaginatedResponse<T> {
data: T[];
total: number;
limit: number;
offset: number;
has_next: boolean;
has_previous: boolean;
}
export interface ApiResponse<T> {
success: boolean;
data: T;
message?: string;
errors?: string[];
}
// Export all types
export type {
// Add any additional export aliases if needed
};

View File

@@ -0,0 +1,209 @@
/**
* Training service TypeScript type definitions
* Mirrored from backend API schemas
*/
// Enums
export enum TrainingStatus {
PENDING = 'pending',
RUNNING = 'running',
COMPLETED = 'completed',
FAILED = 'failed',
CANCELLED = 'cancelled',
}
// Request types
export interface TrainingJobRequest {
products?: string[]; // optional array of product IDs
start_date?: string; // ISO 8601 date string, optional
end_date?: string; // ISO 8601 date string, optional
}
export interface SingleProductTrainingRequest {
start_date?: string; // ISO 8601 date string
end_date?: string; // ISO 8601 date string
seasonality_mode?: string; // 'additive' | 'multiplicative'
daily_seasonality?: boolean;
weekly_seasonality?: boolean;
yearly_seasonality?: boolean;
bakery_location?: [number, number]; // [latitude, longitude]
}
// Response types
export interface TrainingResults {
total_products: number;
successful_trainings: number;
failed_trainings: number;
products: any[]; // Product-specific results
overall_training_time_seconds: number;
}
export interface DataSummary {
// Will be populated based on actual backend response structure
[key: string]: any;
}
export interface ProcessingMetadata {
background_task: boolean;
async_execution: boolean;
enhanced_features: boolean;
repository_pattern: boolean;
}
export interface TrainingJobResponse {
job_id: string;
tenant_id: string;
status: TrainingStatus;
message: string;
created_at: string; // ISO 8601 date string
estimated_duration_minutes: number;
training_results: TrainingResults;
data_summary?: DataSummary | null;
completed_at?: string | null; // ISO 8601 date string
error_details?: string | null;
processing_metadata: ProcessingMetadata;
}
export interface TrainingJobStatus {
job_id: string;
status: TrainingStatus;
progress?: number; // 0-100 percentage
message?: string;
current_step?: string;
estimated_time_remaining?: number; // seconds
}
export interface TrainingJobProgress {
progress: {
percentage: number;
current_step: string;
estimated_time_remaining: number;
products_completed: number;
products_total: number;
};
}
// Model types
export interface TrainingMetrics {
mape: number; // Mean Absolute Percentage Error
mae: number; // Mean Absolute Error
rmse: number; // Root Mean Square Error
r2_score: number; // R-squared score
}
export interface TrainingPeriod {
start_date: string; // ISO 8601 date string
end_date: string; // ISO 8601 date string
}
export interface ActiveModelResponse {
model_id: string;
model_path: string;
features_used: string[];
hyperparameters: Record<string, any>;
training_metrics: TrainingMetrics;
created_at: string; // ISO 8601 date string
training_period: TrainingPeriod;
}
export interface ModelMetricsResponse {
model_id: string;
metrics: TrainingMetrics;
created_at: string;
training_period: TrainingPeriod;
}
export interface TrainedModelResponse {
model_id: string;
tenant_id: string;
inventory_product_id: string;
status: string;
model_type: string;
training_metrics: TrainingMetrics;
created_at: string;
training_period: TrainingPeriod;
features_used: string[];
hyperparameters: Record<string, any>;
}
// Statistics types
export interface TenantStatistics {
total_models: number;
active_models: number;
training_jobs_count: number;
average_accuracy: number;
last_training_date?: string;
}
export interface ModelPerformanceResponse {
model_id: string;
performance_metrics: TrainingMetrics;
validation_results: Record<string, any>;
feature_importance: Record<string, number>;
}
// WebSocket message types
export interface TrainingProgressMessage {
type: 'progress';
job_id: string;
progress: TrainingJobProgress['progress'];
}
export interface TrainingCompletedMessage {
type: 'completed';
job_id: string;
results: {
training_results: TrainingResults;
performance_metrics: TrainingMetrics;
successful_trainings: number;
training_duration: number; // in seconds
};
}
export interface TrainingErrorMessage {
type: 'error';
job_id: string;
error: string;
}
export interface TrainingStartedMessage {
type: 'started';
job_id: string;
message: string;
}
export interface TrainingCancelledMessage {
type: 'cancelled';
job_id: string;
message: string;
}
export type TrainingWebSocketMessage =
| TrainingProgressMessage
| TrainingCompletedMessage
| TrainingErrorMessage
| TrainingStartedMessage
| TrainingCancelledMessage;
// Query parameter types
export interface ModelsQueryParams {
status?: string;
model_type?: string;
limit?: number;
offset?: number;
}
// API response wrappers
export interface PaginatedResponse<T> {
data: T[];
total: number;
limit: number;
offset: number;
has_next: boolean;
has_previous: boolean;
}
// Export all types
export type {
// Add any additional export aliases if needed
};