Start integrating the onboarding flow with backend 2

This commit is contained in:
Urtzi Alfaro
2025-09-04 18:59:56 +02:00
parent a11fdfba24
commit 9eedc2e5f2
30 changed files with 3432 additions and 4735 deletions

View File

@@ -1,85 +1,17 @@
import { apiClient, ApiResponse } from './client';
// Request/Response Types based on backend schemas
export interface UserRegistration {
email: string;
password: string;
full_name: string;
tenant_name?: string;
role?: 'user' | 'admin' | 'manager';
}
export interface UserLogin {
email: string;
password: string;
}
export interface UserData {
id: string;
email: string;
full_name: string;
is_active: boolean;
is_verified: boolean;
created_at: string;
tenant_id?: string;
role?: string;
}
export interface TokenResponse {
access_token: string;
refresh_token?: string;
token_type: string;
expires_in: number;
user?: UserData;
}
export interface RefreshTokenRequest {
refresh_token: string;
}
export interface PasswordChange {
current_password: string;
new_password: string;
}
export interface PasswordReset {
email: string;
}
export interface PasswordResetConfirm {
token: string;
new_password: string;
}
export interface TokenVerification {
valid: boolean;
user_id?: string;
email?: string;
exp?: number;
message?: string;
}
export interface UserResponse {
id: string;
email: string;
full_name: string;
is_active: boolean;
is_verified: boolean;
created_at: string;
last_login?: string;
phone?: string;
language?: string;
timezone?: string;
tenant_id?: string;
role?: string;
}
export interface UserUpdate {
full_name?: string;
phone?: string;
language?: string;
timezone?: string;
}
import {
UserRegistration,
UserLogin,
UserData,
TokenResponse,
RefreshTokenRequest,
PasswordChange,
PasswordReset,
PasswordResetConfirm,
TokenVerification,
UserResponse,
UserUpdate
} from '../../types/auth.types';
class AuthService {
private readonly baseUrl = '/auth';

View File

@@ -1,4 +1,5 @@
import axios, { AxiosInstance, AxiosResponse, AxiosError, InternalAxiosRequestConfig } from 'axios';
import { ApiResponse, ApiError } from '../../types/api.types';
// Utility functions to access auth and tenant store data from localStorage
const getAuthData = () => {
@@ -27,22 +28,13 @@ const clearAuthData = () => {
localStorage.removeItem('auth-storage');
};
export interface ApiResponse<T = any> {
data: T;
// Client-specific error interface
interface ClientError {
success: boolean;
message?: string;
error?: string;
}
export interface ErrorDetail {
message: string;
code?: string;
field?: string;
}
export interface ApiError {
success: boolean;
error: ErrorDetail;
error: {
message: string;
code?: string;
};
timestamp: string;
}
@@ -116,7 +108,7 @@ class ApiClient {
// Handle network errors
if (!error.response) {
const networkError: ApiError = {
const networkError: ClientError = {
success: false,
error: {
message: 'Network error - please check your connection',

View File

@@ -1,67 +1,27 @@
import { apiClient, ApiResponse } from './client';
// External data types
export interface WeatherData {
id: string;
tenant_id: string;
location_id: string;
date: string;
temperature_avg: number;
temperature_min: number;
temperature_max: number;
humidity: number;
precipitation: number;
wind_speed: number;
condition: string;
description: string;
created_at: string;
}
export interface TrafficData {
id: string;
tenant_id: string;
location_id: string;
date: string;
hour: number;
traffic_level: number;
congestion_index: number;
average_speed: number;
incident_count: number;
created_at: string;
}
export interface EventData {
id: string;
tenant_id: string;
location_id: string;
event_name: string;
event_type: string;
start_date: string;
end_date: string;
expected_attendance?: number;
impact_radius_km?: number;
impact_score: number;
created_at: string;
}
export interface LocationConfig {
id: string;
tenant_id: string;
name: string;
latitude: number;
longitude: number;
address: string;
city: string;
country: string;
is_primary: boolean;
data_sources: {
weather_enabled: boolean;
traffic_enabled: boolean;
events_enabled: boolean;
};
created_at: string;
updated_at: string;
}
import {
WeatherData,
WeatherDataParams,
TrafficData,
TrafficDataParams,
TrafficPatternsParams,
TrafficPattern,
EventData,
EventsParams,
CustomEventCreate,
LocationConfig,
LocationCreate,
ExternalFactorsImpact,
ExternalFactorsParams,
DataQualityReport,
DataSettings,
DataSettingsUpdate,
RefreshDataResponse,
DeleteResponse,
WeatherCondition,
EventType,
RefreshInterval
} from '../../types/data.types';
class DataService {
private readonly baseUrl = '/data';
@@ -75,16 +35,7 @@ class DataService {
return apiClient.get(`${this.baseUrl}/locations/${locationId}`);
}
async createLocation(locationData: {
name: string;
latitude: number;
longitude: number;
address: string;
city: string;
country?: string;
is_primary?: boolean;
data_sources?: LocationConfig['data_sources'];
}): Promise<ApiResponse<LocationConfig>> {
async createLocation(locationData: LocationCreate): Promise<ApiResponse<LocationConfig>> {
return apiClient.post(`${this.baseUrl}/locations`, locationData);
}
@@ -92,18 +43,12 @@ class DataService {
return apiClient.put(`${this.baseUrl}/locations/${locationId}`, locationData);
}
async deleteLocation(locationId: string): Promise<ApiResponse<{ message: string }>> {
async deleteLocation(locationId: string): Promise<ApiResponse<DeleteResponse>> {
return apiClient.delete(`${this.baseUrl}/locations/${locationId}`);
}
// Weather data
async getWeatherData(params?: {
location_id?: string;
start_date?: string;
end_date?: string;
page?: number;
size?: number;
}): Promise<ApiResponse<{ items: WeatherData[]; total: number; page: number; size: number; pages: number }>> {
async getWeatherData(params?: WeatherDataParams): Promise<ApiResponse<{ items: WeatherData[]; total: number; page: number; size: number; pages: number }>> {
const queryParams = new URLSearchParams();
if (params) {
@@ -129,7 +74,7 @@ class DataService {
return apiClient.get(`${this.baseUrl}/weather/forecast/${locationId}?days=${days}`);
}
async refreshWeatherData(locationId?: string): Promise<ApiResponse<{ message: string; updated_records: number }>> {
async refreshWeatherData(locationId?: string): Promise<ApiResponse<RefreshDataResponse>> {
const url = locationId
? `${this.baseUrl}/weather/refresh/${locationId}`
: `${this.baseUrl}/weather/refresh`;
@@ -138,14 +83,7 @@ class DataService {
}
// Traffic data
async getTrafficData(params?: {
location_id?: string;
start_date?: string;
end_date?: string;
hour?: number;
page?: number;
size?: number;
}): Promise<ApiResponse<{ items: TrafficData[]; total: number; page: number; size: number; pages: number }>> {
async getTrafficData(params?: TrafficDataParams): Promise<ApiResponse<{ items: TrafficData[]; total: number; page: number; size: number; pages: number }>> {
const queryParams = new URLSearchParams();
if (params) {
@@ -167,15 +105,7 @@ class DataService {
return apiClient.get(`${this.baseUrl}/traffic/current/${locationId}`);
}
async getTrafficPatterns(locationId: string, params?: {
days_back?: number;
granularity?: 'hourly' | 'daily';
}): Promise<ApiResponse<Array<{
period: string;
average_traffic_level: number;
peak_hours: number[];
congestion_patterns: Record<string, number>;
}>>> {
async getTrafficPatterns(locationId: string, params?: TrafficPatternsParams): Promise<ApiResponse<TrafficPattern[]>> {
const queryParams = new URLSearchParams();
if (params) {
@@ -193,7 +123,7 @@ class DataService {
return apiClient.get(url);
}
async refreshTrafficData(locationId?: string): Promise<ApiResponse<{ message: string; updated_records: number }>> {
async refreshTrafficData(locationId?: string): Promise<ApiResponse<RefreshDataResponse>> {
const url = locationId
? `${this.baseUrl}/traffic/refresh/${locationId}`
: `${this.baseUrl}/traffic/refresh`;
@@ -202,14 +132,7 @@ class DataService {
}
// Events data
async getEvents(params?: {
location_id?: string;
start_date?: string;
end_date?: string;
event_type?: string;
page?: number;
size?: number;
}): Promise<ApiResponse<{ items: EventData[]; total: number; page: number; size: number; pages: number }>> {
async getEvents(params?: EventsParams): Promise<ApiResponse<{ items: EventData[]; total: number; page: number; size: number; pages: number }>> {
const queryParams = new URLSearchParams();
if (params) {
@@ -231,16 +154,7 @@ class DataService {
return apiClient.get(`${this.baseUrl}/events/upcoming/${locationId}?days=${days}`);
}
async createCustomEvent(eventData: {
location_id: string;
event_name: string;
event_type: string;
start_date: string;
end_date: string;
expected_attendance?: number;
impact_radius_km?: number;
impact_score?: number;
}): Promise<ApiResponse<EventData>> {
async createCustomEvent(eventData: CustomEventCreate): Promise<ApiResponse<EventData>> {
return apiClient.post(`${this.baseUrl}/events`, eventData);
}
@@ -248,11 +162,11 @@ class DataService {
return apiClient.put(`${this.baseUrl}/events/${eventId}`, eventData);
}
async deleteEvent(eventId: string): Promise<ApiResponse<{ message: string }>> {
async deleteEvent(eventId: string): Promise<ApiResponse<DeleteResponse>> {
return apiClient.delete(`${this.baseUrl}/events/${eventId}`);
}
async refreshEventsData(locationId?: string): Promise<ApiResponse<{ message: string; updated_records: number }>> {
async refreshEventsData(locationId?: string): Promise<ApiResponse<RefreshDataResponse>> {
const url = locationId
? `${this.baseUrl}/events/refresh/${locationId}`
: `${this.baseUrl}/events/refresh`;
@@ -261,27 +175,7 @@ class DataService {
}
// Combined analytics
async getExternalFactorsImpact(params?: {
location_id?: string;
start_date?: string;
end_date?: string;
}): Promise<ApiResponse<{
weather_impact: {
temperature_correlation: number;
precipitation_impact: number;
most_favorable_conditions: string;
};
traffic_impact: {
congestion_correlation: number;
peak_traffic_effect: number;
optimal_traffic_levels: number[];
};
events_impact: {
positive_events: EventData[];
negative_events: EventData[];
average_event_boost: number;
};
}>> {
async getExternalFactorsImpact(params?: ExternalFactorsParams): Promise<ApiResponse<ExternalFactorsImpact>> {
const queryParams = new URLSearchParams();
if (params) {
@@ -299,64 +193,21 @@ class DataService {
return apiClient.get(url);
}
async getDataQualityReport(): Promise<ApiResponse<{
overall_score: number;
data_sources: Array<{
source: 'weather' | 'traffic' | 'events';
completeness: number;
freshness_hours: number;
reliability_score: number;
last_update: string;
}>;
recommendations: Array<{
priority: 'high' | 'medium' | 'low';
message: string;
action: string;
}>;
}>> {
async getDataQualityReport(): Promise<ApiResponse<DataQualityReport>> {
return apiClient.get(`${this.baseUrl}/quality-report`);
}
// Data configuration
async getDataSettings(): Promise<ApiResponse<{
auto_refresh_enabled: boolean;
refresh_intervals: {
weather_minutes: number;
traffic_minutes: number;
events_hours: number;
};
data_retention_days: {
weather: number;
traffic: number;
events: number;
};
external_apis: {
weather_provider: string;
traffic_provider: string;
events_provider: string;
};
}>> {
async getDataSettings(): Promise<ApiResponse<DataSettings>> {
return apiClient.get(`${this.baseUrl}/settings`);
}
async updateDataSettings(settings: {
auto_refresh_enabled?: boolean;
refresh_intervals?: {
weather_minutes?: number;
traffic_minutes?: number;
events_hours?: number;
};
data_retention_days?: {
weather?: number;
traffic?: number;
events?: number;
};
}): Promise<ApiResponse<any>> {
async updateDataSettings(settings: DataSettingsUpdate): Promise<ApiResponse<DataSettings>> {
return apiClient.put(`${this.baseUrl}/settings`, settings);
}
// Utility methods
getWeatherConditions(): { value: string; label: string; impact: 'positive' | 'negative' | 'neutral' }[] {
getWeatherConditions(): WeatherCondition[] {
return [
{ value: 'sunny', label: 'Sunny', impact: 'positive' },
{ value: 'cloudy', label: 'Cloudy', impact: 'neutral' },
@@ -367,7 +218,7 @@ class DataService {
];
}
getEventTypes(): { value: string; label: string; typical_impact: 'positive' | 'negative' | 'neutral' }[] {
getEventTypes(): EventType[] {
return [
{ value: 'festival', label: 'Festival', typical_impact: 'positive' },
{ value: 'concert', label: 'Concert', typical_impact: 'positive' },
@@ -380,7 +231,7 @@ class DataService {
];
}
getRefreshIntervals(): { value: number; label: string; suitable_for: string[] }[] {
getRefreshIntervals(): RefreshInterval[] {
return [
{ value: 5, label: '5 minutes', suitable_for: ['traffic'] },
{ value: 15, label: '15 minutes', suitable_for: ['traffic'] },

View File

@@ -1,68 +1,10 @@
import { apiClient, ApiResponse } from './client';
// Request/Response Types
export interface ForecastRequest {
product_name: string;
days_ahead: number;
start_date?: string;
include_confidence_intervals?: boolean;
external_factors?: {
weather?: string[];
events?: string[];
holidays?: boolean;
};
}
export interface ForecastResponse {
id: string;
tenant_id: string;
product_name: string;
forecast_date: string;
predicted_demand: number;
confidence_lower: number;
confidence_upper: number;
confidence_level: number;
external_factors: Record<string, any>;
model_version: string;
created_at: string;
actual_demand?: number;
accuracy_score?: number;
}
export interface PredictionBatch {
id: string;
tenant_id: string;
name: string;
description?: string;
parameters: Record<string, any>;
status: 'pending' | 'processing' | 'completed' | 'failed';
progress: number;
total_predictions: number;
completed_predictions: number;
failed_predictions: number;
created_at: string;
completed_at?: string;
error_message?: string;
}
export interface ModelPerformance {
model_id: string;
model_name: string;
version: string;
accuracy_metrics: {
mape: number; // Mean Absolute Percentage Error
rmse: number; // Root Mean Square Error
mae: number; // Mean Absolute Error
r2_score: number;
};
training_data_period: {
start_date: string;
end_date: string;
total_records: number;
};
last_training_date: string;
performance_trend: 'improving' | 'stable' | 'declining';
}
import {
ForecastRequest,
ForecastResponse,
PredictionBatch,
ModelPerformance
} from '../../types/forecasting.types';
class ForecastingService {
private readonly baseUrl = '/forecasting';

View File

@@ -1,35 +1,20 @@
import { apiClient, ApiResponse } from './client';
import { apiClient } from './client';
import { ApiResponse } from '../../types/api.types';
import {
UnitOfMeasure,
ProductType,
StockMovementType,
Ingredient,
Stock,
StockMovement,
StockAlert,
InventorySummary,
StockLevelSummary
} from '../../types/inventory.types';
import { PaginatedResponse } from '../../types/api.types';
// Enums
export enum UnitOfMeasure {
KILOGRAM = 'kg',
GRAM = 'g',
LITER = 'l',
MILLILITER = 'ml',
PIECE = 'piece',
PACKAGE = 'package',
BAG = 'bag',
BOX = 'box',
DOZEN = 'dozen',
}
export enum ProductType {
INGREDIENT = 'ingredient',
FINISHED_PRODUCT = 'finished_product',
}
export enum StockMovementType {
PURCHASE = 'purchase',
SALE = 'sale',
USAGE = 'usage',
WASTE = 'waste',
ADJUSTMENT = 'adjustment',
TRANSFER = 'transfer',
RETURN = 'return',
}
// Request/Response Types
export interface IngredientCreate {
// Service-specific types for Create/Update operations
interface IngredientCreate {
name: string;
product_type?: ProductType;
sku?: string;
@@ -57,74 +42,11 @@ export interface IngredientCreate {
allergen_info?: Record<string, any>;
}
export interface IngredientUpdate {
name?: string;
product_type?: ProductType;
sku?: string;
barcode?: string;
category?: string;
subcategory?: string;
description?: string;
brand?: string;
unit_of_measure?: UnitOfMeasure;
package_size?: number;
average_cost?: number;
standard_cost?: number;
low_stock_threshold?: number;
reorder_point?: number;
reorder_quantity?: number;
max_stock_level?: number;
requires_refrigeration?: boolean;
requires_freezing?: boolean;
storage_temperature_min?: number;
storage_temperature_max?: number;
storage_humidity_max?: number;
shelf_life_days?: number;
storage_instructions?: string;
interface IngredientUpdate extends Partial<IngredientCreate> {
is_active?: boolean;
is_perishable?: boolean;
allergen_info?: Record<string, any>;
}
export interface IngredientResponse {
id: string;
tenant_id: string;
name: string;
product_type: ProductType;
sku?: string;
barcode?: string;
category?: string;
subcategory?: string;
description?: string;
brand?: string;
unit_of_measure: UnitOfMeasure;
package_size?: number;
average_cost?: number;
last_purchase_price?: number;
standard_cost?: number;
low_stock_threshold: number;
reorder_point: number;
reorder_quantity: number;
max_stock_level?: number;
requires_refrigeration: boolean;
requires_freezing: boolean;
storage_temperature_min?: number;
storage_temperature_max?: number;
storage_humidity_max?: number;
shelf_life_days?: number;
storage_instructions?: string;
is_active: boolean;
is_perishable: boolean;
allergen_info?: Record<string, any>;
created_at: string;
updated_at: string;
created_by?: string;
current_stock?: number;
is_low_stock?: boolean;
needs_reorder?: boolean;
}
export interface StockCreate {
interface StockCreate {
ingredient_id: string;
batch_number?: string;
lot_number?: string;
@@ -140,50 +62,12 @@ export interface StockCreate {
quality_status?: string;
}
export interface StockUpdate {
batch_number?: string;
lot_number?: string;
supplier_batch_ref?: string;
current_quantity?: number;
interface StockUpdate extends Partial<StockCreate> {
reserved_quantity?: number;
received_date?: string;
expiration_date?: string;
best_before_date?: string;
unit_cost?: number;
storage_location?: string;
warehouse_zone?: string;
shelf_position?: string;
is_available?: boolean;
quality_status?: string;
}
export interface StockResponse {
id: string;
tenant_id: string;
ingredient_id: string;
batch_number?: string;
lot_number?: string;
supplier_batch_ref?: string;
current_quantity: number;
reserved_quantity: number;
available_quantity: number;
received_date?: string;
expiration_date?: string;
best_before_date?: string;
unit_cost?: number;
total_cost?: number;
storage_location?: string;
warehouse_zone?: string;
shelf_position?: string;
is_available: boolean;
is_expired: boolean;
quality_status: string;
created_at: string;
updated_at: string;
ingredient?: IngredientResponse;
}
export interface StockMovementCreate {
interface StockMovementCreate {
ingredient_id: string;
stock_id?: string;
movement_type: StockMovementType;
@@ -196,107 +80,11 @@ export interface StockMovementCreate {
movement_date?: string;
}
export interface StockMovementResponse {
id: string;
tenant_id: string;
ingredient_id: string;
stock_id?: string;
movement_type: StockMovementType;
quantity: number;
unit_cost?: number;
total_cost?: number;
quantity_before?: number;
quantity_after?: number;
reference_number?: string;
supplier_id?: string;
notes?: string;
reason_code?: string;
movement_date: string;
created_at: string;
created_by?: string;
ingredient?: IngredientResponse;
}
export interface StockAlertResponse {
id: string;
tenant_id: string;
ingredient_id: string;
stock_id?: string;
alert_type: string;
severity: string;
title: string;
message: string;
current_quantity?: number;
threshold_value?: number;
expiration_date?: string;
is_active: boolean;
is_acknowledged: boolean;
acknowledged_by?: string;
acknowledged_at?: string;
is_resolved: boolean;
resolved_by?: string;
resolved_at?: string;
resolution_notes?: string;
created_at: string;
updated_at: string;
ingredient?: IngredientResponse;
}
export interface InventorySummary {
total_ingredients: number;
total_stock_value: number;
low_stock_alerts: number;
expiring_soon_items: number;
expired_items: number;
out_of_stock_items: number;
stock_by_category: Record<string, Record<string, any>>;
recent_movements: number;
recent_purchases: number;
recent_waste: number;
}
export interface StockLevelSummary {
ingredient_id: string;
ingredient_name: string;
unit_of_measure: string;
total_quantity: number;
available_quantity: number;
reserved_quantity: number;
is_low_stock: boolean;
needs_reorder: boolean;
has_expired_stock: boolean;
total_batches: number;
oldest_batch_date?: string;
newest_batch_date?: string;
next_expiration_date?: string;
average_unit_cost?: number;
total_stock_value?: number;
}
export interface PaginatedResponse<T> {
items: T[];
total: number;
page: number;
size: number;
pages: number;
}
export interface InventoryFilter {
category?: string;
is_active?: boolean;
is_low_stock?: boolean;
needs_reorder?: boolean;
search?: string;
}
export interface StockFilter {
ingredient_id?: string;
is_available?: boolean;
is_expired?: boolean;
expiring_within_days?: number;
storage_location?: string;
quality_status?: string;
}
// Type aliases for response consistency
type IngredientResponse = Ingredient;
type StockResponse = Stock;
type StockMovementResponse = StockMovement;
type StockAlertResponse = StockAlert;
class InventoryService {
private readonly baseUrl = '/inventory';

View File

@@ -88,6 +88,7 @@ class OnboardingApiService {
/**
* Step 1: Validate uploaded file and extract unique products
* Now uses Sales Service directly
*/
async validateOnboardingFile(
tenantId: string,
@@ -98,7 +99,7 @@ class OnboardingApiService {
formData.append('file', file);
const response = await apiClient.post<OnboardingFileValidationResponse>(
`${this.basePath}/${tenantId}/onboarding/validate-file`,
`${this.salesBasePath}/${tenantId}/sales/import/validate`,
formData,
{
headers: {
@@ -120,6 +121,7 @@ class OnboardingApiService {
/**
* Step 2: Generate AI-powered inventory suggestions
* Now uses Inventory Service directly
*/
async generateInventorySuggestions(
tenantId: string,
@@ -127,18 +129,24 @@ class OnboardingApiService {
productList: string[]
): Promise<ProductSuggestionsResponse> {
try {
const formData = new FormData();
formData.append('file', file);
formData.append('product_list', JSON.stringify(productList));
if (!productList || !Array.isArray(productList) || productList.length === 0) {
throw new Error('Product list is empty or invalid');
}
// Transform product list into the expected format for BatchClassificationRequest
const products = productList.map(productName => ({
product_name: productName,
// sales_volume is optional, omit it if we don't have the data
sales_data: {} // Additional context can be added later
}));
const requestData = {
products: products
};
const response = await apiClient.post<ProductSuggestionsResponse>(
`${this.basePath}/${tenantId}/onboarding/generate-suggestions`,
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
},
}
`${this.basePath}/${tenantId}/inventory/classify-products-batch`,
requestData
);
if (!response.success) {
@@ -154,34 +162,56 @@ class OnboardingApiService {
/**
* Step 3: Create inventory items from approved suggestions
* Now uses Inventory Service directly
*/
async createInventoryFromSuggestions(
tenantId: string,
approvedSuggestions: any[]
): Promise<InventoryCreationResponse> {
try {
const response = await apiClient.post<InventoryCreationResponse>(
`${this.basePath}/${tenantId}/onboarding/create-inventory`,
{
suggestions: approvedSuggestions
}
);
const createdItems: any[] = [];
const failedItems: any[] = [];
const inventoryMapping: { [productName: string]: string } = {};
if (!response.success) {
throw new Error(`Inventory creation failed: ${response.error || 'Unknown error'}`);
}
// Create inventory items one by one using inventory service
for (const suggestion of approvedSuggestions) {
try {
const ingredientData = {
name: suggestion.suggested_name,
category: suggestion.category,
unit_of_measure: suggestion.unit_of_measure,
shelf_life_days: suggestion.estimated_shelf_life_days,
requires_refrigeration: suggestion.requires_refrigeration,
requires_freezing: suggestion.requires_freezing,
is_seasonal: suggestion.is_seasonal,
product_type: suggestion.product_type
};
// Create inventory mapping if not provided
if (!response.data.inventory_mapping) {
response.data.inventory_mapping = {};
response.data.created_items.forEach((item, index) => {
if (approvedSuggestions[index]) {
response.data.inventory_mapping![approvedSuggestions[index].original_name] = item.id;
const response = await apiClient.post<any>(
`${this.basePath}/${tenantId}/ingredients`,
ingredientData
);
if (response.success) {
createdItems.push(response.data);
inventoryMapping[suggestion.original_name] = response.data.id;
} else {
failedItems.push({ suggestion, error: response.error });
}
});
} catch (error) {
failedItems.push({ suggestion, error: error.message });
}
}
return response.data;
const result = {
created_items: createdItems,
failed_items: failedItems,
total_approved: approvedSuggestions.length,
success_rate: createdItems.length / approvedSuggestions.length,
inventory_mapping: inventoryMapping
};
return result;
} catch (error) {
console.error('Inventory creation failed:', error);
throw error;
@@ -190,6 +220,7 @@ class OnboardingApiService {
/**
* Step 4: Import sales data with inventory mapping
* Now uses Sales Service directly with validation first
*/
async importSalesWithInventory(
tenantId: string,
@@ -197,12 +228,16 @@ class OnboardingApiService {
inventoryMapping: { [productName: string]: string }
): Promise<SalesImportResponse> {
try {
// First validate the file with inventory mapping
await this.validateSalesData(tenantId, file);
// Then import the sales data
const formData = new FormData();
formData.append('file', file);
formData.append('inventory_mapping', JSON.stringify(inventoryMapping));
formData.append('update_existing', 'true');
const response = await apiClient.post<SalesImportResponse>(
`${this.basePath}/${tenantId}/onboarding/import-sales`,
`${this.salesBasePath}/${tenantId}/sales/import`,
formData,
{
headers: {
@@ -224,25 +259,80 @@ class OnboardingApiService {
/**
* Get business model specific recommendations
* Returns static recommendations since orchestration is removed
*/
async getBusinessModelGuide(
tenantId: string,
model: 'production' | 'retail' | 'hybrid'
): Promise<BusinessModelGuide> {
try {
const response = await apiClient.get<BusinessModelGuide>(
`${this.basePath}/${tenantId}/onboarding/business-model-guide?model=${model}`
);
if (!response.success) {
throw new Error(`Failed to get business model guide: ${response.error || 'Unknown error'}`);
// Return static business model guides since we removed orchestration
const guides = {
production: {
title: 'Production Bakery Setup',
description: 'Your bakery focuses on creating products from raw ingredients.',
next_steps: [
'Set up ingredient inventory management',
'Configure recipe management',
'Set up production planning',
'Implement quality control processes'
],
recommended_features: [
'Inventory tracking for raw ingredients',
'Recipe costing and management',
'Production scheduling',
'Supplier management'
],
sample_workflows: [
'Daily production planning based on demand forecasts',
'Inventory reordering based on production schedules',
'Quality control checkpoints during production'
]
},
retail: {
title: 'Retail Bakery Setup',
description: 'Your bakery focuses on selling finished products to customers.',
next_steps: [
'Set up finished product inventory',
'Configure point-of-sale integration',
'Set up customer management',
'Implement sales analytics'
],
recommended_features: [
'Finished product inventory tracking',
'Sales analytics and reporting',
'Customer loyalty programs',
'Promotional campaign management'
],
sample_workflows: [
'Daily sales reporting and analysis',
'Inventory reordering based on sales velocity',
'Customer engagement and retention campaigns'
]
},
hybrid: {
title: 'Hybrid Bakery Setup',
description: 'Your bakery combines production and retail operations.',
next_steps: [
'Set up both ingredient and finished product inventory',
'Configure production-to-retail workflows',
'Set up integrated analytics',
'Implement comprehensive supplier management'
],
recommended_features: [
'Dual inventory management system',
'Production-to-sales analytics',
'Integrated supplier and customer management',
'Cross-channel reporting'
],
sample_workflows: [
'Production planning based on both wholesale and retail demand',
'Integrated inventory management across production and retail',
'Comprehensive business intelligence and reporting'
]
}
};
return response.data;
} catch (error) {
console.error('Failed to get business model guide:', error);
throw error;
}
return guides[model] || guides.hybrid;
}
/**
@@ -366,14 +456,18 @@ class OnboardingApiService {
/**
* Utility: Check if a tenant has completed onboarding
* Uses Auth Service for user progress tracking
*/
async checkOnboardingStatus(tenantId: string): Promise<{ completed: boolean; steps_completed: string[] }> {
try {
const response = await apiClient.get<any>(
`${this.basePath}/${tenantId}/onboarding/status`
'/me/onboarding/progress'
);
return response.data || { completed: false, steps_completed: [] };
return {
completed: response.data?.onboarding_completed || false,
steps_completed: response.data?.completed_steps || []
};
} catch (error) {
console.warn('Could not check onboarding status:', error);
return { completed: false, steps_completed: [] };
@@ -382,11 +476,12 @@ class OnboardingApiService {
/**
* Utility: Mark onboarding as complete
* Uses Auth Service for user progress tracking
*/
async completeOnboarding(tenantId: string, metadata?: any): Promise<void> {
try {
await apiClient.post(
`${this.basePath}/${tenantId}/onboarding/complete`,
'/me/onboarding/complete',
{ metadata }
);
} catch (error) {
@@ -394,6 +489,7 @@ class OnboardingApiService {
// Don't throw error, this is not critical
}
}
}
export const onboardingApiService = new OnboardingApiService();

View File

@@ -1,134 +1,25 @@
import { apiClient, ApiResponse } from './client';
// Enums
export enum OrderStatus {
PENDING = 'pending',
CONFIRMED = 'confirmed',
IN_PREPARATION = 'in_preparation',
READY = 'ready',
DELIVERED = 'delivered',
CANCELLED = 'cancelled',
}
export enum OrderType {
DINE_IN = 'dine_in',
TAKEAWAY = 'takeaway',
DELIVERY = 'delivery',
CATERING = 'catering',
}
// Request/Response Types
export interface OrderItem {
product_id?: string;
product_name: string;
quantity: number;
unit_price: number;
total_price: number;
notes?: string;
customizations?: Record<string, any>;
}
export interface OrderCreate {
customer_id?: string;
customer_name: string;
customer_email?: string;
customer_phone?: string;
order_type: OrderType;
items: OrderItem[];
special_instructions?: string;
delivery_address?: string;
delivery_date?: string;
delivery_time?: string;
payment_method?: string;
}
export interface OrderUpdate {
status?: OrderStatus;
customer_name?: string;
customer_email?: string;
customer_phone?: string;
special_instructions?: string;
delivery_address?: string;
delivery_date?: string;
delivery_time?: string;
estimated_completion_time?: string;
actual_completion_time?: string;
}
export interface OrderResponse {
id: string;
tenant_id: string;
order_number: string;
customer_id?: string;
customer_name: string;
customer_email?: string;
customer_phone?: string;
order_type: OrderType;
status: OrderStatus;
items: OrderItem[];
subtotal: number;
tax_amount: number;
discount_amount: number;
total_amount: number;
special_instructions?: string;
delivery_address?: string;
delivery_date?: string;
delivery_time?: string;
estimated_completion_time?: string;
actual_completion_time?: string;
payment_method?: string;
payment_status?: string;
created_at: string;
updated_at: string;
created_by?: string;
}
export interface Customer {
id: string;
tenant_id: string;
name: string;
email?: string;
phone?: string;
address?: string;
preferences?: Record<string, any>;
total_orders: number;
total_spent: number;
created_at: string;
updated_at: string;
}
export interface OrderAnalytics {
total_orders: number;
total_revenue: number;
average_order_value: number;
order_completion_rate: number;
delivery_success_rate: number;
customer_satisfaction_score?: number;
popular_products: Array<{
product_name: string;
quantity_sold: number;
revenue: number;
}>;
order_trends: Array<{
date: string;
orders: number;
revenue: number;
}>;
}
import { apiClient } from './client';
import { ApiResponse } from '../../types/api.types';
import {
OrderStatus,
OrderType,
OrderItem,
OrderCreate,
OrderUpdate,
OrderResponse,
Customer,
OrderAnalytics,
OrderFilters,
CustomerFilters,
OrderTrendsParams,
OrderTrendData
} from '../../types/orders.types';
class OrdersService {
private readonly baseUrl = '/orders';
// Order management
async getOrders(params?: {
page?: number;
size?: number;
status?: OrderStatus;
order_type?: OrderType;
customer_id?: string;
start_date?: string;
end_date?: string;
}): Promise<ApiResponse<{ items: OrderResponse[]; total: number; page: number; size: number; pages: number }>> {
async getOrders(params?: OrderFilters): Promise<ApiResponse<{ items: OrderResponse[]; total: number; page: number; size: number; pages: number }>> {
const queryParams = new URLSearchParams();
if (params) {
@@ -179,11 +70,7 @@ class OrdersService {
}
// Customer management
async getCustomers(params?: {
page?: number;
size?: number;
search?: string;
}): Promise<ApiResponse<{ items: Customer[]; total: number; page: number; size: number; pages: number }>> {
async getCustomers(params?: CustomerFilters): Promise<ApiResponse<{ items: Customer[]; total: number; page: number; size: number; pages: number }>> {
const queryParams = new URLSearchParams();
if (params) {
@@ -232,16 +119,7 @@ class OrdersService {
return apiClient.get(url);
}
async getOrderTrends(params?: {
start_date?: string;
end_date?: string;
granularity?: 'hourly' | 'daily' | 'weekly' | 'monthly';
}): Promise<ApiResponse<Array<{
period: string;
orders: number;
revenue: number;
avg_order_value: number;
}>>> {
async getOrderTrends(params?: OrderTrendsParams): Promise<ApiResponse<OrderTrendData[]>> {
const queryParams = new URLSearchParams();
if (params) {

View File

@@ -1,126 +1,28 @@
import { apiClient, ApiResponse } from './client';
import { apiClient } from './client';
import { ApiResponse } from '../../types/api.types';
import {
SupplierCreate,
SupplierUpdate,
SupplierResponse,
SupplierSummary,
SupplierSearchParams,
SupplierApproval,
SupplierStatistics,
PurchaseOrderCreate,
PurchaseOrderUpdate,
PurchaseOrderResponse,
PurchaseOrderStatus,
DeliveryCreate,
DeliveryResponse,
DeliveryStatus,
DeliveryReceiptConfirmation,
Supplier
} from '../../types/suppliers.types';
// Enums
export enum PurchaseOrderStatus {
DRAFT = 'draft',
PENDING = 'pending',
APPROVED = 'approved',
SENT = 'sent',
PARTIALLY_RECEIVED = 'partially_received',
RECEIVED = 'received',
CANCELLED = 'cancelled',
}
export enum DeliveryStatus {
SCHEDULED = 'scheduled',
IN_TRANSIT = 'in_transit',
DELIVERED = 'delivered',
FAILED = 'failed',
RETURNED = 'returned',
}
// Request/Response Types
export interface PurchaseOrderItem {
ingredient_id: string;
ingredient_name: string;
quantity: number;
unit_price: number;
total_price: number;
notes?: string;
}
export interface PurchaseOrderCreate {
supplier_id: string;
items: PurchaseOrderItem[];
delivery_date?: string;
notes?: string;
priority?: 'low' | 'normal' | 'high' | 'urgent';
}
export interface PurchaseOrderUpdate {
supplier_id?: string;
delivery_date?: string;
notes?: string;
priority?: 'low' | 'normal' | 'high' | 'urgent';
status?: PurchaseOrderStatus;
}
export interface PurchaseOrderResponse {
id: string;
tenant_id: string;
order_number: string;
supplier_id: string;
supplier_name: string;
status: PurchaseOrderStatus;
items: PurchaseOrderItem[];
subtotal: number;
tax_amount: number;
total_amount: number;
delivery_date?: string;
expected_delivery_date?: string;
actual_delivery_date?: string;
notes?: string;
priority: 'low' | 'normal' | 'high' | 'urgent';
created_at: string;
updated_at: string;
created_by: string;
approved_by?: string;
approved_at?: string;
}
export interface Supplier {
id: string;
tenant_id: string;
name: string;
contact_name?: string;
email?: string;
phone?: string;
address: string;
tax_id?: string;
payment_terms?: string;
delivery_terms?: string;
rating?: number;
is_active: boolean;
performance_metrics: {
on_time_delivery_rate: number;
quality_score: number;
total_orders: number;
average_delivery_time: number;
};
created_at: string;
updated_at: string;
}
export interface DeliveryResponse {
id: string;
tenant_id: string;
purchase_order_id: string;
delivery_number: string;
supplier_id: string;
status: DeliveryStatus;
scheduled_date: string;
actual_delivery_date?: string;
delivery_items: Array<{
ingredient_id: string;
ingredient_name: string;
ordered_quantity: number;
delivered_quantity: number;
unit_price: number;
batch_number?: string;
expiration_date?: string;
quality_notes?: string;
}>;
total_items: number;
delivery_notes?: string;
quality_check_notes?: string;
received_by?: string;
created_at: string;
updated_at: string;
}
class ProcurementService {
private readonly baseUrl = '/procurement';
// Purchase Order management
async getPurchaseOrders(params?: {
page?: number;
@@ -141,90 +43,88 @@ class ProcurementService {
}
const url = queryParams.toString()
? `${this.baseUrl}/purchase-orders?${queryParams.toString()}`
: `${this.baseUrl}/purchase-orders`;
? `/purchase-orders?${queryParams.toString()}`
: `/purchase-orders`;
return apiClient.get(url);
}
async getPurchaseOrder(orderId: string): Promise<ApiResponse<PurchaseOrderResponse>> {
return apiClient.get(`${this.baseUrl}/purchase-orders/${orderId}`);
return apiClient.get(`/purchase-orders/${orderId}`);
}
async createPurchaseOrder(orderData: PurchaseOrderCreate): Promise<ApiResponse<PurchaseOrderResponse>> {
return apiClient.post(`${this.baseUrl}/purchase-orders`, orderData);
return apiClient.post(`/purchase-orders`, orderData);
}
async updatePurchaseOrder(orderId: string, orderData: PurchaseOrderUpdate): Promise<ApiResponse<PurchaseOrderResponse>> {
return apiClient.put(`${this.baseUrl}/purchase-orders/${orderId}`, orderData);
return apiClient.put(`/purchase-orders/${orderId}`, orderData);
}
async approvePurchaseOrder(orderId: string): Promise<ApiResponse<PurchaseOrderResponse>> {
return apiClient.post(`${this.baseUrl}/purchase-orders/${orderId}/approve`);
return apiClient.post(`/purchase-orders/${orderId}/approve`);
}
async sendPurchaseOrder(orderId: string, sendEmail: boolean = true): Promise<ApiResponse<{ message: string; sent_at: string }>> {
return apiClient.post(`${this.baseUrl}/purchase-orders/${orderId}/send`, { send_email: sendEmail });
return apiClient.post(`/purchase-orders/${orderId}/send`, { send_email: sendEmail });
}
async cancelPurchaseOrder(orderId: string, reason?: string): Promise<ApiResponse<PurchaseOrderResponse>> {
return apiClient.post(`${this.baseUrl}/purchase-orders/${orderId}/cancel`, { reason });
return apiClient.post(`/purchase-orders/${orderId}/cancel`, { reason });
}
// Supplier management
async getSuppliers(params?: {
page?: number;
size?: number;
is_active?: boolean;
search?: string;
}): Promise<ApiResponse<{ items: Supplier[]; total: number; page: number; size: number; pages: number }>> {
async getSuppliers(params?: SupplierSearchParams): Promise<ApiResponse<SupplierSummary[]>> {
const queryParams = new URLSearchParams();
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
queryParams.append(key, value.toString());
}
});
if (params.search_term) queryParams.append('search_term', params.search_term);
if (params.supplier_type) queryParams.append('supplier_type', params.supplier_type);
if (params.status) queryParams.append('status', params.status);
if (params.limit) queryParams.append('limit', params.limit.toString());
if (params.offset) queryParams.append('offset', params.offset.toString());
}
const url = queryParams.toString()
? `${this.baseUrl}/suppliers?${queryParams.toString()}`
: `${this.baseUrl}/suppliers`;
? `/suppliers?${queryParams.toString()}`
: `/suppliers`;
return apiClient.get(url);
}
async getSupplier(supplierId: string): Promise<ApiResponse<Supplier>> {
return apiClient.get(`${this.baseUrl}/suppliers/${supplierId}`);
async getSupplier(supplierId: string): Promise<ApiResponse<SupplierResponse>> {
return apiClient.get(`/suppliers/${supplierId}`);
}
async createSupplier(supplierData: Omit<Supplier, 'id' | 'tenant_id' | 'performance_metrics' | 'created_at' | 'updated_at'>): Promise<ApiResponse<Supplier>> {
return apiClient.post(`${this.baseUrl}/suppliers`, supplierData);
async createSupplier(supplierData: SupplierCreate): Promise<ApiResponse<SupplierResponse>> {
return apiClient.post(`/suppliers`, supplierData);
}
async updateSupplier(supplierId: string, supplierData: Partial<Supplier>): Promise<ApiResponse<Supplier>> {
return apiClient.put(`${this.baseUrl}/suppliers/${supplierId}`, supplierData);
async updateSupplier(supplierId: string, supplierData: SupplierUpdate): Promise<ApiResponse<SupplierResponse>> {
return apiClient.put(`/suppliers/${supplierId}`, supplierData);
}
async deleteSupplier(supplierId: string): Promise<ApiResponse<{ message: string }>> {
return apiClient.delete(`${this.baseUrl}/suppliers/${supplierId}`);
return apiClient.delete(`/suppliers/${supplierId}`);
}
async getSupplierPerformance(supplierId: string): Promise<ApiResponse<{
supplier: Supplier;
performance_history: Array<{
month: string;
on_time_delivery_rate: number;
quality_score: number;
order_count: number;
total_value: number;
}>;
recent_deliveries: DeliveryResponse[];
}>> {
return apiClient.get(`${this.baseUrl}/suppliers/${supplierId}/performance`);
async approveSupplier(supplierId: string, approval: SupplierApproval): Promise<ApiResponse<SupplierResponse>> {
return apiClient.post(`/suppliers/${supplierId}/approve`, approval);
}
async getSupplierStatistics(): Promise<ApiResponse<SupplierStatistics>> {
return apiClient.get(`/suppliers/statistics`);
}
async getActiveSuppliers(): Promise<ApiResponse<SupplierSummary[]>> {
return apiClient.get(`/suppliers/active`);
}
async getTopSuppliers(limit: number = 10): Promise<ApiResponse<SupplierSummary[]>> {
return apiClient.get(`/suppliers/top?limit=${limit}`);
}
// Delivery management
async getDeliveries(params?: {
page?: number;
@@ -246,131 +146,32 @@ class ProcurementService {
}
const url = queryParams.toString()
? `${this.baseUrl}/deliveries?${queryParams.toString()}`
: `${this.baseUrl}/deliveries`;
? `/deliveries?${queryParams.toString()}`
: `/deliveries`;
return apiClient.get(url);
}
async getDelivery(deliveryId: string): Promise<ApiResponse<DeliveryResponse>> {
return apiClient.get(`${this.baseUrl}/deliveries/${deliveryId}`);
return apiClient.get(`/deliveries/${deliveryId}`);
}
async receiveDelivery(deliveryId: string, deliveryData: {
delivered_items: Array<{
ingredient_id: string;
delivered_quantity: number;
batch_number?: string;
expiration_date?: string;
quality_notes?: string;
}>;
delivery_notes?: string;
quality_check_notes?: string;
}): Promise<ApiResponse<DeliveryResponse>> {
return apiClient.post(`${this.baseUrl}/deliveries/${deliveryId}/receive`, deliveryData);
async createDelivery(deliveryData: DeliveryCreate): Promise<ApiResponse<DeliveryResponse>> {
return apiClient.post(`/deliveries`, deliveryData);
}
async reportDeliveryIssue(deliveryId: string, issue: {
issue_type: 'late_delivery' | 'quality_issue' | 'quantity_mismatch' | 'damaged_goods' | 'other';
description: string;
affected_items?: string[];
severity: 'low' | 'medium' | 'high';
}): Promise<ApiResponse<{ message: string; issue_id: string }>> {
return apiClient.post(`${this.baseUrl}/deliveries/${deliveryId}/report-issue`, issue);
async updateDelivery(deliveryId: string, deliveryData: Partial<DeliveryCreate>): Promise<ApiResponse<DeliveryResponse>> {
return apiClient.put(`/deliveries/${deliveryId}`, deliveryData);
}
// Analytics and reporting
async getProcurementAnalytics(params?: {
start_date?: string;
end_date?: string;
supplier_id?: string;
}): Promise<ApiResponse<{
total_purchase_value: number;
total_orders: number;
average_order_value: number;
on_time_delivery_rate: number;
quality_score: number;
cost_savings: number;
top_suppliers: Array<{
supplier_name: string;
total_value: number;
order_count: number;
performance_score: number;
}>;
spending_trends: Array<{
month: string;
total_spending: number;
order_count: number;
}>;
}>> {
const queryParams = new URLSearchParams();
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
queryParams.append(key, value.toString());
}
});
}
const url = queryParams.toString()
? `${this.baseUrl}/analytics?${queryParams.toString()}`
: `${this.baseUrl}/analytics`;
return apiClient.get(url);
async updateDeliveryStatus(deliveryId: string, status: DeliveryStatus, notes?: string): Promise<ApiResponse<DeliveryResponse>> {
return apiClient.put(`/deliveries/${deliveryId}/status`, { status, notes });
}
async getSpendingByCategory(params?: {
start_date?: string;
end_date?: string;
}): Promise<ApiResponse<Array<{
category: string;
total_spending: number;
percentage: number;
trend: 'up' | 'down' | 'stable';
}>>> {
const queryParams = new URLSearchParams();
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
queryParams.append(key, value.toString());
}
});
}
const url = queryParams.toString()
? `${this.baseUrl}/spending/by-category?${queryParams.toString()}`
: `${this.baseUrl}/spending/by-category`;
return apiClient.get(url);
async confirmDeliveryReceipt(deliveryId: string, confirmation: DeliveryReceiptConfirmation): Promise<ApiResponse<DeliveryResponse>> {
return apiClient.post(`/deliveries/${deliveryId}/confirm-receipt`, confirmation);
}
// Automated procurement
async getReorderSuggestions(): Promise<ApiResponse<Array<{
ingredient_id: string;
ingredient_name: string;
current_stock: number;
reorder_point: number;
suggested_quantity: number;
preferred_supplier: {
id: string;
name: string;
last_price: number;
lead_time_days: number;
};
urgency: 'low' | 'medium' | 'high' | 'critical';
}>>> {
return apiClient.get(`${this.baseUrl}/reorder-suggestions`);
}
async createReorderFromSuggestions(suggestions: Array<{
ingredient_id: string;
supplier_id: string;
quantity: number;
}>): Promise<ApiResponse<PurchaseOrderResponse[]>> {
return apiClient.post(`${this.baseUrl}/auto-reorder`, { suggestions });
}
// Utility methods
getPurchaseOrderStatusOptions(): { value: PurchaseOrderStatus; label: string; color: string }[] {
@@ -395,14 +196,6 @@ class ProcurementService {
];
}
getPriorityOptions(): { value: string; label: string; color: string }[] {
return [
{ value: 'low', label: 'Low', color: 'gray' },
{ value: 'normal', label: 'Normal', color: 'blue' },
{ value: 'high', label: 'High', color: 'orange' },
{ value: 'urgent', label: 'Urgent', color: 'red' },
];
}
}
export const procurementService = new ProcurementService();

View File

@@ -1,27 +1,21 @@
import { apiClient, ApiResponse } from './client';
import {
ProductionBatchStatus,
QualityCheckStatus,
ProductionPriority,
ProductionBatch,
ProductionSchedule,
QualityCheck,
Recipe
} from '../../types/production.types';
// Enums
export enum ProductionBatchStatus {
PLANNED = 'planned',
IN_PROGRESS = 'in_progress',
COMPLETED = 'completed',
CANCELLED = 'cancelled',
ON_HOLD = 'on_hold',
}
export enum QualityCheckStatus {
PASSED = 'passed',
FAILED = 'failed',
PENDING = 'pending',
REQUIRES_REVIEW = 'requires_review',
}
export enum ProductionPriority {
LOW = 'low',
NORMAL = 'normal',
HIGH = 'high',
URGENT = 'urgent',
}
// Type aliases for service compatibility
type ProductionBatchCreate = Omit<ProductionBatch, 'id' | 'tenant_id' | 'created_at' | 'updated_at'>;
type ProductionBatchUpdate = Partial<ProductionBatchCreate>;
type ProductionBatchResponse = ProductionBatch;
type ProductionScheduleEntry = ProductionSchedule;
type QualityCheckCreate = Omit<QualityCheck, 'id' | 'tenant_id' | 'created_at' | 'updated_at'>;
type QualityCheckResponse = QualityCheck;
// Request/Response Types
export interface ProductionBatchCreate {

View File

@@ -2,69 +2,76 @@
* Training service for ML model training operations
*/
import { ApiClient } from './client';
import { apiClient } from './client';
import { ApiResponse } from '../../types/api.types';
import {
TrainingJob,
TrainingJobCreate,
TrainingJobUpdate
} from '../../types/training.types';
export interface TrainingJob {
id: string;
model_id: string;
status: 'pending' | 'running' | 'completed' | 'failed';
progress: number;
started_at?: string;
completed_at?: string;
error_message?: string;
parameters: Record<string, any>;
metrics?: Record<string, number>;
}
export class TrainingService {
private getTenantId(): string {
const tenantStorage = localStorage.getItem('tenant-storage');
if (tenantStorage) {
try {
const { state } = JSON.parse(tenantStorage);
return state?.currentTenant?.id;
} catch {
return '';
}
}
return '';
}
export interface TrainingJobCreate {
model_id: string;
parameters?: Record<string, any>;
}
export interface TrainingJobUpdate {
parameters?: Record<string, any>;
}
export class TrainingService extends ApiClient {
constructor() {
super('/ml/training');
private getBaseUrl(): string {
const tenantId = this.getTenantId();
return `/tenants/${tenantId}/training`;
}
async getTrainingJobs(modelId?: string): Promise<ApiResponse<TrainingJob[]>> {
const params = modelId ? { model_id: modelId } : {};
return this.get('/', params);
const queryParams = new URLSearchParams();
if (params.model_id) {
queryParams.append('model_id', params.model_id);
}
const url = queryParams.toString()
? `${this.getBaseUrl()}/jobs?${queryParams.toString()}`
: `${this.getBaseUrl()}/jobs`;
return apiClient.get(url);
}
async getTrainingJob(id: string): Promise<ApiResponse<TrainingJob>> {
return this.get(`/${id}`);
return apiClient.get(`${this.getBaseUrl()}/jobs/${id}`);
}
async createTrainingJob(data: TrainingJobCreate): Promise<ApiResponse<TrainingJob>> {
return this.post('/', data);
return apiClient.post(`${this.getBaseUrl()}/jobs`, data);
}
async updateTrainingJob(id: string, data: TrainingJobUpdate): Promise<ApiResponse<TrainingJob>> {
return this.put(`/${id}`, data);
return apiClient.put(`${this.getBaseUrl()}/jobs/${id}`, data);
}
async deleteTrainingJob(id: string): Promise<ApiResponse<void>> {
return this.delete(`/${id}`);
return apiClient.delete(`${this.getBaseUrl()}/jobs/${id}`);
}
async startTraining(id: string): Promise<ApiResponse<TrainingJob>> {
return this.post(`/${id}/start`);
return apiClient.post(`${this.getBaseUrl()}/jobs/${id}/start`);
}
async stopTraining(id: string): Promise<ApiResponse<TrainingJob>> {
return this.post(`/${id}/stop`);
return apiClient.post(`${this.getBaseUrl()}/jobs/${id}/stop`);
}
async getTrainingLogs(id: string): Promise<ApiResponse<string[]>> {
return this.get(`/${id}/logs`);
return apiClient.get(`${this.getBaseUrl()}/jobs/${id}/logs`);
}
async getTrainingMetrics(id: string): Promise<ApiResponse<Record<string, number>>> {
return this.get(`/${id}/metrics`);
return apiClient.get(`${this.getBaseUrl()}/jobs/${id}/metrics`);
}
}
}
export const trainingService = new TrainingService();