Files
bakery-ia/frontend/src/api/services/recipes.ts
2025-10-07 07:15:07 +02:00

209 lines
7.3 KiB
TypeScript

// ================================================================
// 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<RecipeResponse> {
return apiClient.post<RecipeResponse>(`${this.baseUrl}/${tenantId}/recipes`, recipeData);
}
/**
* Search recipes with filters
* GET /tenants/{tenant_id}/recipes
*/
async searchRecipes(tenantId: string, params: RecipeSearchParams = {}): Promise<RecipeResponse[]> {
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<RecipeResponse[]>(url);
}
/**
* Get all recipes (shorthand for search without filters)
* GET /tenants/{tenant_id}/recipes
*/
async getRecipes(tenantId: string): Promise<RecipeResponse[]> {
return this.searchRecipes(tenantId);
}
/**
* Get recipe by ID with ingredients
* GET /tenants/{tenant_id}/recipes/{recipe_id}
*/
async getRecipe(tenantId: string, recipeId: string): Promise<RecipeResponse> {
return apiClient.get<RecipeResponse>(`${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<RecipeResponse> {
return apiClient.put<RecipeResponse>(`${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<RecipeQualityConfiguration> {
return apiClient.get<RecipeQualityConfiguration>(`${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<RecipeQualityConfiguration> {
return apiClient.put<RecipeQualityConfiguration>(
`${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<RecipeResponse> {
return apiClient.post<RecipeResponse>(`${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<RecipeResponse> {
return apiClient.post<RecipeResponse>(`${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<RecipeFeasibilityResponse> {
const params = new URLSearchParams({ batch_multiplier: String(batchMultiplier) });
return apiClient.get<RecipeFeasibilityResponse>(`${this.baseUrl}/${tenantId}/recipes/${recipeId}/feasibility?${params}`);
}
/**
* Get recipe statistics for dashboard
* GET /tenants/{tenant_id}/recipes/dashboard/statistics
*/
async getRecipeStatistics(tenantId: string): Promise<RecipeStatisticsResponse> {
return apiClient.get<RecipeStatisticsResponse>(`${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<RecipeCategoriesResponse> {
return apiClient.get<RecipeCategoriesResponse>(`${this.baseUrl}/${tenantId}/recipes/categories/list`);
}
}
// Create and export singleton instance
export const recipesService = new RecipesService();
export default recipesService;