650 lines
18 KiB
TypeScript
650 lines
18 KiB
TypeScript
/**
|
|
* Production hook for managing production batches, recipes, and scheduling
|
|
*/
|
|
|
|
import { useState, useEffect, useCallback } from 'react';
|
|
import { ProductionService } from '../../services/api/production.service';
|
|
import {
|
|
ProductionBatch,
|
|
ProductionBatchCreate,
|
|
ProductionBatchUpdate,
|
|
Recipe,
|
|
RecipeCreate,
|
|
RecipeUpdate,
|
|
ProductionSchedule,
|
|
ProductionScheduleCreate,
|
|
QualityControl,
|
|
QualityControlCreate
|
|
} from '../../types/production.types';
|
|
import { ApiResponse, PaginatedResponse, QueryParams } from '../../types/api.types';
|
|
|
|
interface ProductionState {
|
|
batches: ProductionBatch[];
|
|
recipes: Recipe[];
|
|
schedules: ProductionSchedule[];
|
|
qualityControls: QualityControl[];
|
|
isLoading: boolean;
|
|
error: string | null;
|
|
pagination: {
|
|
total: number;
|
|
page: number;
|
|
pages: number;
|
|
limit: number;
|
|
};
|
|
}
|
|
|
|
interface ProductionActions {
|
|
// Production Batches
|
|
fetchBatches: (params?: QueryParams) => Promise<void>;
|
|
createBatch: (data: ProductionBatchCreate) => Promise<boolean>;
|
|
updateBatch: (id: string, data: ProductionBatchUpdate) => Promise<boolean>;
|
|
deleteBatch: (id: string) => Promise<boolean>;
|
|
getBatch: (id: string) => Promise<ProductionBatch | null>;
|
|
startBatch: (id: string) => Promise<boolean>;
|
|
completeBatch: (id: string) => Promise<boolean>;
|
|
cancelBatch: (id: string, reason: string) => Promise<boolean>;
|
|
|
|
// Recipes
|
|
fetchRecipes: (params?: QueryParams) => Promise<void>;
|
|
createRecipe: (data: RecipeCreate) => Promise<boolean>;
|
|
updateRecipe: (id: string, data: RecipeUpdate) => Promise<boolean>;
|
|
deleteRecipe: (id: string) => Promise<boolean>;
|
|
getRecipe: (id: string) => Promise<Recipe | null>;
|
|
duplicateRecipe: (id: string, name: string) => Promise<boolean>;
|
|
|
|
// Production Scheduling
|
|
fetchSchedules: (params?: QueryParams) => Promise<void>;
|
|
createSchedule: (data: ProductionScheduleCreate) => Promise<boolean>;
|
|
updateSchedule: (id: string, data: Partial<ProductionScheduleCreate>) => Promise<boolean>;
|
|
deleteSchedule: (id: string) => Promise<boolean>;
|
|
getCapacityAnalysis: (date: string) => Promise<any>;
|
|
|
|
// Quality Control
|
|
fetchQualityControls: (params?: QueryParams) => Promise<void>;
|
|
createQualityControl: (data: QualityControlCreate) => Promise<boolean>;
|
|
updateQualityControl: (id: string, data: Partial<QualityControlCreate>) => Promise<boolean>;
|
|
|
|
// Analytics
|
|
getProductionAnalytics: (startDate?: string, endDate?: string) => Promise<any>;
|
|
getEfficiencyReport: (period: string) => Promise<any>;
|
|
getRecipePerformance: (recipeId?: string) => Promise<any>;
|
|
|
|
// Utilities
|
|
clearError: () => void;
|
|
refresh: () => Promise<void>;
|
|
}
|
|
|
|
export const useProduction = (): ProductionState & ProductionActions => {
|
|
const [state, setState] = useState<ProductionState>({
|
|
batches: [],
|
|
recipes: [],
|
|
schedules: [],
|
|
qualityControls: [],
|
|
isLoading: false,
|
|
error: null,
|
|
pagination: {
|
|
total: 0,
|
|
page: 1,
|
|
pages: 1,
|
|
limit: 20,
|
|
},
|
|
});
|
|
|
|
const productionService = new ProductionService();
|
|
|
|
// Fetch production batches
|
|
const fetchBatches = useCallback(async (params?: QueryParams) => {
|
|
setState(prev => ({ ...prev, isLoading: true, error: null }));
|
|
|
|
try {
|
|
const response = await productionService.getBatches(params);
|
|
|
|
if (response.success && response.data) {
|
|
setState(prev => ({
|
|
...prev,
|
|
batches: Array.isArray(response.data) ? response.data : response.data.items || [],
|
|
pagination: response.data.pagination || prev.pagination,
|
|
isLoading: false,
|
|
}));
|
|
} else {
|
|
setState(prev => ({
|
|
...prev,
|
|
isLoading: false,
|
|
error: response.error || 'Error al cargar lotes de producción',
|
|
}));
|
|
}
|
|
} catch (error) {
|
|
setState(prev => ({
|
|
...prev,
|
|
isLoading: false,
|
|
error: 'Error de conexión al servidor',
|
|
}));
|
|
}
|
|
}, [productionService]);
|
|
|
|
// Create production batch
|
|
const createBatch = useCallback(async (data: ProductionBatchCreate): Promise<boolean> => {
|
|
setState(prev => ({ ...prev, error: null }));
|
|
|
|
try {
|
|
const response = await productionService.createBatch(data);
|
|
|
|
if (response.success) {
|
|
await fetchBatches();
|
|
return true;
|
|
} else {
|
|
setState(prev => ({
|
|
...prev,
|
|
error: response.error || 'Error al crear lote de producción',
|
|
}));
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
setState(prev => ({
|
|
...prev,
|
|
error: 'Error de conexión al servidor',
|
|
}));
|
|
return false;
|
|
}
|
|
}, [productionService, fetchBatches]);
|
|
|
|
// Update production batch
|
|
const updateBatch = useCallback(async (id: string, data: ProductionBatchUpdate): Promise<boolean> => {
|
|
setState(prev => ({ ...prev, error: null }));
|
|
|
|
try {
|
|
const response = await productionService.updateBatch(id, data);
|
|
|
|
if (response.success) {
|
|
await fetchBatches();
|
|
return true;
|
|
} else {
|
|
setState(prev => ({
|
|
...prev,
|
|
error: response.error || 'Error al actualizar lote de producción',
|
|
}));
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
setState(prev => ({
|
|
...prev,
|
|
error: 'Error de conexión al servidor',
|
|
}));
|
|
return false;
|
|
}
|
|
}, [productionService, fetchBatches]);
|
|
|
|
// Delete production batch
|
|
const deleteBatch = useCallback(async (id: string): Promise<boolean> => {
|
|
setState(prev => ({ ...prev, error: null }));
|
|
|
|
try {
|
|
const response = await productionService.deleteBatch(id);
|
|
|
|
if (response.success) {
|
|
setState(prev => ({
|
|
...prev,
|
|
batches: prev.batches.filter(batch => batch.id !== id),
|
|
}));
|
|
return true;
|
|
} else {
|
|
setState(prev => ({
|
|
...prev,
|
|
error: response.error || 'Error al eliminar lote de producción',
|
|
}));
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
setState(prev => ({
|
|
...prev,
|
|
error: 'Error de conexión al servidor',
|
|
}));
|
|
return false;
|
|
}
|
|
}, [productionService]);
|
|
|
|
// Get single production batch
|
|
const getBatch = useCallback(async (id: string): Promise<ProductionBatch | null> => {
|
|
try {
|
|
const response = await productionService.getBatch(id);
|
|
return response.success ? response.data : null;
|
|
} catch (error) {
|
|
console.error('Error fetching batch:', error);
|
|
return null;
|
|
}
|
|
}, [productionService]);
|
|
|
|
// Start production batch
|
|
const startBatch = useCallback(async (id: string): Promise<boolean> => {
|
|
setState(prev => ({ ...prev, error: null }));
|
|
|
|
try {
|
|
const response = await productionService.startBatch(id);
|
|
|
|
if (response.success) {
|
|
await fetchBatches();
|
|
return true;
|
|
} else {
|
|
setState(prev => ({
|
|
...prev,
|
|
error: response.error || 'Error al iniciar lote de producción',
|
|
}));
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
setState(prev => ({
|
|
...prev,
|
|
error: 'Error de conexión al servidor',
|
|
}));
|
|
return false;
|
|
}
|
|
}, [productionService, fetchBatches]);
|
|
|
|
// Complete production batch
|
|
const completeBatch = useCallback(async (id: string): Promise<boolean> => {
|
|
setState(prev => ({ ...prev, error: null }));
|
|
|
|
try {
|
|
const response = await productionService.completeBatch(id);
|
|
|
|
if (response.success) {
|
|
await fetchBatches();
|
|
return true;
|
|
} else {
|
|
setState(prev => ({
|
|
...prev,
|
|
error: response.error || 'Error al completar lote de producción',
|
|
}));
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
setState(prev => ({
|
|
...prev,
|
|
error: 'Error de conexión al servidor',
|
|
}));
|
|
return false;
|
|
}
|
|
}, [productionService, fetchBatches]);
|
|
|
|
// Cancel production batch
|
|
const cancelBatch = useCallback(async (id: string, reason: string): Promise<boolean> => {
|
|
setState(prev => ({ ...prev, error: null }));
|
|
|
|
try {
|
|
const response = await productionService.cancelBatch(id, reason);
|
|
|
|
if (response.success) {
|
|
await fetchBatches();
|
|
return true;
|
|
} else {
|
|
setState(prev => ({
|
|
...prev,
|
|
error: response.error || 'Error al cancelar lote de producción',
|
|
}));
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
setState(prev => ({
|
|
...prev,
|
|
error: 'Error de conexión al servidor',
|
|
}));
|
|
return false;
|
|
}
|
|
}, [productionService, fetchBatches]);
|
|
|
|
// Fetch recipes
|
|
const fetchRecipes = useCallback(async (params?: QueryParams) => {
|
|
setState(prev => ({ ...prev, isLoading: true, error: null }));
|
|
|
|
try {
|
|
const response = await productionService.getRecipes(params);
|
|
|
|
if (response.success && response.data) {
|
|
setState(prev => ({
|
|
...prev,
|
|
recipes: Array.isArray(response.data) ? response.data : response.data.items || [],
|
|
isLoading: false,
|
|
}));
|
|
} else {
|
|
setState(prev => ({
|
|
...prev,
|
|
isLoading: false,
|
|
error: response.error || 'Error al cargar recetas',
|
|
}));
|
|
}
|
|
} catch (error) {
|
|
setState(prev => ({
|
|
...prev,
|
|
isLoading: false,
|
|
error: 'Error de conexión al servidor',
|
|
}));
|
|
}
|
|
}, [productionService]);
|
|
|
|
// Create recipe
|
|
const createRecipe = useCallback(async (data: RecipeCreate): Promise<boolean> => {
|
|
setState(prev => ({ ...prev, error: null }));
|
|
|
|
try {
|
|
const response = await productionService.createRecipe(data);
|
|
|
|
if (response.success) {
|
|
await fetchRecipes();
|
|
return true;
|
|
} else {
|
|
setState(prev => ({
|
|
...prev,
|
|
error: response.error || 'Error al crear receta',
|
|
}));
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
setState(prev => ({
|
|
...prev,
|
|
error: 'Error de conexión al servidor',
|
|
}));
|
|
return false;
|
|
}
|
|
}, [productionService, fetchRecipes]);
|
|
|
|
// Update recipe
|
|
const updateRecipe = useCallback(async (id: string, data: RecipeUpdate): Promise<boolean> => {
|
|
setState(prev => ({ ...prev, error: null }));
|
|
|
|
try {
|
|
const response = await productionService.updateRecipe(id, data);
|
|
|
|
if (response.success) {
|
|
await fetchRecipes();
|
|
return true;
|
|
} else {
|
|
setState(prev => ({
|
|
...prev,
|
|
error: response.error || 'Error al actualizar receta',
|
|
}));
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
setState(prev => ({
|
|
...prev,
|
|
error: 'Error de conexión al servidor',
|
|
}));
|
|
return false;
|
|
}
|
|
}, [productionService, fetchRecipes]);
|
|
|
|
// Delete recipe
|
|
const deleteRecipe = useCallback(async (id: string): Promise<boolean> => {
|
|
setState(prev => ({ ...prev, error: null }));
|
|
|
|
try {
|
|
const response = await productionService.deleteRecipe(id);
|
|
|
|
if (response.success) {
|
|
setState(prev => ({
|
|
...prev,
|
|
recipes: prev.recipes.filter(recipe => recipe.id !== id),
|
|
}));
|
|
return true;
|
|
} else {
|
|
setState(prev => ({
|
|
...prev,
|
|
error: response.error || 'Error al eliminar receta',
|
|
}));
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
setState(prev => ({
|
|
...prev,
|
|
error: 'Error de conexión al servidor',
|
|
}));
|
|
return false;
|
|
}
|
|
}, [productionService]);
|
|
|
|
// Get single recipe
|
|
const getRecipe = useCallback(async (id: string): Promise<Recipe | null> => {
|
|
try {
|
|
const response = await productionService.getRecipe(id);
|
|
return response.success ? response.data : null;
|
|
} catch (error) {
|
|
console.error('Error fetching recipe:', error);
|
|
return null;
|
|
}
|
|
}, [productionService]);
|
|
|
|
// Duplicate recipe
|
|
const duplicateRecipe = useCallback(async (id: string, name: string): Promise<boolean> => {
|
|
setState(prev => ({ ...prev, error: null }));
|
|
|
|
try {
|
|
const response = await productionService.duplicateRecipe(id, name);
|
|
|
|
if (response.success) {
|
|
await fetchRecipes();
|
|
return true;
|
|
} else {
|
|
setState(prev => ({
|
|
...prev,
|
|
error: response.error || 'Error al duplicar receta',
|
|
}));
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
setState(prev => ({
|
|
...prev,
|
|
error: 'Error de conexión al servidor',
|
|
}));
|
|
return false;
|
|
}
|
|
}, [productionService, fetchRecipes]);
|
|
|
|
// Fetch production schedules
|
|
const fetchSchedules = useCallback(async (params?: QueryParams) => {
|
|
try {
|
|
const response = await productionService.getSchedules(params);
|
|
|
|
if (response.success && response.data) {
|
|
setState(prev => ({
|
|
...prev,
|
|
schedules: Array.isArray(response.data) ? response.data : response.data.items || [],
|
|
}));
|
|
}
|
|
} catch (error) {
|
|
console.error('Error fetching schedules:', error);
|
|
}
|
|
}, [productionService]);
|
|
|
|
// Create production schedule
|
|
const createSchedule = useCallback(async (data: ProductionScheduleCreate): Promise<boolean> => {
|
|
try {
|
|
const response = await productionService.createSchedule(data);
|
|
|
|
if (response.success) {
|
|
await fetchSchedules();
|
|
return true;
|
|
}
|
|
return false;
|
|
} catch (error) {
|
|
console.error('Error creating schedule:', error);
|
|
return false;
|
|
}
|
|
}, [productionService, fetchSchedules]);
|
|
|
|
// Update production schedule
|
|
const updateSchedule = useCallback(async (id: string, data: Partial<ProductionScheduleCreate>): Promise<boolean> => {
|
|
try {
|
|
const response = await productionService.updateSchedule(id, data);
|
|
|
|
if (response.success) {
|
|
await fetchSchedules();
|
|
return true;
|
|
}
|
|
return false;
|
|
} catch (error) {
|
|
console.error('Error updating schedule:', error);
|
|
return false;
|
|
}
|
|
}, [productionService, fetchSchedules]);
|
|
|
|
// Delete production schedule
|
|
const deleteSchedule = useCallback(async (id: string): Promise<boolean> => {
|
|
try {
|
|
const response = await productionService.deleteSchedule(id);
|
|
|
|
if (response.success) {
|
|
setState(prev => ({
|
|
...prev,
|
|
schedules: prev.schedules.filter(schedule => schedule.id !== id),
|
|
}));
|
|
return true;
|
|
}
|
|
return false;
|
|
} catch (error) {
|
|
console.error('Error deleting schedule:', error);
|
|
return false;
|
|
}
|
|
}, [productionService]);
|
|
|
|
// Get capacity analysis
|
|
const getCapacityAnalysis = useCallback(async (date: string) => {
|
|
try {
|
|
const response = await productionService.getCapacityAnalysis(date);
|
|
return response.success ? response.data : null;
|
|
} catch (error) {
|
|
console.error('Error fetching capacity analysis:', error);
|
|
return null;
|
|
}
|
|
}, [productionService]);
|
|
|
|
// Fetch quality controls
|
|
const fetchQualityControls = useCallback(async (params?: QueryParams) => {
|
|
try {
|
|
const response = await productionService.getQualityControls(params);
|
|
|
|
if (response.success && response.data) {
|
|
setState(prev => ({
|
|
...prev,
|
|
qualityControls: Array.isArray(response.data) ? response.data : response.data.items || [],
|
|
}));
|
|
}
|
|
} catch (error) {
|
|
console.error('Error fetching quality controls:', error);
|
|
}
|
|
}, [productionService]);
|
|
|
|
// Create quality control
|
|
const createQualityControl = useCallback(async (data: QualityControlCreate): Promise<boolean> => {
|
|
try {
|
|
const response = await productionService.createQualityControl(data);
|
|
|
|
if (response.success) {
|
|
await fetchQualityControls();
|
|
return true;
|
|
}
|
|
return false;
|
|
} catch (error) {
|
|
console.error('Error creating quality control:', error);
|
|
return false;
|
|
}
|
|
}, [productionService, fetchQualityControls]);
|
|
|
|
// Update quality control
|
|
const updateQualityControl = useCallback(async (id: string, data: Partial<QualityControlCreate>): Promise<boolean> => {
|
|
try {
|
|
const response = await productionService.updateQualityControl(id, data);
|
|
|
|
if (response.success) {
|
|
await fetchQualityControls();
|
|
return true;
|
|
}
|
|
return false;
|
|
} catch (error) {
|
|
console.error('Error updating quality control:', error);
|
|
return false;
|
|
}
|
|
}, [productionService, fetchQualityControls]);
|
|
|
|
// Get production analytics
|
|
const getProductionAnalytics = useCallback(async (startDate?: string, endDate?: string) => {
|
|
try {
|
|
const response = await productionService.getAnalytics(startDate, endDate);
|
|
return response.success ? response.data : null;
|
|
} catch (error) {
|
|
console.error('Error fetching production analytics:', error);
|
|
return null;
|
|
}
|
|
}, [productionService]);
|
|
|
|
// Get efficiency report
|
|
const getEfficiencyReport = useCallback(async (period: string) => {
|
|
try {
|
|
const response = await productionService.getEfficiencyReport(period);
|
|
return response.success ? response.data : null;
|
|
} catch (error) {
|
|
console.error('Error fetching efficiency report:', error);
|
|
return null;
|
|
}
|
|
}, [productionService]);
|
|
|
|
// Get recipe performance
|
|
const getRecipePerformance = useCallback(async (recipeId?: string) => {
|
|
try {
|
|
const response = await productionService.getRecipePerformance(recipeId);
|
|
return response.success ? response.data : null;
|
|
} catch (error) {
|
|
console.error('Error fetching recipe performance:', error);
|
|
return null;
|
|
}
|
|
}, [productionService]);
|
|
|
|
// Clear error
|
|
const clearError = useCallback(() => {
|
|
setState(prev => ({ ...prev, error: null }));
|
|
}, []);
|
|
|
|
// Refresh all data
|
|
const refresh = useCallback(async () => {
|
|
await Promise.all([
|
|
fetchBatches(),
|
|
fetchRecipes(),
|
|
fetchSchedules(),
|
|
]);
|
|
}, [fetchBatches, fetchRecipes, fetchSchedules]);
|
|
|
|
// Initialize data on mount
|
|
useEffect(() => {
|
|
refresh();
|
|
}, []);
|
|
|
|
return {
|
|
...state,
|
|
fetchBatches,
|
|
createBatch,
|
|
updateBatch,
|
|
deleteBatch,
|
|
getBatch,
|
|
startBatch,
|
|
completeBatch,
|
|
cancelBatch,
|
|
fetchRecipes,
|
|
createRecipe,
|
|
updateRecipe,
|
|
deleteRecipe,
|
|
getRecipe,
|
|
duplicateRecipe,
|
|
fetchSchedules,
|
|
createSchedule,
|
|
updateSchedule,
|
|
deleteSchedule,
|
|
getCapacityAnalysis,
|
|
fetchQualityControls,
|
|
createQualityControl,
|
|
updateQualityControl,
|
|
getProductionAnalytics,
|
|
getEfficiencyReport,
|
|
getRecipePerformance,
|
|
clearError,
|
|
refresh,
|
|
};
|
|
}; |