// ================================================================ // frontend/src/api/services/recipes.ts // ================================================================ /** * Recipes Service - Complete backend alignment * * Backend API structure (3-tier architecture): * - ATOMIC: recipes.py, recipe_quality_configs.py * - OPERATIONS: recipe_operations.py (duplicate, activate, feasibility) * * Last Updated: 2025-10-05 * Status: ✅ Complete - Zero drift with backend */ import { apiClient } from '../client/apiClient'; import type { RecipeResponse, RecipeCreate, RecipeUpdate, RecipeSearchParams, RecipeDuplicateRequest, RecipeFeasibilityResponse, RecipeStatisticsResponse, RecipeCategoriesResponse, RecipeQualityConfiguration, RecipeQualityConfigurationUpdate, } from '../types/recipes'; export class RecipesService { private readonly baseUrl = '/tenants'; // =================================================================== // ATOMIC: Recipes CRUD // Backend: services/recipes/app/api/recipes.py // =================================================================== /** * Create a new recipe * POST /tenants/{tenant_id}/recipes */ async createRecipe(tenantId: string, recipeData: RecipeCreate): Promise { return apiClient.post(`${this.baseUrl}/${tenantId}/recipes`, recipeData); } /** * Search recipes with filters * GET /tenants/{tenant_id}/recipes */ async searchRecipes(tenantId: string, params: RecipeSearchParams = {}): Promise { const searchParams = new URLSearchParams(); // Add all non-empty parameters to the query string 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}/${tenantId}/recipes?${queryString}` : `${this.baseUrl}/${tenantId}/recipes`; return apiClient.get(url); } /** * Get all recipes (shorthand for search without filters) * GET /tenants/{tenant_id}/recipes */ async getRecipes(tenantId: string): Promise { return this.searchRecipes(tenantId); } /** * Get recipe by ID with ingredients * GET /tenants/{tenant_id}/recipes/{recipe_id} */ async getRecipe(tenantId: string, recipeId: string): Promise { return apiClient.get(`${this.baseUrl}/${tenantId}/recipes/${recipeId}`); } /** * Update an existing recipe * PUT /tenants/{tenant_id}/recipes/{recipe_id} */ async updateRecipe(tenantId: string, recipeId: string, recipeData: RecipeUpdate): Promise { return apiClient.put(`${this.baseUrl}/${tenantId}/recipes/${recipeId}`, recipeData); } /** * Delete a recipe * DELETE /tenants/{tenant_id}/recipes/{recipe_id} */ async deleteRecipe(tenantId: string, recipeId: string): Promise<{ message: string }> { return apiClient.delete<{ message: string }>(`${this.baseUrl}/${tenantId}/recipes/${recipeId}`); } // =================================================================== // ATOMIC: Quality Configuration CRUD // Backend: services/recipes/app/api/recipe_quality_configs.py // =================================================================== /** * Get quality configuration for a recipe * GET /tenants/{tenant_id}/recipes/{recipe_id}/quality-configuration */ async getRecipeQualityConfiguration( tenantId: string, recipeId: string ): Promise { return apiClient.get(`${this.baseUrl}/${tenantId}/recipes/${recipeId}/quality-configuration`); } /** * Update quality configuration for a recipe * PUT /tenants/{tenant_id}/recipes/{recipe_id}/quality-configuration */ async updateRecipeQualityConfiguration( tenantId: string, recipeId: string, qualityConfig: RecipeQualityConfigurationUpdate ): Promise { return apiClient.put( `${this.baseUrl}/${tenantId}/recipes/${recipeId}/quality-configuration`, qualityConfig ); } /** * Add quality templates to a recipe stage * POST /tenants/{tenant_id}/recipes/{recipe_id}/quality-configuration/stages/{stage}/templates */ async addQualityTemplatesToStage( tenantId: string, recipeId: string, stage: string, templateIds: string[] ): Promise<{ message: string }> { return apiClient.post<{ message: string }>( `${this.baseUrl}/${tenantId}/recipes/${recipeId}/quality-configuration/stages/${stage}/templates`, templateIds ); } /** * Remove a quality template from a recipe stage * DELETE /tenants/{tenant_id}/recipes/{recipe_id}/quality-configuration/stages/{stage}/templates/{template_id} */ async removeQualityTemplateFromStage( tenantId: string, recipeId: string, stage: string, templateId: string ): Promise<{ message: string }> { return apiClient.delete<{ message: string }>( `${this.baseUrl}/${tenantId}/recipes/${recipeId}/quality-configuration/stages/${stage}/templates/${templateId}` ); } // =================================================================== // OPERATIONS: Recipe Management // Backend: services/recipes/app/api/recipe_operations.py // =================================================================== /** * Duplicate an existing recipe * POST /tenants/{tenant_id}/recipes/{recipe_id}/duplicate */ async duplicateRecipe(tenantId: string, recipeId: string, duplicateData: RecipeDuplicateRequest): Promise { return apiClient.post(`${this.baseUrl}/${tenantId}/recipes/${recipeId}/duplicate`, duplicateData); } /** * Activate a recipe for production * POST /tenants/{tenant_id}/recipes/{recipe_id}/activate */ async activateRecipe(tenantId: string, recipeId: string): Promise { return apiClient.post(`${this.baseUrl}/${tenantId}/recipes/${recipeId}/activate`); } /** * Check if recipe can be produced with current inventory * GET /tenants/{tenant_id}/recipes/{recipe_id}/feasibility */ async checkRecipeFeasibility(tenantId: string, recipeId: string, batchMultiplier: number = 1.0): Promise { const params = new URLSearchParams({ batch_multiplier: String(batchMultiplier) }); return apiClient.get(`${this.baseUrl}/${tenantId}/recipes/${recipeId}/feasibility?${params}`); } /** * Get recipe statistics for dashboard * GET /tenants/{tenant_id}/recipes/dashboard/statistics */ async getRecipeStatistics(tenantId: string): Promise { return apiClient.get(`${this.baseUrl}/${tenantId}/recipes/dashboard/statistics`); } /** * Get list of recipe categories used by tenant * GET /tenants/{tenant_id}/recipes/categories/list */ async getRecipeCategories(tenantId: string): Promise { return apiClient.get(`${this.baseUrl}/${tenantId}/recipes/categories/list`); } } // Create and export singleton instance export const recipesService = new RecipesService(); export default recipesService;