212 lines
6.2 KiB
TypeScript
212 lines
6.2 KiB
TypeScript
|
|
/**
|
||
|
|
* Recipes service - API communication layer
|
||
|
|
* Handles all recipe-related HTTP requests using the API client
|
||
|
|
*/
|
||
|
|
|
||
|
|
import { apiClient } from '../client/apiClient';
|
||
|
|
import type {
|
||
|
|
RecipeResponse,
|
||
|
|
RecipeCreate,
|
||
|
|
RecipeUpdate,
|
||
|
|
RecipeSearchParams,
|
||
|
|
RecipeDuplicateRequest,
|
||
|
|
RecipeFeasibilityResponse,
|
||
|
|
RecipeStatisticsResponse,
|
||
|
|
RecipeCategoriesResponse,
|
||
|
|
ProductionBatchResponse,
|
||
|
|
ProductionBatchCreate,
|
||
|
|
ProductionBatchUpdate,
|
||
|
|
} from '../types/recipes';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Recipes API service
|
||
|
|
* All methods return promises that resolve to the response data
|
||
|
|
*/
|
||
|
|
export class RecipesService {
|
||
|
|
private readonly baseUrl = '/recipes';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Create a new recipe
|
||
|
|
*/
|
||
|
|
async createRecipe(recipeData: RecipeCreate): Promise<RecipeResponse> {
|
||
|
|
return apiClient.post<RecipeResponse>(this.baseUrl, recipeData);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get recipe by ID with ingredients
|
||
|
|
*/
|
||
|
|
async getRecipe(recipeId: string): Promise<RecipeResponse> {
|
||
|
|
return apiClient.get<RecipeResponse>(`${this.baseUrl}/${recipeId}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Update an existing recipe
|
||
|
|
*/
|
||
|
|
async updateRecipe(recipeId: string, recipeData: RecipeUpdate): Promise<RecipeResponse> {
|
||
|
|
return apiClient.put<RecipeResponse>(`${this.baseUrl}/${recipeId}`, recipeData);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Delete a recipe
|
||
|
|
*/
|
||
|
|
async deleteRecipe(recipeId: string): Promise<{ message: string }> {
|
||
|
|
return apiClient.delete<{ message: string }>(`${this.baseUrl}/${recipeId}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Search recipes with filters
|
||
|
|
*/
|
||
|
|
async searchRecipes(params: RecipeSearchParams = {}): Promise<RecipeResponse[]> {
|
||
|
|
const searchParams = new URLSearchParams();
|
||
|
|
|
||
|
|
Object.entries(params).forEach(([key, value]) => {
|
||
|
|
if (value !== undefined && value !== null && value !== '') {
|
||
|
|
searchParams.append(key, String(value));
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
const queryString = searchParams.toString();
|
||
|
|
const url = queryString ? `${this.baseUrl}?${queryString}` : this.baseUrl;
|
||
|
|
|
||
|
|
return apiClient.get<RecipeResponse[]>(url);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get all recipes (shorthand for search without filters)
|
||
|
|
*/
|
||
|
|
async getRecipes(): Promise<RecipeResponse[]> {
|
||
|
|
return this.searchRecipes();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Duplicate an existing recipe
|
||
|
|
*/
|
||
|
|
async duplicateRecipe(recipeId: string, duplicateData: RecipeDuplicateRequest): Promise<RecipeResponse> {
|
||
|
|
return apiClient.post<RecipeResponse>(`${this.baseUrl}/${recipeId}/duplicate`, duplicateData);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Activate a recipe for production
|
||
|
|
*/
|
||
|
|
async activateRecipe(recipeId: string): Promise<RecipeResponse> {
|
||
|
|
return apiClient.post<RecipeResponse>(`${this.baseUrl}/${recipeId}/activate`);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Check if recipe can be produced with current inventory
|
||
|
|
*/
|
||
|
|
async checkRecipeFeasibility(recipeId: string, batchMultiplier: number = 1.0): Promise<RecipeFeasibilityResponse> {
|
||
|
|
const params = new URLSearchParams({ batch_multiplier: String(batchMultiplier) });
|
||
|
|
return apiClient.get<RecipeFeasibilityResponse>(`${this.baseUrl}/${recipeId}/feasibility?${params}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get recipe statistics for dashboard
|
||
|
|
*/
|
||
|
|
async getRecipeStatistics(): Promise<RecipeStatisticsResponse> {
|
||
|
|
return apiClient.get<RecipeStatisticsResponse>(`${this.baseUrl}/statistics/dashboard`);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get list of recipe categories used by tenant
|
||
|
|
*/
|
||
|
|
async getRecipeCategories(): Promise<RecipeCategoriesResponse> {
|
||
|
|
return apiClient.get<RecipeCategoriesResponse>(`${this.baseUrl}/categories/list`);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Production Batch Methods
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Create a production batch for a recipe
|
||
|
|
*/
|
||
|
|
async createProductionBatch(batchData: ProductionBatchCreate): Promise<ProductionBatchResponse> {
|
||
|
|
return apiClient.post<ProductionBatchResponse>('/production/batches', batchData);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get production batch by ID
|
||
|
|
*/
|
||
|
|
async getProductionBatch(batchId: string): Promise<ProductionBatchResponse> {
|
||
|
|
return apiClient.get<ProductionBatchResponse>(`/production/batches/${batchId}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Update production batch
|
||
|
|
*/
|
||
|
|
async updateProductionBatch(batchId: string, batchData: ProductionBatchUpdate): Promise<ProductionBatchResponse> {
|
||
|
|
return apiClient.put<ProductionBatchResponse>(`/production/batches/${batchId}`, batchData);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Delete production batch
|
||
|
|
*/
|
||
|
|
async deleteProductionBatch(batchId: string): Promise<{ message: string }> {
|
||
|
|
return apiClient.delete<{ message: string }>(`/production/batches/${batchId}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get production batches for a recipe
|
||
|
|
*/
|
||
|
|
async getRecipeProductionBatches(recipeId: string): Promise<ProductionBatchResponse[]> {
|
||
|
|
return apiClient.get<ProductionBatchResponse[]>(`/production/batches?recipe_id=${recipeId}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get all production batches with optional filtering
|
||
|
|
*/
|
||
|
|
async getProductionBatches(params: {
|
||
|
|
recipe_id?: string;
|
||
|
|
status?: string;
|
||
|
|
start_date?: string;
|
||
|
|
end_date?: string;
|
||
|
|
limit?: number;
|
||
|
|
offset?: number;
|
||
|
|
} = {}): Promise<ProductionBatchResponse[]> {
|
||
|
|
const searchParams = new URLSearchParams();
|
||
|
|
|
||
|
|
Object.entries(params).forEach(([key, value]) => {
|
||
|
|
if (value !== undefined && value !== null && value !== '') {
|
||
|
|
searchParams.append(key, String(value));
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
const queryString = searchParams.toString();
|
||
|
|
const url = queryString ? `/production/batches?${queryString}` : '/production/batches';
|
||
|
|
|
||
|
|
return apiClient.get<ProductionBatchResponse[]>(url);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Start production batch
|
||
|
|
*/
|
||
|
|
async startProductionBatch(batchId: string): Promise<ProductionBatchResponse> {
|
||
|
|
return apiClient.post<ProductionBatchResponse>(`/production/batches/${batchId}/start`);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Complete production batch
|
||
|
|
*/
|
||
|
|
async completeProductionBatch(
|
||
|
|
batchId: string,
|
||
|
|
completionData: {
|
||
|
|
actual_quantity?: number;
|
||
|
|
quality_score?: number;
|
||
|
|
quality_notes?: string;
|
||
|
|
waste_quantity?: number;
|
||
|
|
waste_reason?: string;
|
||
|
|
}
|
||
|
|
): Promise<ProductionBatchResponse> {
|
||
|
|
return apiClient.post<ProductionBatchResponse>(`/production/batches/${batchId}/complete`, completionData);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Cancel production batch
|
||
|
|
*/
|
||
|
|
async cancelProductionBatch(batchId: string, reason?: string): Promise<ProductionBatchResponse> {
|
||
|
|
return apiClient.post<ProductionBatchResponse>(`/production/batches/${batchId}/cancel`, { reason });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create and export singleton instance
|
||
|
|
export const recipesService = new RecipesService();
|
||
|
|
export default recipesService;
|