Files
bakery-ia/frontend/src/hooks/api/useInventory.ts

475 lines
13 KiB
TypeScript
Raw Normal View History

2025-08-28 10:41:04 +02:00
/**
* Inventory hook for managing inventory state and operations
*/
import { useState, useEffect, useCallback } from 'react';
import { InventoryService } from '../../services/api/inventory.service';
import {
Ingredient,
IngredientCreate,
IngredientUpdate,
StockLevel,
StockMovement,
StockMovementCreate,
InventoryAlert,
QualityCheckCreate,
QualityCheck
} from '../../types/inventory.types';
import { ApiResponse, PaginatedResponse, QueryParams } from '../../types/api.types';
interface InventoryState {
ingredients: Ingredient[];
stockLevels: StockLevel[];
stockMovements: StockMovement[];
alerts: InventoryAlert[];
qualityChecks: QualityCheck[];
isLoading: boolean;
error: string | null;
pagination: {
total: number;
page: number;
pages: number;
limit: number;
};
}
interface InventoryActions {
// Ingredients
fetchIngredients: (params?: QueryParams) => Promise<void>;
createIngredient: (data: IngredientCreate) => Promise<boolean>;
updateIngredient: (id: string, data: IngredientUpdate) => Promise<boolean>;
deleteIngredient: (id: string) => Promise<boolean>;
getIngredient: (id: string) => Promise<Ingredient | null>;
// Stock Levels
fetchStockLevels: (params?: QueryParams) => Promise<void>;
updateStockLevel: (ingredientId: string, quantity: number, reason?: string) => Promise<boolean>;
// Stock Movements
fetchStockMovements: (params?: QueryParams) => Promise<void>;
createStockMovement: (data: StockMovementCreate) => Promise<boolean>;
// Alerts
fetchAlerts: (params?: QueryParams) => Promise<void>;
markAlertAsRead: (id: string) => Promise<boolean>;
dismissAlert: (id: string) => Promise<boolean>;
// Quality Checks
fetchQualityChecks: (params?: QueryParams) => Promise<void>;
createQualityCheck: (data: QualityCheckCreate) => Promise<boolean>;
// Analytics
getInventoryAnalytics: (startDate?: string, endDate?: string) => Promise<any>;
getExpirationReport: () => Promise<any>;
// Utilities
clearError: () => void;
refresh: () => Promise<void>;
}
export const useInventory = (): InventoryState & InventoryActions => {
const [state, setState] = useState<InventoryState>({
ingredients: [],
stockLevels: [],
stockMovements: [],
alerts: [],
qualityChecks: [],
isLoading: false,
error: null,
pagination: {
total: 0,
page: 1,
pages: 1,
limit: 20,
},
});
const inventoryService = new InventoryService();
// Fetch ingredients
const fetchIngredients = useCallback(async (params?: QueryParams) => {
setState(prev => ({ ...prev, isLoading: true, error: null }));
try {
const response = await inventoryService.getIngredients(params);
if (response.success && response.data) {
setState(prev => ({
...prev,
ingredients: 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 ingredientes',
}));
}
} catch (error) {
setState(prev => ({
...prev,
isLoading: false,
error: 'Error de conexión al servidor',
}));
}
}, [inventoryService]);
// Create ingredient
const createIngredient = useCallback(async (data: IngredientCreate): Promise<boolean> => {
setState(prev => ({ ...prev, error: null }));
try {
const response = await inventoryService.createIngredient(data);
if (response.success) {
await fetchIngredients();
return true;
} else {
setState(prev => ({
...prev,
error: response.error || 'Error al crear ingrediente',
}));
return false;
}
} catch (error) {
setState(prev => ({
...prev,
error: 'Error de conexión al servidor',
}));
return false;
}
}, [inventoryService, fetchIngredients]);
// Update ingredient
const updateIngredient = useCallback(async (id: string, data: IngredientUpdate): Promise<boolean> => {
setState(prev => ({ ...prev, error: null }));
try {
const response = await inventoryService.updateIngredient(id, data);
if (response.success) {
await fetchIngredients();
return true;
} else {
setState(prev => ({
...prev,
error: response.error || 'Error al actualizar ingrediente',
}));
return false;
}
} catch (error) {
setState(prev => ({
...prev,
error: 'Error de conexión al servidor',
}));
return false;
}
}, [inventoryService, fetchIngredients]);
// Delete ingredient
const deleteIngredient = useCallback(async (id: string): Promise<boolean> => {
setState(prev => ({ ...prev, error: null }));
try {
const response = await inventoryService.deleteIngredient(id);
if (response.success) {
setState(prev => ({
...prev,
ingredients: prev.ingredients.filter(ingredient => ingredient.id !== id),
}));
return true;
} else {
setState(prev => ({
...prev,
error: response.error || 'Error al eliminar ingrediente',
}));
return false;
}
} catch (error) {
setState(prev => ({
...prev,
error: 'Error de conexión al servidor',
}));
return false;
}
}, [inventoryService]);
// Get single ingredient
const getIngredient = useCallback(async (id: string): Promise<Ingredient | null> => {
try {
const response = await inventoryService.getIngredient(id);
return response.success ? response.data : null;
} catch (error) {
console.error('Error fetching ingredient:', error);
return null;
}
}, [inventoryService]);
// Fetch stock levels
const fetchStockLevels = useCallback(async (params?: QueryParams) => {
setState(prev => ({ ...prev, isLoading: true, error: null }));
try {
const response = await inventoryService.getStockLevels(params);
if (response.success && response.data) {
setState(prev => ({
...prev,
stockLevels: Array.isArray(response.data) ? response.data : response.data.items || [],
isLoading: false,
}));
} else {
setState(prev => ({
...prev,
isLoading: false,
error: response.error || 'Error al cargar niveles de stock',
}));
}
} catch (error) {
setState(prev => ({
...prev,
isLoading: false,
error: 'Error de conexión al servidor',
}));
}
}, [inventoryService]);
// Update stock level
const updateStockLevel = useCallback(async (ingredientId: string, quantity: number, reason?: string): Promise<boolean> => {
setState(prev => ({ ...prev, error: null }));
try {
const response = await inventoryService.updateStockLevel(ingredientId, {
quantity,
reason: reason || 'Manual adjustment'
});
if (response.success) {
await fetchStockLevels();
return true;
} else {
setState(prev => ({
...prev,
error: response.error || 'Error al actualizar stock',
}));
return false;
}
} catch (error) {
setState(prev => ({
...prev,
error: 'Error de conexión al servidor',
}));
return false;
}
}, [inventoryService, fetchStockLevels]);
// Fetch stock movements
const fetchStockMovements = useCallback(async (params?: QueryParams) => {
setState(prev => ({ ...prev, isLoading: true, error: null }));
try {
const response = await inventoryService.getStockMovements(params);
if (response.success && response.data) {
setState(prev => ({
...prev,
stockMovements: Array.isArray(response.data) ? response.data : response.data.items || [],
isLoading: false,
}));
} else {
setState(prev => ({
...prev,
isLoading: false,
error: response.error || 'Error al cargar movimientos de stock',
}));
}
} catch (error) {
setState(prev => ({
...prev,
isLoading: false,
error: 'Error de conexión al servidor',
}));
}
}, [inventoryService]);
// Create stock movement
const createStockMovement = useCallback(async (data: StockMovementCreate): Promise<boolean> => {
setState(prev => ({ ...prev, error: null }));
try {
const response = await inventoryService.createStockMovement(data);
if (response.success) {
await fetchStockMovements();
await fetchStockLevels();
return true;
} else {
setState(prev => ({
...prev,
error: response.error || 'Error al crear movimiento de stock',
}));
return false;
}
} catch (error) {
setState(prev => ({
...prev,
error: 'Error de conexión al servidor',
}));
return false;
}
}, [inventoryService, fetchStockMovements, fetchStockLevels]);
// Fetch alerts
const fetchAlerts = useCallback(async (params?: QueryParams) => {
try {
const response = await inventoryService.getAlerts(params);
if (response.success && response.data) {
setState(prev => ({
...prev,
alerts: Array.isArray(response.data) ? response.data : response.data.items || [],
}));
}
} catch (error) {
console.error('Error fetching alerts:', error);
}
}, [inventoryService]);
// Mark alert as read
const markAlertAsRead = useCallback(async (id: string): Promise<boolean> => {
try {
const response = await inventoryService.markAlertAsRead(id);
if (response.success) {
setState(prev => ({
...prev,
alerts: prev.alerts.map(alert =>
alert.id === id ? { ...alert, is_read: true } : alert
),
}));
return true;
}
return false;
} catch (error) {
console.error('Error marking alert as read:', error);
return false;
}
}, [inventoryService]);
// Dismiss alert
const dismissAlert = useCallback(async (id: string): Promise<boolean> => {
try {
const response = await inventoryService.dismissAlert(id);
if (response.success) {
setState(prev => ({
...prev,
alerts: prev.alerts.filter(alert => alert.id !== id),
}));
return true;
}
return false;
} catch (error) {
console.error('Error dismissing alert:', error);
return false;
}
}, [inventoryService]);
// Fetch quality checks
const fetchQualityChecks = useCallback(async (params?: QueryParams) => {
try {
const response = await inventoryService.getQualityChecks(params);
if (response.success && response.data) {
setState(prev => ({
...prev,
qualityChecks: Array.isArray(response.data) ? response.data : response.data.items || [],
}));
}
} catch (error) {
console.error('Error fetching quality checks:', error);
}
}, [inventoryService]);
// Create quality check
const createQualityCheck = useCallback(async (data: QualityCheckCreate): Promise<boolean> => {
try {
const response = await inventoryService.createQualityCheck(data);
if (response.success) {
await fetchQualityChecks();
return true;
}
return false;
} catch (error) {
console.error('Error creating quality check:', error);
return false;
}
}, [inventoryService, fetchQualityChecks]);
// Get inventory analytics
const getInventoryAnalytics = useCallback(async (startDate?: string, endDate?: string) => {
try {
const response = await inventoryService.getAnalytics(startDate, endDate);
return response.success ? response.data : null;
} catch (error) {
console.error('Error fetching inventory analytics:', error);
return null;
}
}, [inventoryService]);
// Get expiration report
const getExpirationReport = useCallback(async () => {
try {
const response = await inventoryService.getExpirationReport();
return response.success ? response.data : null;
} catch (error) {
console.error('Error fetching expiration report:', error);
return null;
}
}, [inventoryService]);
// Clear error
const clearError = useCallback(() => {
setState(prev => ({ ...prev, error: null }));
}, []);
// Refresh all data
const refresh = useCallback(async () => {
await Promise.all([
fetchIngredients(),
fetchStockLevels(),
fetchAlerts(),
]);
}, [fetchIngredients, fetchStockLevels, fetchAlerts]);
// Initialize data on mount
useEffect(() => {
refresh();
}, []);
return {
...state,
fetchIngredients,
createIngredient,
updateIngredient,
deleteIngredient,
getIngredient,
fetchStockLevels,
updateStockLevel,
fetchStockMovements,
createStockMovement,
fetchAlerts,
markAlertAsRead,
dismissAlert,
fetchQualityChecks,
createQualityCheck,
getInventoryAnalytics,
getExpirationReport,
clearError,
refresh,
};
};