Improve the inventory page
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
import { useMutation, useQuery, useQueryClient, UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';
|
||||
import { inventoryService } from '../services/inventory';
|
||||
import { transformationService } from '../services/transformations';
|
||||
import {
|
||||
IngredientCreate,
|
||||
IngredientUpdate,
|
||||
@@ -17,6 +18,10 @@ import {
|
||||
StockConsumptionRequest,
|
||||
StockConsumptionResponse,
|
||||
PaginatedResponse,
|
||||
ProductTransformationCreate,
|
||||
ProductTransformationResponse,
|
||||
ProductionStage,
|
||||
DeletionSummary,
|
||||
} from '../types/inventory';
|
||||
import { ApiError } from '../client';
|
||||
|
||||
@@ -53,8 +58,23 @@ export const inventoryKeys = {
|
||||
movements: (tenantId: string, ingredientId?: string) =>
|
||||
[...inventoryKeys.stock.all(), 'movements', tenantId, ingredientId] as const,
|
||||
},
|
||||
analytics: (tenantId: string, startDate?: string, endDate?: string) =>
|
||||
analytics: (tenantId: string, startDate?: string, endDate?: string) =>
|
||||
[...inventoryKeys.all, 'analytics', tenantId, { startDate, endDate }] as const,
|
||||
transformations: {
|
||||
all: () => [...inventoryKeys.all, 'transformations'] as const,
|
||||
lists: () => [...inventoryKeys.transformations.all(), 'list'] as const,
|
||||
list: (tenantId: string, filters?: any) =>
|
||||
[...inventoryKeys.transformations.lists(), tenantId, filters] as const,
|
||||
details: () => [...inventoryKeys.transformations.all(), 'detail'] as const,
|
||||
detail: (tenantId: string, transformationId: string) =>
|
||||
[...inventoryKeys.transformations.details(), tenantId, transformationId] as const,
|
||||
summary: (tenantId: string, daysBack?: number) =>
|
||||
[...inventoryKeys.transformations.all(), 'summary', tenantId, daysBack] as const,
|
||||
byIngredient: (tenantId: string, ingredientId: string) =>
|
||||
[...inventoryKeys.transformations.all(), 'by-ingredient', tenantId, ingredientId] as const,
|
||||
byStage: (tenantId: string, sourceStage?: ProductionStage, targetStage?: ProductionStage) =>
|
||||
[...inventoryKeys.transformations.all(), 'by-stage', tenantId, { sourceStage, targetStage }] as const,
|
||||
},
|
||||
} as const;
|
||||
|
||||
// Ingredient Queries
|
||||
@@ -246,19 +266,41 @@ export const useUpdateIngredient = (
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteIngredient = (
|
||||
options?: UseMutationOptions<{ message: string }, ApiError, { tenantId: string; ingredientId: string }>
|
||||
export const useSoftDeleteIngredient = (
|
||||
options?: UseMutationOptions<void, ApiError, { tenantId: string; ingredientId: string }>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<{ message: string }, ApiError, { tenantId: string; ingredientId: string }>({
|
||||
mutationFn: ({ tenantId, ingredientId }) => inventoryService.deleteIngredient(tenantId, ingredientId),
|
||||
|
||||
return useMutation<void, ApiError, { tenantId: string; ingredientId: string }>({
|
||||
mutationFn: ({ tenantId, ingredientId }) => inventoryService.softDeleteIngredient(tenantId, ingredientId),
|
||||
onSuccess: (data, { tenantId, ingredientId }) => {
|
||||
// Remove from cache
|
||||
queryClient.removeQueries({ queryKey: inventoryKeys.ingredients.detail(tenantId, ingredientId) });
|
||||
// Invalidate lists
|
||||
// Invalidate lists to reflect the soft deletion (item marked as inactive)
|
||||
queryClient.invalidateQueries({ queryKey: inventoryKeys.ingredients.lists() });
|
||||
queryClient.invalidateQueries({ queryKey: inventoryKeys.ingredients.byCategory(tenantId) });
|
||||
queryClient.invalidateQueries({ queryKey: inventoryKeys.stock.lists() });
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useHardDeleteIngredient = (
|
||||
options?: UseMutationOptions<DeletionSummary, ApiError, { tenantId: string; ingredientId: string }>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<DeletionSummary, ApiError, { tenantId: string; ingredientId: string }>({
|
||||
mutationFn: ({ tenantId, ingredientId }) => inventoryService.hardDeleteIngredient(tenantId, ingredientId),
|
||||
onSuccess: (data, { tenantId, ingredientId }) => {
|
||||
// Remove from cache completely
|
||||
queryClient.removeQueries({ queryKey: inventoryKeys.ingredients.detail(tenantId, ingredientId) });
|
||||
// Invalidate all related data since everything was deleted
|
||||
queryClient.invalidateQueries({ queryKey: inventoryKeys.ingredients.lists() });
|
||||
queryClient.invalidateQueries({ queryKey: inventoryKeys.ingredients.byCategory(tenantId) });
|
||||
queryClient.invalidateQueries({ queryKey: inventoryKeys.stock.lists() });
|
||||
queryClient.invalidateQueries({ queryKey: inventoryKeys.stock.movements(tenantId) });
|
||||
queryClient.invalidateQueries({ queryKey: inventoryKeys.analytics.all() });
|
||||
},
|
||||
...options,
|
||||
});
|
||||
@@ -452,4 +494,245 @@ export const useStockOperations = (tenantId: string) => {
|
||||
consumeStock,
|
||||
adjustStock
|
||||
};
|
||||
};
|
||||
|
||||
// ===== TRANSFORMATION HOOKS =====
|
||||
|
||||
export const useTransformations = (
|
||||
tenantId: string,
|
||||
options?: {
|
||||
skip?: number;
|
||||
limit?: number;
|
||||
ingredient_id?: string;
|
||||
source_stage?: ProductionStage;
|
||||
target_stage?: ProductionStage;
|
||||
days_back?: number;
|
||||
},
|
||||
queryOptions?: Omit<UseQueryOptions<ProductTransformationResponse[], ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<ProductTransformationResponse[], ApiError>({
|
||||
queryKey: inventoryKeys.transformations.list(tenantId, options),
|
||||
queryFn: () => transformationService.getTransformations(tenantId, options),
|
||||
enabled: !!tenantId,
|
||||
staleTime: 1 * 60 * 1000, // 1 minute
|
||||
...queryOptions,
|
||||
});
|
||||
};
|
||||
|
||||
export const useTransformation = (
|
||||
tenantId: string,
|
||||
transformationId: string,
|
||||
options?: Omit<UseQueryOptions<ProductTransformationResponse, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<ProductTransformationResponse, ApiError>({
|
||||
queryKey: inventoryKeys.transformations.detail(tenantId, transformationId),
|
||||
queryFn: () => transformationService.getTransformation(tenantId, transformationId),
|
||||
enabled: !!tenantId && !!transformationId,
|
||||
staleTime: 2 * 60 * 1000, // 2 minutes
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useTransformationSummary = (
|
||||
tenantId: string,
|
||||
daysBack: number = 30,
|
||||
options?: Omit<UseQueryOptions<any, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<any, ApiError>({
|
||||
queryKey: inventoryKeys.transformations.summary(tenantId, daysBack),
|
||||
queryFn: () => transformationService.getTransformationSummary(tenantId, daysBack),
|
||||
enabled: !!tenantId,
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useTransformationsByIngredient = (
|
||||
tenantId: string,
|
||||
ingredientId: string,
|
||||
limit: number = 50,
|
||||
options?: Omit<UseQueryOptions<ProductTransformationResponse[], ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<ProductTransformationResponse[], ApiError>({
|
||||
queryKey: inventoryKeys.transformations.byIngredient(tenantId, ingredientId),
|
||||
queryFn: () => transformationService.getTransformationsForIngredient(tenantId, ingredientId, limit),
|
||||
enabled: !!tenantId && !!ingredientId,
|
||||
staleTime: 2 * 60 * 1000, // 2 minutes
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useTransformationsByStage = (
|
||||
tenantId: string,
|
||||
sourceStage?: ProductionStage,
|
||||
targetStage?: ProductionStage,
|
||||
limit: number = 50,
|
||||
options?: Omit<UseQueryOptions<ProductTransformationResponse[], ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<ProductTransformationResponse[], ApiError>({
|
||||
queryKey: inventoryKeys.transformations.byStage(tenantId, sourceStage, targetStage),
|
||||
queryFn: () => transformationService.getTransformationsByStage(tenantId, sourceStage, targetStage, limit),
|
||||
enabled: !!tenantId,
|
||||
staleTime: 2 * 60 * 1000, // 2 minutes
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
// ===== TRANSFORMATION MUTATIONS =====
|
||||
|
||||
export const useCreateTransformation = (
|
||||
options?: UseMutationOptions<
|
||||
ProductTransformationResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; transformationData: ProductTransformationCreate }
|
||||
>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<
|
||||
ProductTransformationResponse,
|
||||
ApiError,
|
||||
{ tenantId: string; transformationData: ProductTransformationCreate }
|
||||
>({
|
||||
mutationFn: ({ tenantId, transformationData }) =>
|
||||
transformationService.createTransformation(tenantId, transformationData),
|
||||
onSuccess: (data, { tenantId, transformationData }) => {
|
||||
// Add to cache
|
||||
queryClient.setQueryData(
|
||||
inventoryKeys.transformations.detail(tenantId, data.id),
|
||||
data
|
||||
);
|
||||
// Invalidate related queries
|
||||
queryClient.invalidateQueries({ queryKey: inventoryKeys.transformations.lists() });
|
||||
queryClient.invalidateQueries({ queryKey: inventoryKeys.stock.lists() });
|
||||
queryClient.invalidateQueries({ queryKey: inventoryKeys.ingredients.lists() });
|
||||
queryClient.invalidateQueries({ queryKey: inventoryKeys.stock.movements(tenantId) });
|
||||
// Invalidate ingredient-specific queries
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: inventoryKeys.transformations.byIngredient(tenantId, transformationData.source_ingredient_id)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: inventoryKeys.transformations.byIngredient(tenantId, transformationData.target_ingredient_id)
|
||||
});
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useParBakeTransformation = (
|
||||
options?: UseMutationOptions<
|
||||
any,
|
||||
ApiError,
|
||||
{
|
||||
tenantId: string;
|
||||
source_ingredient_id: string;
|
||||
target_ingredient_id: string;
|
||||
quantity: number;
|
||||
target_batch_number?: string;
|
||||
expiration_hours?: number;
|
||||
notes?: string;
|
||||
}
|
||||
>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<
|
||||
any,
|
||||
ApiError,
|
||||
{
|
||||
tenantId: string;
|
||||
source_ingredient_id: string;
|
||||
target_ingredient_id: string;
|
||||
quantity: number;
|
||||
target_batch_number?: string;
|
||||
expiration_hours?: number;
|
||||
notes?: string;
|
||||
}
|
||||
>({
|
||||
mutationFn: ({ tenantId, ...transformationOptions }) =>
|
||||
transformationService.createParBakeToFreshTransformation(tenantId, transformationOptions),
|
||||
onSuccess: (data, { tenantId, source_ingredient_id, target_ingredient_id }) => {
|
||||
// Invalidate related queries
|
||||
queryClient.invalidateQueries({ queryKey: inventoryKeys.transformations.lists() });
|
||||
queryClient.invalidateQueries({ queryKey: inventoryKeys.stock.lists() });
|
||||
queryClient.invalidateQueries({ queryKey: inventoryKeys.ingredients.lists() });
|
||||
queryClient.invalidateQueries({ queryKey: inventoryKeys.stock.movements(tenantId) });
|
||||
// Invalidate ingredient-specific queries
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: inventoryKeys.transformations.byIngredient(tenantId, source_ingredient_id)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: inventoryKeys.transformations.byIngredient(tenantId, target_ingredient_id)
|
||||
});
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
// Custom hook for common transformation operations
|
||||
export const useTransformationOperations = (tenantId: string) => {
|
||||
const createTransformation = useCreateTransformation();
|
||||
const parBakeTransformation = useParBakeTransformation();
|
||||
|
||||
const bakeParBakedCroissants = useMutation({
|
||||
mutationFn: async ({
|
||||
parBakedIngredientId,
|
||||
freshBakedIngredientId,
|
||||
quantity,
|
||||
expirationHours = 24,
|
||||
notes,
|
||||
}: {
|
||||
parBakedIngredientId: string;
|
||||
freshBakedIngredientId: string;
|
||||
quantity: number;
|
||||
expirationHours?: number;
|
||||
notes?: string;
|
||||
}) => {
|
||||
return transformationService.bakeParBakedCroissants(
|
||||
tenantId,
|
||||
parBakedIngredientId,
|
||||
freshBakedIngredientId,
|
||||
quantity,
|
||||
expirationHours,
|
||||
notes
|
||||
);
|
||||
},
|
||||
onSuccess: () => {
|
||||
// Invalidate related queries
|
||||
createTransformation.reset();
|
||||
},
|
||||
});
|
||||
|
||||
const transformFrozenToPrepared = useMutation({
|
||||
mutationFn: async ({
|
||||
frozenIngredientId,
|
||||
preparedIngredientId,
|
||||
quantity,
|
||||
notes,
|
||||
}: {
|
||||
frozenIngredientId: string;
|
||||
preparedIngredientId: string;
|
||||
quantity: number;
|
||||
notes?: string;
|
||||
}) => {
|
||||
return transformationService.transformFrozenToPrepared(
|
||||
tenantId,
|
||||
frozenIngredientId,
|
||||
preparedIngredientId,
|
||||
quantity,
|
||||
notes
|
||||
);
|
||||
},
|
||||
onSuccess: () => {
|
||||
// Invalidate related queries
|
||||
createTransformation.reset();
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
createTransformation,
|
||||
parBakeTransformation,
|
||||
bakeParBakedCroissants,
|
||||
transformFrozenToPrepared,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user