Fix UI for inventory page 3

This commit is contained in:
Urtzi Alfaro
2025-09-16 12:21:15 +02:00
parent dd4016e217
commit 7aa26d51d3
15 changed files with 1660 additions and 2030 deletions

View File

@@ -174,9 +174,9 @@ export const useStockMovements = (
ingredientId?: string,
limit: number = 50,
offset: number = 0,
options?: Omit<UseQueryOptions<PaginatedResponse<StockMovementResponse>, ApiError>, 'queryKey' | 'queryFn'>
options?: Omit<UseQueryOptions<StockMovementResponse[], ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<PaginatedResponse<StockMovementResponse>, ApiError>({
return useQuery<StockMovementResponse[], ApiError>({
queryKey: inventoryKeys.stock.movements(tenantId, ingredientId),
queryFn: () => inventoryService.getStockMovements(tenantId, ingredientId, limit, offset),
enabled: !!tenantId,
@@ -339,34 +339,117 @@ export const useConsumeStock = (
export const useCreateStockMovement = (
options?: UseMutationOptions<
StockMovementResponse,
ApiError,
StockMovementResponse,
ApiError,
{ tenantId: string; movementData: StockMovementCreate }
>
) => {
const queryClient = useQueryClient();
return useMutation<
StockMovementResponse,
ApiError,
StockMovementResponse,
ApiError,
{ tenantId: string; movementData: StockMovementCreate }
>({
mutationFn: ({ tenantId, movementData }) => inventoryService.createStockMovement(tenantId, movementData),
onSuccess: (data, { tenantId, movementData }) => {
// Invalidate movement queries
queryClient.invalidateQueries({ queryKey: inventoryKeys.stock.movements(tenantId) });
queryClient.invalidateQueries({
queryKey: inventoryKeys.stock.movements(tenantId, movementData.ingredient_id)
queryClient.invalidateQueries({
queryKey: inventoryKeys.stock.movements(tenantId, movementData.ingredient_id)
});
// Invalidate stock queries if this affects stock levels
if (['in', 'out', 'adjustment'].includes(movementData.movement_type)) {
queryClient.invalidateQueries({ queryKey: inventoryKeys.stock.lists() });
queryClient.invalidateQueries({
queryKey: inventoryKeys.stock.byIngredient(tenantId, movementData.ingredient_id)
queryClient.invalidateQueries({
queryKey: inventoryKeys.stock.byIngredient(tenantId, movementData.ingredient_id)
});
queryClient.invalidateQueries({ queryKey: inventoryKeys.ingredients.lists() });
}
},
...options,
});
};
// Custom hooks for stock management operations
export const useStockOperations = (tenantId: string) => {
const queryClient = useQueryClient();
const addStock = useMutation({
mutationFn: async ({ ingredientId, quantity, unit_cost, notes }: {
ingredientId: string;
quantity: number;
unit_cost?: number;
notes?: string;
}) => {
// Create stock entry via backend API
const stockData: StockCreate = {
ingredient_id: ingredientId,
quantity,
unit_price: unit_cost || 0,
notes
};
return inventoryService.addStock(tenantId, stockData);
},
onSuccess: (data, variables) => {
// Invalidate relevant queries
queryClient.invalidateQueries({ queryKey: inventoryKeys.ingredients.lists() });
queryClient.invalidateQueries({ queryKey: inventoryKeys.stock.movements(tenantId) });
queryClient.invalidateQueries({ queryKey: inventoryKeys.stock.movements(tenantId, variables.ingredientId) });
}
});
const consumeStock = useMutation({
mutationFn: async ({ ingredientId, quantity, reference_number, notes, fifo = true }: {
ingredientId: string;
quantity: number;
reference_number?: string;
notes?: string;
fifo?: boolean;
}) => {
const consumptionData: StockConsumptionRequest = {
ingredient_id: ingredientId,
quantity,
reference_number,
notes,
fifo
};
return inventoryService.consumeStock(tenantId, consumptionData);
},
onSuccess: (data, variables) => {
// Invalidate relevant queries
queryClient.invalidateQueries({ queryKey: inventoryKeys.ingredients.lists() });
queryClient.invalidateQueries({ queryKey: inventoryKeys.stock.movements(tenantId) });
queryClient.invalidateQueries({ queryKey: inventoryKeys.stock.movements(tenantId, variables.ingredientId) });
}
});
const adjustStock = useMutation({
mutationFn: async ({ ingredientId, quantity, notes }: {
ingredientId: string;
quantity: number;
notes?: string;
}) => {
// Create adjustment movement via backend API
const movementData: StockMovementCreate = {
ingredient_id: ingredientId,
movement_type: 'adjustment',
quantity,
notes
};
return inventoryService.createStockMovement(tenantId, movementData);
},
onSuccess: (data, variables) => {
// Invalidate relevant queries
queryClient.invalidateQueries({ queryKey: inventoryKeys.ingredients.lists() });
queryClient.invalidateQueries({ queryKey: inventoryKeys.stock.movements(tenantId) });
queryClient.invalidateQueries({ queryKey: inventoryKeys.stock.movements(tenantId, variables.ingredientId) });
}
});
return {
addStock,
consumeStock,
adjustStock
};
};

View File

@@ -172,15 +172,23 @@ export class InventoryService {
ingredientId?: string,
limit: number = 50,
offset: number = 0
): Promise<PaginatedResponse<StockMovementResponse>> {
): Promise<StockMovementResponse[]> {
const queryParams = new URLSearchParams();
if (ingredientId) queryParams.append('ingredient_id', ingredientId);
queryParams.append('limit', limit.toString());
queryParams.append('offset', offset.toString());
queryParams.append('skip', offset.toString()); // Backend expects 'skip' not 'offset'
return apiClient.get<PaginatedResponse<StockMovementResponse>>(
`${this.baseUrl}/${tenantId}/stock/movements?${queryParams.toString()}`
);
const url = `${this.baseUrl}/${tenantId}/stock/movements?${queryParams.toString()}`;
console.log('🔍 Frontend calling API:', url);
try {
const result = await apiClient.get<StockMovementResponse[]>(url);
console.log('✅ Frontend API response:', result);
return result;
} catch (error) {
console.error('❌ Frontend API error:', error);
throw error;
}
}
// Expiry Management

View File

@@ -183,27 +183,36 @@ export interface StockResponse {
export interface StockMovementCreate {
ingredient_id: string;
movement_type: 'in' | 'out' | 'adjustment' | 'transfer' | 'waste';
stock_id?: string;
movement_type: 'purchase' | 'production_use' | 'adjustment' | 'waste' | 'transfer' | 'return' | 'initial_stock';
quantity: number;
unit_price?: number;
unit_cost?: number;
reference_number?: string;
supplier_id?: string;
notes?: string;
related_stock_id?: string;
reason_code?: string;
movement_date?: string;
}
export interface StockMovementResponse {
id: string;
ingredient_id: string;
tenant_id: string;
movement_type: 'in' | 'out' | 'adjustment' | 'transfer' | 'waste';
ingredient_id: string;
stock_id?: string;
movement_type: 'purchase' | 'production_use' | 'adjustment' | 'waste' | 'transfer' | 'return' | 'initial_stock';
quantity: number;
unit_price?: number;
total_value?: number;
unit_cost?: number;
total_cost?: number;
quantity_before?: number;
quantity_after?: number;
reference_number?: string;
supplier_id?: string;
notes?: string;
related_stock_id?: string;
reason_code?: string;
movement_date: string;
created_at: string;
created_by?: string;
ingredient?: IngredientResponse;
}
// Filter and Query Types