From 44b789f5238278135b9271d495cebcd620d04e9f Mon Sep 17 00:00:00 2001 From: Urtzi Alfaro Date: Wed, 10 Sep 2025 08:00:50 +0200 Subject: [PATCH] Add production APIs to frontend --- frontend/src/api/hooks/production.ts | 243 ++++++++++++++++++++ frontend/src/api/index.ts | 50 +++++ frontend/src/api/services/production.ts | 79 +++++++ frontend/src/api/types/production.ts | 285 ++++++++++++++++++++++++ 4 files changed, 657 insertions(+) create mode 100644 frontend/src/api/hooks/production.ts create mode 100644 frontend/src/api/services/production.ts create mode 100644 frontend/src/api/types/production.ts diff --git a/frontend/src/api/hooks/production.ts b/frontend/src/api/hooks/production.ts new file mode 100644 index 00000000..15018e22 --- /dev/null +++ b/frontend/src/api/hooks/production.ts @@ -0,0 +1,243 @@ +/** + * Production React Query hooks + */ +import { useMutation, useQuery, useQueryClient, UseQueryOptions, UseMutationOptions } from '@tanstack/react-query'; +import { productionService } from '../services/production'; +import type { + ProductionBatchCreate, + ProductionBatchStatusUpdate, + ProductionBatchResponse, + ProductionBatchListResponse, + ProductionDashboardSummary, + DailyProductionRequirements, + ProductionScheduleData, + ProductionCapacityStatus, + ProductionRequirements, + ProductionYieldMetrics, +} from '../types/production'; +import { ApiError } from '../client'; + +// Query Keys +export const productionKeys = { + all: ['production'] as const, + tenant: (tenantId: string) => [...productionKeys.all, tenantId] as const, + dashboard: (tenantId: string) => [...productionKeys.tenant(tenantId), 'dashboard'] as const, + dailyRequirements: (tenantId: string, date?: string) => + [...productionKeys.tenant(tenantId), 'daily-requirements', date] as const, + requirements: (tenantId: string, date?: string) => + [...productionKeys.tenant(tenantId), 'requirements', date] as const, + batches: (tenantId: string) => [...productionKeys.tenant(tenantId), 'batches'] as const, + activeBatches: (tenantId: string) => [...productionKeys.batches(tenantId), 'active'] as const, + batch: (tenantId: string, batchId: string) => + [...productionKeys.batches(tenantId), batchId] as const, + schedule: (tenantId: string, startDate?: string, endDate?: string) => + [...productionKeys.tenant(tenantId), 'schedule', startDate, endDate] as const, + capacity: (tenantId: string, date?: string) => + [...productionKeys.tenant(tenantId), 'capacity', date] as const, + yieldMetrics: (tenantId: string, startDate: string, endDate: string) => + [...productionKeys.tenant(tenantId), 'yield-metrics', startDate, endDate] as const, +} as const; + +// Dashboard Queries +export const useProductionDashboard = ( + tenantId: string, + options?: Omit, 'queryKey' | 'queryFn'> +) => { + return useQuery({ + queryKey: productionKeys.dashboard(tenantId), + queryFn: () => productionService.getDashboardSummary(tenantId), + enabled: !!tenantId, + staleTime: 30 * 1000, // 30 seconds + refetchInterval: 60 * 1000, // 1 minute + ...options, + }); +}; + +export const useDailyProductionRequirements = ( + tenantId: string, + date?: string, + options?: Omit, 'queryKey' | 'queryFn'> +) => { + return useQuery({ + queryKey: productionKeys.dailyRequirements(tenantId, date), + queryFn: () => productionService.getDailyRequirements(tenantId, date), + enabled: !!tenantId, + staleTime: 5 * 60 * 1000, // 5 minutes + ...options, + }); +}; + +export const useProductionRequirements = ( + tenantId: string, + date?: string, + options?: Omit, 'queryKey' | 'queryFn'> +) => { + return useQuery({ + queryKey: productionKeys.requirements(tenantId, date), + queryFn: () => productionService.getProductionRequirements(tenantId, date), + enabled: !!tenantId, + staleTime: 5 * 60 * 1000, // 5 minutes + ...options, + }); +}; + +// Batch Queries +export const useActiveBatches = ( + tenantId: string, + options?: Omit, 'queryKey' | 'queryFn'> +) => { + return useQuery({ + queryKey: productionKeys.activeBatches(tenantId), + queryFn: () => productionService.getActiveBatches(tenantId), + enabled: !!tenantId, + staleTime: 30 * 1000, // 30 seconds + refetchInterval: 60 * 1000, // 1 minute + ...options, + }); +}; + +export const useBatchDetails = ( + tenantId: string, + batchId: string, + options?: Omit, 'queryKey' | 'queryFn'> +) => { + return useQuery({ + queryKey: productionKeys.batch(tenantId, batchId), + queryFn: () => productionService.getBatchDetails(tenantId, batchId), + enabled: !!tenantId && !!batchId, + staleTime: 30 * 1000, // 30 seconds + ...options, + }); +}; + +// Schedule and Capacity Queries +export const useProductionSchedule = ( + tenantId: string, + startDate?: string, + endDate?: string, + options?: Omit, 'queryKey' | 'queryFn'> +) => { + return useQuery({ + queryKey: productionKeys.schedule(tenantId, startDate, endDate), + queryFn: () => productionService.getProductionSchedule(tenantId, startDate, endDate), + enabled: !!tenantId, + staleTime: 5 * 60 * 1000, // 5 minutes + ...options, + }); +}; + +export const useCapacityStatus = ( + tenantId: string, + date?: string, + options?: Omit, 'queryKey' | 'queryFn'> +) => { + return useQuery({ + queryKey: productionKeys.capacity(tenantId, date), + queryFn: () => productionService.getCapacityStatus(tenantId, date), + enabled: !!tenantId, + staleTime: 5 * 60 * 1000, // 5 minutes + ...options, + }); +}; + +export const useYieldMetrics = ( + tenantId: string, + startDate: string, + endDate: string, + options?: Omit, 'queryKey' | 'queryFn'> +) => { + return useQuery({ + queryKey: productionKeys.yieldMetrics(tenantId, startDate, endDate), + queryFn: () => productionService.getYieldMetrics(tenantId, startDate, endDate), + enabled: !!tenantId && !!startDate && !!endDate, + staleTime: 15 * 60 * 1000, // 15 minutes (metrics are less frequently changing) + ...options, + }); +}; + +// Mutations +export const useCreateProductionBatch = ( + options?: UseMutationOptions +) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ tenantId, batchData }) => productionService.createProductionBatch(tenantId, batchData), + onSuccess: (data, { tenantId }) => { + // Invalidate active batches to refresh the list + queryClient.invalidateQueries({ queryKey: productionKeys.activeBatches(tenantId) }); + // Invalidate dashboard to update summary + queryClient.invalidateQueries({ queryKey: productionKeys.dashboard(tenantId) }); + // Cache the new batch details + queryClient.setQueryData(productionKeys.batch(tenantId, data.id), data); + }, + ...options, + }); +}; + +export const useUpdateBatchStatus = ( + options?: UseMutationOptions< + ProductionBatchResponse, + ApiError, + { tenantId: string; batchId: string; statusUpdate: ProductionBatchStatusUpdate } + > +) => { + const queryClient = useQueryClient(); + + return useMutation< + ProductionBatchResponse, + ApiError, + { tenantId: string; batchId: string; statusUpdate: ProductionBatchStatusUpdate } + >({ + mutationFn: ({ tenantId, batchId, statusUpdate }) => + productionService.updateBatchStatus(tenantId, batchId, statusUpdate), + onSuccess: (data, { tenantId, batchId }) => { + // Update the specific batch data + queryClient.setQueryData(productionKeys.batch(tenantId, batchId), data); + // Invalidate active batches to refresh the list + queryClient.invalidateQueries({ queryKey: productionKeys.activeBatches(tenantId) }); + // Invalidate dashboard to update summary + queryClient.invalidateQueries({ queryKey: productionKeys.dashboard(tenantId) }); + }, + ...options, + }); +}; + +// Helper hooks for common use cases +export const useProductionDashboardData = (tenantId: string) => { + const dashboard = useProductionDashboard(tenantId); + const activeBatches = useActiveBatches(tenantId); + const dailyRequirements = useDailyProductionRequirements(tenantId); + + return { + dashboard: dashboard.data, + activeBatches: activeBatches.data, + dailyRequirements: dailyRequirements.data, + isLoading: dashboard.isLoading || activeBatches.isLoading || dailyRequirements.isLoading, + error: dashboard.error || activeBatches.error || dailyRequirements.error, + refetch: () => { + dashboard.refetch(); + activeBatches.refetch(); + dailyRequirements.refetch(); + }, + }; +}; + +export const useProductionPlanningData = (tenantId: string, date?: string) => { + const schedule = useProductionSchedule(tenantId); + const capacity = useCapacityStatus(tenantId, date); + const requirements = useProductionRequirements(tenantId, date); + + return { + schedule: schedule.data, + capacity: capacity.data, + requirements: requirements.data, + isLoading: schedule.isLoading || capacity.isLoading || requirements.isLoading, + error: schedule.error || capacity.error || requirements.error, + refetch: () => { + schedule.refetch(); + capacity.refetch(); + requirements.refetch(); + }, + }; +}; \ No newline at end of file diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index 4875e7b1..fb837873 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -26,6 +26,7 @@ export { alertProcessorService } from './services/alert_processor'; export { suppliersService } from './services/suppliers'; export { OrdersService } from './services/orders'; export { forecastingService } from './services/forecasting'; +export { productionService } from './services/production'; // Types - Auth export type { @@ -312,6 +313,37 @@ export type { export { BusinessType } from './types/forecasting'; +// Types - Production +export type { + ProductionBatchBase, + ProductionBatchCreate, + ProductionBatchUpdate, + ProductionBatchStatusUpdate, + ProductionBatchResponse, + ProductionScheduleBase, + ProductionScheduleCreate, + ProductionScheduleUpdate, + ProductionScheduleResponse, + QualityCheckBase, + QualityCheckCreate, + QualityCheckResponse, + ProductionDashboardSummary, + DailyProductionRequirements, + ProductionMetrics, + ProductionBatchListResponse, + ProductionScheduleListResponse, + QualityCheckListResponse, + ProductionScheduleData, + ProductionCapacityStatus, + ProductionRequirements, + ProductionYieldMetrics, +} from './types/production'; + +export { + ProductionStatusEnum, + ProductionPriorityEnum, +} from './types/production'; + // Hooks - Auth export { useAuthProfile, @@ -583,6 +615,23 @@ export { forecastingKeys, } from './hooks/forecasting'; +// Hooks - Production +export { + useProductionDashboard, + useDailyProductionRequirements, + useProductionRequirements, + useActiveBatches, + useBatchDetails, + useProductionSchedule, + useCapacityStatus, + useYieldMetrics, + useCreateProductionBatch, + useUpdateBatchStatus, + useProductionDashboardData, + useProductionPlanningData, + productionKeys, +} from './hooks/production'; + // Query Key Factories (for advanced usage) export { authKeys, @@ -600,4 +649,5 @@ export { ordersKeys, dataImportKeys, forecastingKeys, + productionKeys, }; \ No newline at end of file diff --git a/frontend/src/api/services/production.ts b/frontend/src/api/services/production.ts new file mode 100644 index 00000000..7b6ece79 --- /dev/null +++ b/frontend/src/api/services/production.ts @@ -0,0 +1,79 @@ +import { apiClient } from '../client'; +import type { + ProductionBatchCreate, + ProductionBatchStatusUpdate, + ProductionBatchResponse, + ProductionBatchListResponse, + ProductionDashboardSummary, + DailyProductionRequirements, + ProductionScheduleData, + ProductionCapacityStatus, + ProductionRequirements, + ProductionYieldMetrics, +} from '../types/production'; + +export class ProductionService { + private readonly baseUrl = '/production'; + + getDashboardSummary(tenantId: string): Promise { + return apiClient.get(`/tenants/${tenantId}${this.baseUrl}/dashboard-summary`); + } + + getDailyRequirements(tenantId: string, date?: string): Promise { + const params = date ? { date } : {}; + return apiClient.get(`/tenants/${tenantId}${this.baseUrl}/daily-requirements`, { params }); + } + + getProductionRequirements(tenantId: string, date?: string): Promise { + const params = date ? { date } : {}; + return apiClient.get(`/tenants/${tenantId}${this.baseUrl}/requirements`, { params }); + } + + createProductionBatch(tenantId: string, batchData: ProductionBatchCreate): Promise { + return apiClient.post(`/tenants/${tenantId}${this.baseUrl}/batches`, batchData); + } + + getActiveBatches(tenantId: string): Promise { + return apiClient.get(`/tenants/${tenantId}${this.baseUrl}/batches/active`); + } + + getBatchDetails(tenantId: string, batchId: string): Promise { + return apiClient.get(`/tenants/${tenantId}${this.baseUrl}/batches/${batchId}`); + } + + updateBatchStatus( + tenantId: string, + batchId: string, + statusUpdate: ProductionBatchStatusUpdate + ): Promise { + return apiClient.put(`/tenants/${tenantId}${this.baseUrl}/batches/${batchId}/status`, statusUpdate); + } + + getProductionSchedule( + tenantId: string, + startDate?: string, + endDate?: string + ): Promise { + const params: Record = {}; + if (startDate) params.start_date = startDate; + if (endDate) params.end_date = endDate; + + return apiClient.get(`/tenants/${tenantId}${this.baseUrl}/schedule`, { params }); + } + + getCapacityStatus(tenantId: string, date?: string): Promise { + const params = date ? { date } : {}; + return apiClient.get(`/tenants/${tenantId}${this.baseUrl}/capacity/status`, { params }); + } + + getYieldMetrics( + tenantId: string, + startDate: string, + endDate: string + ): Promise { + const params = { start_date: startDate, end_date: endDate }; + return apiClient.get(`/tenants/${tenantId}${this.baseUrl}/metrics/yield`, { params }); + } +} + +export const productionService = new ProductionService(); \ No newline at end of file diff --git a/frontend/src/api/types/production.ts b/frontend/src/api/types/production.ts new file mode 100644 index 00000000..5fcfdb6d --- /dev/null +++ b/frontend/src/api/types/production.ts @@ -0,0 +1,285 @@ +export enum ProductionStatusEnum { + PENDING = "pending", + IN_PROGRESS = "in_progress", + COMPLETED = "completed", + CANCELLED = "cancelled", + ON_HOLD = "on_hold", + QUALITY_CHECK = "quality_check", + FAILED = "failed" +} + +export enum ProductionPriorityEnum { + LOW = "low", + MEDIUM = "medium", + HIGH = "high", + URGENT = "urgent" +} + +export interface ProductionBatchBase { + product_id: string; + product_name: string; + recipe_id?: string | null; + planned_start_time: string; + planned_end_time: string; + planned_quantity: number; + planned_duration_minutes: number; + priority: ProductionPriorityEnum; + is_rush_order: boolean; + is_special_recipe: boolean; + production_notes?: string | null; +} + +export interface ProductionBatchCreate extends ProductionBatchBase { + batch_number?: string | null; + order_id?: string | null; + forecast_id?: string | null; + equipment_used?: string[] | null; + staff_assigned?: string[] | null; + station_id?: string | null; +} + +export interface ProductionBatchUpdate { + product_name?: string | null; + planned_start_time?: string | null; + planned_end_time?: string | null; + planned_quantity?: number | null; + planned_duration_minutes?: number | null; + actual_quantity?: number | null; + priority?: ProductionPriorityEnum | null; + equipment_used?: string[] | null; + staff_assigned?: string[] | null; + station_id?: string | null; + production_notes?: string | null; +} + +export interface ProductionBatchStatusUpdate { + status: ProductionStatusEnum; + actual_quantity?: number | null; + notes?: string | null; +} + +export interface ProductionBatchResponse { + id: string; + tenant_id: string; + batch_number: string; + product_id: string; + product_name: string; + recipe_id?: string | null; + planned_start_time: string; + planned_end_time: string; + planned_quantity: number; + planned_duration_minutes: number; + actual_start_time?: string | null; + actual_end_time?: string | null; + actual_quantity?: number | null; + actual_duration_minutes?: number | null; + status: ProductionStatusEnum; + priority: ProductionPriorityEnum; + estimated_cost?: number | null; + actual_cost?: number | null; + yield_percentage?: number | null; + quality_score?: number | null; + equipment_used?: string[] | null; + staff_assigned?: string[] | null; + station_id?: string | null; + order_id?: string | null; + forecast_id?: string | null; + is_rush_order: boolean; + is_special_recipe: boolean; + production_notes?: string | null; + quality_notes?: string | null; + delay_reason?: string | null; + cancellation_reason?: string | null; + created_at: string; + updated_at: string; + completed_at?: string | null; +} + +export interface ProductionScheduleBase { + schedule_date: string; + shift_start: string; + shift_end: string; + total_capacity_hours: number; + planned_capacity_hours: number; + staff_count: number; + equipment_capacity?: Record | null; + station_assignments?: Record | null; + schedule_notes?: string | null; +} + +export interface ProductionScheduleCreate extends ProductionScheduleBase {} + +export interface ProductionScheduleUpdate { + shift_start?: string | null; + shift_end?: string | null; + total_capacity_hours?: number | null; + planned_capacity_hours?: number | null; + staff_count?: number | null; + overtime_hours?: number | null; + equipment_capacity?: Record | null; + station_assignments?: Record | null; + schedule_notes?: string | null; +} + +export interface ProductionScheduleResponse { + id: string; + tenant_id: string; + schedule_date: string; + shift_start: string; + shift_end: string; + total_capacity_hours: number; + planned_capacity_hours: number; + actual_capacity_hours?: number | null; + overtime_hours?: number | null; + staff_count: number; + equipment_capacity?: Record | null; + station_assignments?: Record | null; + total_batches_planned: number; + total_batches_completed?: number | null; + total_quantity_planned: number; + total_quantity_produced?: number | null; + is_finalized: boolean; + is_active: boolean; + efficiency_percentage?: number | null; + utilization_percentage?: number | null; + on_time_completion_rate?: number | null; + schedule_notes?: string | null; + schedule_adjustments?: Record | null; + created_at: string; + updated_at: string; + finalized_at?: string | null; +} + +export interface QualityCheckBase { + batch_id: string; + check_type: string; + check_time: string; + quality_score: number; + pass_fail: boolean; + defect_count: number; + defect_types?: string[] | null; + check_notes?: string | null; +} + +export interface QualityCheckCreate extends QualityCheckBase { + checker_id?: string | null; + measured_weight?: number | null; + measured_temperature?: number | null; + measured_moisture?: number | null; + measured_dimensions?: Record | null; + target_weight?: number | null; + target_temperature?: number | null; + target_moisture?: number | null; + tolerance_percentage?: number | null; + corrective_actions?: string[] | null; +} + +export interface QualityCheckResponse { + id: string; + tenant_id: string; + batch_id: string; + check_type: string; + check_time: string; + checker_id?: string | null; + quality_score: number; + pass_fail: boolean; + defect_count: number; + defect_types?: string[] | null; + measured_weight?: number | null; + measured_temperature?: number | null; + measured_moisture?: number | null; + measured_dimensions?: Record | null; + target_weight?: number | null; + target_temperature?: number | null; + target_moisture?: number | null; + tolerance_percentage?: number | null; + within_tolerance?: boolean | null; + corrective_action_needed: boolean; + corrective_actions?: string[] | null; + check_notes?: string | null; + photos_urls?: string[] | null; + certificate_url?: string | null; + created_at: string; + updated_at: string; +} + +export interface ProductionDashboardSummary { + active_batches: number; + todays_production_plan: Record[]; + capacity_utilization: number; + on_time_completion_rate: number; + average_quality_score: number; + total_output_today: number; + efficiency_percentage: number; +} + +export interface DailyProductionRequirements { + date: string; + production_plan: Record[]; + total_capacity_needed: number; + available_capacity: number; + capacity_gap: number; + urgent_items: number; + recommended_schedule?: Record | null; +} + +export interface ProductionMetrics { + period_start: string; + period_end: string; + total_batches: number; + completed_batches: number; + completion_rate: number; + average_yield_percentage: number; + on_time_completion_rate: number; + total_production_cost: number; + average_quality_score: number; + efficiency_trends: Record[]; +} + +export interface ProductionBatchListResponse { + batches: ProductionBatchResponse[]; + total_count: number; + page: number; + page_size: number; +} + +export interface ProductionScheduleListResponse { + schedules: ProductionScheduleResponse[]; + total_count: number; + page: number; + page_size: number; +} + +export interface QualityCheckListResponse { + quality_checks: QualityCheckResponse[]; + total_count: number; + page: number; + page_size: number; +} + +export interface ProductionScheduleData { + start_date: string; + end_date: string; + schedules: { + id: string; + date: string; + shift_start: string; + shift_end: string; + capacity_utilization: number; + batches_planned: number; + is_finalized: boolean; + }[]; + total_schedules: number; +} + +export interface ProductionCapacityStatus { + [key: string]: any; +} + +export interface ProductionRequirements { + [key: string]: any; +} + +export interface ProductionYieldMetrics { + [key: string]: any; +} \ No newline at end of file