Start integrating the onboarding flow with backend 2
This commit is contained in:
@@ -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';
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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'] },
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
Reference in New Issue
Block a user