180 lines
5.8 KiB
TypeScript
180 lines
5.8 KiB
TypeScript
/**
|
|
* Product Transformation Service - Handle transformation operations
|
|
*/
|
|
import { apiClient } from '../client';
|
|
import {
|
|
ProductTransformationCreate,
|
|
ProductTransformationResponse,
|
|
ProductionStage,
|
|
} from '../types/inventory';
|
|
|
|
export class TransformationService {
|
|
private readonly baseUrl = '/tenants';
|
|
|
|
// Product Transformation Operations
|
|
async createTransformation(
|
|
tenantId: string,
|
|
transformationData: ProductTransformationCreate
|
|
): Promise<ProductTransformationResponse> {
|
|
return apiClient.post<ProductTransformationResponse>(
|
|
`${this.baseUrl}/${tenantId}/transformations`,
|
|
transformationData
|
|
);
|
|
}
|
|
|
|
async getTransformation(
|
|
tenantId: string,
|
|
transformationId: string
|
|
): Promise<ProductTransformationResponse> {
|
|
return apiClient.get<ProductTransformationResponse>(
|
|
`${this.baseUrl}/${tenantId}/transformations/${transformationId}`
|
|
);
|
|
}
|
|
|
|
async getTransformations(
|
|
tenantId: string,
|
|
options?: {
|
|
skip?: number;
|
|
limit?: number;
|
|
ingredient_id?: string;
|
|
source_stage?: ProductionStage;
|
|
target_stage?: ProductionStage;
|
|
days_back?: number;
|
|
}
|
|
): Promise<ProductTransformationResponse[]> {
|
|
const queryParams = new URLSearchParams();
|
|
|
|
if (options?.skip !== undefined) queryParams.append('skip', options.skip.toString());
|
|
if (options?.limit !== undefined) queryParams.append('limit', options.limit.toString());
|
|
if (options?.ingredient_id) queryParams.append('ingredient_id', options.ingredient_id);
|
|
if (options?.source_stage) queryParams.append('source_stage', options.source_stage);
|
|
if (options?.target_stage) queryParams.append('target_stage', options.target_stage);
|
|
if (options?.days_back !== undefined) queryParams.append('days_back', options.days_back.toString());
|
|
|
|
const url = queryParams.toString()
|
|
? `${this.baseUrl}/${tenantId}/transformations?${queryParams.toString()}`
|
|
: `${this.baseUrl}/${tenantId}/transformations`;
|
|
|
|
return apiClient.get<ProductTransformationResponse[]>(url);
|
|
}
|
|
|
|
async getTransformationSummary(
|
|
tenantId: string,
|
|
daysBack: number = 30
|
|
): Promise<any> {
|
|
const queryParams = new URLSearchParams();
|
|
queryParams.append('days_back', daysBack.toString());
|
|
|
|
return apiClient.get<any>(
|
|
`${this.baseUrl}/${tenantId}/transformations/summary?${queryParams.toString()}`
|
|
);
|
|
}
|
|
|
|
// Convenience Methods for Common Transformations
|
|
|
|
async createParBakeToFreshTransformation(
|
|
tenantId: string,
|
|
options: {
|
|
source_ingredient_id: string;
|
|
target_ingredient_id: string;
|
|
quantity: number;
|
|
target_batch_number?: string;
|
|
expiration_hours?: number;
|
|
notes?: string;
|
|
}
|
|
): Promise<{
|
|
transformation_id: string;
|
|
transformation_reference: string;
|
|
source_quantity: number;
|
|
target_quantity: number;
|
|
expiration_date: string;
|
|
message: string;
|
|
}> {
|
|
const queryParams = new URLSearchParams();
|
|
queryParams.append('source_ingredient_id', options.source_ingredient_id);
|
|
queryParams.append('target_ingredient_id', options.target_ingredient_id);
|
|
queryParams.append('quantity', options.quantity.toString());
|
|
|
|
if (options.target_batch_number) {
|
|
queryParams.append('target_batch_number', options.target_batch_number);
|
|
}
|
|
if (options.expiration_hours !== undefined) {
|
|
queryParams.append('expiration_hours', options.expiration_hours.toString());
|
|
}
|
|
if (options.notes) {
|
|
queryParams.append('notes', options.notes);
|
|
}
|
|
|
|
return apiClient.post<any>(
|
|
`${this.baseUrl}/${tenantId}/transformations/par-bake-to-fresh?${queryParams.toString()}`
|
|
);
|
|
}
|
|
|
|
async bakeParBakedCroissants(
|
|
tenantId: string,
|
|
parBakedIngredientId: string,
|
|
freshBakedIngredientId: string,
|
|
quantity: number,
|
|
expirationHours: number = 24,
|
|
notes?: string
|
|
): Promise<ProductTransformationResponse> {
|
|
return this.createTransformation(tenantId, {
|
|
source_ingredient_id: parBakedIngredientId,
|
|
target_ingredient_id: freshBakedIngredientId,
|
|
source_stage: ProductionStage.PAR_BAKED,
|
|
target_stage: ProductionStage.FULLY_BAKED,
|
|
source_quantity: quantity,
|
|
target_quantity: quantity, // Assume 1:1 ratio for croissants
|
|
expiration_calculation_method: 'days_from_transformation',
|
|
expiration_days_offset: Math.max(1, Math.floor(expirationHours / 24)),
|
|
process_notes: notes || `Baked ${quantity} par-baked croissants to fresh croissants`,
|
|
});
|
|
}
|
|
|
|
async transformFrozenToPrepared(
|
|
tenantId: string,
|
|
frozenIngredientId: string,
|
|
preparedIngredientId: string,
|
|
quantity: number,
|
|
notes?: string
|
|
): Promise<ProductTransformationResponse> {
|
|
return this.createTransformation(tenantId, {
|
|
source_ingredient_id: frozenIngredientId,
|
|
target_ingredient_id: preparedIngredientId,
|
|
source_stage: ProductionStage.FROZEN_PRODUCT,
|
|
target_stage: ProductionStage.PREPARED_DOUGH,
|
|
source_quantity: quantity,
|
|
target_quantity: quantity,
|
|
expiration_calculation_method: 'days_from_transformation',
|
|
expiration_days_offset: 3, // Prepared dough typically lasts 3 days
|
|
process_notes: notes || `Thawed and prepared ${quantity} frozen products`,
|
|
});
|
|
}
|
|
|
|
// Analytics and Reporting
|
|
async getTransformationsByStage(
|
|
tenantId: string,
|
|
sourceStage?: ProductionStage,
|
|
targetStage?: ProductionStage,
|
|
limit: number = 50
|
|
): Promise<ProductTransformationResponse[]> {
|
|
return this.getTransformations(tenantId, {
|
|
source_stage: sourceStage,
|
|
target_stage: targetStage,
|
|
limit,
|
|
});
|
|
}
|
|
|
|
async getTransformationsForIngredient(
|
|
tenantId: string,
|
|
ingredientId: string,
|
|
limit: number = 50
|
|
): Promise<ProductTransformationResponse[]> {
|
|
return this.getTransformations(tenantId, {
|
|
ingredient_id: ingredientId,
|
|
limit,
|
|
});
|
|
}
|
|
}
|
|
|
|
export const transformationService = new TransformationService(); |