ADD new frontend

This commit is contained in:
Urtzi Alfaro
2025-08-28 10:41:04 +02:00
parent 9c247a5f99
commit 0fd273cfce
492 changed files with 114979 additions and 1632 deletions

View File

@@ -0,0 +1,251 @@
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;
}
class AuthService {
private readonly baseUrl = '/auth';
// Authentication endpoints
async register(userData: UserRegistration): Promise<ApiResponse<TokenResponse>> {
return apiClient.post(`${this.baseUrl}/register`, userData);
}
async login(credentials: UserLogin): Promise<ApiResponse<TokenResponse>> {
const response = await apiClient.post<TokenResponse>(`${this.baseUrl}/login`, credentials);
if (response.success && response.data) {
this.handleSuccessfulAuth(response.data);
}
return response;
}
async logout(): Promise<ApiResponse<{ message: string; success: boolean }>> {
try {
const response = await apiClient.post(`${this.baseUrl}/logout`);
this.clearAuthData();
return response;
} catch (error) {
// Even if logout fails on server, clear local data
this.clearAuthData();
throw error;
}
}
async refreshToken(refreshToken?: string): Promise<ApiResponse<TokenResponse>> {
const token = refreshToken || localStorage.getItem('refresh_token');
if (!token) {
throw new Error('No refresh token available');
}
const response = await apiClient.post<TokenResponse>(`${this.baseUrl}/refresh`, {
refresh_token: token
});
if (response.success && response.data) {
this.handleSuccessfulAuth(response.data);
}
return response;
}
async verifyToken(token?: string): Promise<ApiResponse<TokenVerification>> {
const authToken = token || localStorage.getItem('access_token');
return apiClient.post(`${this.baseUrl}/verify`, { token: authToken });
}
// Password management
async changePassword(passwordData: PasswordChange): Promise<ApiResponse<{ message: string }>> {
return apiClient.post(`${this.baseUrl}/change-password`, passwordData);
}
async resetPassword(email: string): Promise<ApiResponse<{ message: string; reset_token?: string }>> {
return apiClient.post(`${this.baseUrl}/reset-password`, { email });
}
async confirmPasswordReset(data: PasswordResetConfirm): Promise<ApiResponse<{ message: string }>> {
return apiClient.post(`${this.baseUrl}/reset-password/confirm`, data);
}
// User management
async getCurrentUser(): Promise<ApiResponse<UserResponse>> {
return apiClient.get(`${this.baseUrl}/me`);
}
async updateProfile(userData: UserUpdate): Promise<ApiResponse<UserResponse>> {
return apiClient.patch(`${this.baseUrl}/me`, userData);
}
// Email verification
async sendEmailVerification(): Promise<ApiResponse<{ message: string }>> {
return apiClient.post(`${this.baseUrl}/verify-email`);
}
async confirmEmailVerification(token: string): Promise<ApiResponse<{ message: string }>> {
return apiClient.post(`${this.baseUrl}/verify-email/confirm`, { token });
}
// Local auth state management
private handleSuccessfulAuth(tokenData: TokenResponse) {
localStorage.setItem('access_token', tokenData.access_token);
if (tokenData.refresh_token) {
localStorage.setItem('refresh_token', tokenData.refresh_token);
}
if (tokenData.user) {
localStorage.setItem('user_data', JSON.stringify(tokenData.user));
if (tokenData.user.tenant_id) {
localStorage.setItem('tenant_id', tokenData.user.tenant_id);
apiClient.setTenantId(tokenData.user.tenant_id);
}
}
apiClient.setAuthToken(tokenData.access_token);
}
private clearAuthData() {
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
localStorage.removeItem('user_data');
localStorage.removeItem('tenant_id');
apiClient.removeAuthToken();
}
// Utility methods
isAuthenticated(): boolean {
return !!localStorage.getItem('access_token');
}
getCurrentUserData(): UserData | null {
const userData = localStorage.getItem('user_data');
return userData ? JSON.parse(userData) : null;
}
getAccessToken(): string | null {
return localStorage.getItem('access_token');
}
getRefreshToken(): string | null {
return localStorage.getItem('refresh_token');
}
getTenantId(): string | null {
return localStorage.getItem('tenant_id');
}
// Check if token is expired (basic check)
isTokenExpired(): boolean {
const token = this.getAccessToken();
if (!token) return true;
try {
const payload = JSON.parse(atob(token.split('.')[1]));
const currentTime = Date.now() / 1000;
return payload.exp < currentTime;
} catch {
return true;
}
}
// Auto-refresh token if needed
async ensureValidToken(): Promise<boolean> {
if (!this.isAuthenticated()) {
return false;
}
if (this.isTokenExpired()) {
try {
await this.refreshToken();
return true;
} catch {
this.clearAuthData();
return false;
}
}
return true;
}
}
export const authService = new AuthService();

View File

@@ -0,0 +1,221 @@
import axios, { AxiosInstance, AxiosResponse, AxiosError, InternalAxiosRequestConfig } from 'axios';
export interface ApiResponse<T = any> {
data: T;
success: boolean;
message?: string;
error?: string;
}
export interface ErrorDetail {
message: string;
code?: string;
field?: string;
}
export interface ApiError {
success: boolean;
error: ErrorDetail;
timestamp: string;
}
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080/api/v1';
class ApiClient {
private axiosInstance: AxiosInstance;
private refreshTokenPromise: Promise<string> | null = null;
constructor() {
this.axiosInstance = axios.create({
baseURL: API_BASE_URL,
timeout: 30000,
headers: {
'Content-Type': 'application/json',
},
});
this.setupInterceptors();
}
private setupInterceptors(): void {
// Request interceptor - add auth token and tenant ID
this.axiosInstance.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
const token = localStorage.getItem('access_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
const tenantId = localStorage.getItem('tenant_id');
if (tenantId) {
config.headers['X-Tenant-ID'] = tenantId;
}
return config;
},
(error) => Promise.reject(error)
);
// Response interceptor - handle common responses and errors
this.axiosInstance.interceptors.response.use(
(response: AxiosResponse) => response,
async (error: AxiosError) => {
const originalRequest = error.config as InternalAxiosRequestConfig & { _retry?: boolean };
// Handle 401 - Token expired
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
const refreshToken = localStorage.getItem('refresh_token');
if (refreshToken) {
const newToken = await this.refreshToken();
originalRequest.headers.Authorization = `Bearer ${newToken}`;
return this.axiosInstance(originalRequest);
}
} catch (refreshError) {
this.handleAuthFailure();
return Promise.reject(refreshError);
}
}
// Handle 403 - Forbidden
if (error.response?.status === 403) {
console.warn('Access denied - insufficient permissions');
}
// Handle network errors
if (!error.response) {
const networkError: ApiError = {
success: false,
error: {
message: 'Network error - please check your connection',
code: 'NETWORK_ERROR'
},
timestamp: new Date().toISOString()
};
return Promise.reject(networkError);
}
return Promise.reject(error);
}
);
}
private async refreshToken(): Promise<string> {
if (!this.refreshTokenPromise) {
this.refreshTokenPromise = this.performTokenRefresh();
}
const token = await this.refreshTokenPromise;
this.refreshTokenPromise = null;
return token;
}
private async performTokenRefresh(): Promise<string> {
const refreshToken = localStorage.getItem('refresh_token');
if (!refreshToken) {
throw new Error('No refresh token available');
}
const response = await axios.post(`${API_BASE_URL}/auth/refresh`, {
refresh_token: refreshToken,
});
const { access_token, refresh_token } = response.data;
localStorage.setItem('access_token', access_token);
if (refresh_token) {
localStorage.setItem('refresh_token', refresh_token);
}
return access_token;
}
private handleAuthFailure() {
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
localStorage.removeItem('user_data');
localStorage.removeItem('tenant_id');
// Redirect to login
window.location.href = '/login';
}
// HTTP Methods with consistent response format
async get<T = any>(url: string, config = {}): Promise<ApiResponse<T>> {
const response = await this.axiosInstance.get(url, config);
return this.transformResponse(response);
}
async post<T = any>(url: string, data = {}, config = {}): Promise<ApiResponse<T>> {
const response = await this.axiosInstance.post(url, data, config);
return this.transformResponse(response);
}
async put<T = any>(url: string, data = {}, config = {}): Promise<ApiResponse<T>> {
const response = await this.axiosInstance.put(url, data, config);
return this.transformResponse(response);
}
async patch<T = any>(url: string, data = {}, config = {}): Promise<ApiResponse<T>> {
const response = await this.axiosInstance.patch(url, data, config);
return this.transformResponse(response);
}
async delete<T = any>(url: string, config = {}): Promise<ApiResponse<T>> {
const response = await this.axiosInstance.delete(url, config);
return this.transformResponse(response);
}
// File upload helper
async uploadFile<T = any>(url: string, file: File, progressCallback?: (progress: number) => void): Promise<ApiResponse<T>> {
const formData = new FormData();
formData.append('file', file);
const config = {
headers: {
'Content-Type': 'multipart/form-data',
},
onUploadProgress: (progressEvent: any) => {
if (progressCallback && progressEvent.total) {
const progress = Math.round((progressEvent.loaded * 100) / progressEvent.total);
progressCallback(progress);
}
},
};
const response = await this.axiosInstance.post(url, formData, config);
return this.transformResponse(response);
}
// Transform response to consistent format
private transformResponse<T>(response: AxiosResponse): ApiResponse<T> {
return {
data: response.data,
success: response.status >= 200 && response.status < 300,
message: response.statusText,
};
}
// Utility methods
setAuthToken(token: string) {
localStorage.setItem('access_token', token);
this.axiosInstance.defaults.headers.common['Authorization'] = `Bearer ${token}`;
}
removeAuthToken() {
localStorage.removeItem('access_token');
delete this.axiosInstance.defaults.headers.common['Authorization'];
}
setTenantId(tenantId: string) {
localStorage.setItem('tenant_id', tenantId);
this.axiosInstance.defaults.headers.common['X-Tenant-ID'] = tenantId;
}
getBaseURL(): string {
return API_BASE_URL;
}
}
export const apiClient = new ApiClient();

View File

@@ -0,0 +1,395 @@
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;
}
class DataService {
private readonly baseUrl = '/data';
// Location management
async getLocations(): Promise<ApiResponse<LocationConfig[]>> {
return apiClient.get(`${this.baseUrl}/locations`);
}
async getLocation(locationId: string): Promise<ApiResponse<LocationConfig>> {
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>> {
return apiClient.post(`${this.baseUrl}/locations`, locationData);
}
async updateLocation(locationId: string, locationData: Partial<LocationConfig>): Promise<ApiResponse<LocationConfig>> {
return apiClient.put(`${this.baseUrl}/locations/${locationId}`, locationData);
}
async deleteLocation(locationId: string): Promise<ApiResponse<{ message: string }>> {
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 }>> {
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}/weather?${queryParams.toString()}`
: `${this.baseUrl}/weather`;
return apiClient.get(url);
}
async getCurrentWeather(locationId: string): Promise<ApiResponse<WeatherData>> {
return apiClient.get(`${this.baseUrl}/weather/current/${locationId}`);
}
async getWeatherForecast(locationId: string, days: number = 7): Promise<ApiResponse<WeatherData[]>> {
return apiClient.get(`${this.baseUrl}/weather/forecast/${locationId}?days=${days}`);
}
async refreshWeatherData(locationId?: string): Promise<ApiResponse<{ message: string; updated_records: number }>> {
const url = locationId
? `${this.baseUrl}/weather/refresh/${locationId}`
: `${this.baseUrl}/weather/refresh`;
return apiClient.post(url);
}
// 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 }>> {
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}/traffic?${queryParams.toString()}`
: `${this.baseUrl}/traffic`;
return apiClient.get(url);
}
async getCurrentTraffic(locationId: string): Promise<ApiResponse<TrafficData>> {
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>;
}>>> {
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}/traffic/patterns/${locationId}?${queryParams.toString()}`
: `${this.baseUrl}/traffic/patterns/${locationId}`;
return apiClient.get(url);
}
async refreshTrafficData(locationId?: string): Promise<ApiResponse<{ message: string; updated_records: number }>> {
const url = locationId
? `${this.baseUrl}/traffic/refresh/${locationId}`
: `${this.baseUrl}/traffic/refresh`;
return apiClient.post(url);
}
// 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 }>> {
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}/events?${queryParams.toString()}`
: `${this.baseUrl}/events`;
return apiClient.get(url);
}
async getUpcomingEvents(locationId: string, days: number = 30): Promise<ApiResponse<EventData[]>> {
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>> {
return apiClient.post(`${this.baseUrl}/events`, eventData);
}
async updateEvent(eventId: string, eventData: Partial<EventData>): Promise<ApiResponse<EventData>> {
return apiClient.put(`${this.baseUrl}/events/${eventId}`, eventData);
}
async deleteEvent(eventId: string): Promise<ApiResponse<{ message: string }>> {
return apiClient.delete(`${this.baseUrl}/events/${eventId}`);
}
async refreshEventsData(locationId?: string): Promise<ApiResponse<{ message: string; updated_records: number }>> {
const url = locationId
? `${this.baseUrl}/events/refresh/${locationId}`
: `${this.baseUrl}/events/refresh`;
return apiClient.post(url);
}
// 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;
};
}>> {
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}/impact-analysis?${queryParams.toString()}`
: `${this.baseUrl}/impact-analysis`;
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;
}>;
}>> {
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;
};
}>> {
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>> {
return apiClient.put(`${this.baseUrl}/settings`, settings);
}
// Utility methods
getWeatherConditions(): { value: string; label: string; impact: 'positive' | 'negative' | 'neutral' }[] {
return [
{ value: 'sunny', label: 'Sunny', impact: 'positive' },
{ value: 'cloudy', label: 'Cloudy', impact: 'neutral' },
{ value: 'rainy', label: 'Rainy', impact: 'negative' },
{ value: 'stormy', label: 'Stormy', impact: 'negative' },
{ value: 'snowy', label: 'Snowy', impact: 'negative' },
{ value: 'foggy', label: 'Foggy', impact: 'negative' },
];
}
getEventTypes(): { value: string; label: string; typical_impact: 'positive' | 'negative' | 'neutral' }[] {
return [
{ value: 'festival', label: 'Festival', typical_impact: 'positive' },
{ value: 'concert', label: 'Concert', typical_impact: 'positive' },
{ value: 'sports_event', label: 'Sports Event', typical_impact: 'positive' },
{ value: 'conference', label: 'Conference', typical_impact: 'positive' },
{ value: 'construction', label: 'Construction', typical_impact: 'negative' },
{ value: 'roadwork', label: 'Road Work', typical_impact: 'negative' },
{ value: 'protest', label: 'Protest', typical_impact: 'negative' },
{ value: 'holiday', label: 'Holiday', typical_impact: 'neutral' },
];
}
getRefreshIntervals(): { value: number; label: string; suitable_for: string[] }[] {
return [
{ value: 5, label: '5 minutes', suitable_for: ['traffic'] },
{ value: 15, label: '15 minutes', suitable_for: ['traffic'] },
{ value: 30, label: '30 minutes', suitable_for: ['weather', 'traffic'] },
{ value: 60, label: '1 hour', suitable_for: ['weather'] },
{ value: 240, label: '4 hours', suitable_for: ['weather'] },
{ value: 1440, label: '24 hours', suitable_for: ['events'] },
];
}
}
export const dataService = new DataService();

View File

@@ -0,0 +1,325 @@
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';
}
class ForecastingService {
private readonly baseUrl = '/forecasting';
// Forecast generation
async createForecast(forecastData: ForecastRequest): Promise<ApiResponse<ForecastResponse[]>> {
return apiClient.post(`${this.baseUrl}/forecasts`, forecastData);
}
async getForecasts(params?: {
page?: number;
size?: number;
product_name?: string;
start_date?: string;
end_date?: string;
}): Promise<ApiResponse<{ items: ForecastResponse[]; total: number; page: number; size: number; pages: 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}/forecasts?${queryParams.toString()}`
: `${this.baseUrl}/forecasts`;
return apiClient.get(url);
}
async getForecast(forecastId: string): Promise<ApiResponse<ForecastResponse>> {
return apiClient.get(`${this.baseUrl}/forecasts/${forecastId}`);
}
async updateForecastActual(forecastId: string, actualDemand: number): Promise<ApiResponse<ForecastResponse>> {
return apiClient.patch(`${this.baseUrl}/forecasts/${forecastId}`, {
actual_demand: actualDemand
});
}
// Batch predictions
async createPredictionBatch(batchData: {
name: string;
description?: string;
products: string[];
days_ahead: number;
parameters?: Record<string, any>;
}): Promise<ApiResponse<PredictionBatch>> {
return apiClient.post(`${this.baseUrl}/batches`, batchData);
}
async getPredictionBatches(): Promise<ApiResponse<PredictionBatch[]>> {
return apiClient.get(`${this.baseUrl}/batches`);
}
async getPredictionBatch(batchId: string): Promise<ApiResponse<PredictionBatch>> {
return apiClient.get(`${this.baseUrl}/batches/${batchId}`);
}
async getPredictionBatchResults(batchId: string): Promise<ApiResponse<ForecastResponse[]>> {
return apiClient.get(`${this.baseUrl}/batches/${batchId}/results`);
}
// Model performance and metrics
async getModelPerformance(): Promise<ApiResponse<ModelPerformance[]>> {
return apiClient.get(`${this.baseUrl}/models/performance`);
}
async getAccuracyReport(params?: {
start_date?: string;
end_date?: string;
product_name?: string;
}): Promise<ApiResponse<{
overall_accuracy: number;
product_accuracy: Array<{
product_name: string;
accuracy: number;
total_predictions: number;
recent_trend: 'improving' | 'stable' | 'declining';
}>;
accuracy_trends: Array<{
date: string;
accuracy: 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}/accuracy?${queryParams.toString()}`
: `${this.baseUrl}/accuracy`;
return apiClient.get(url);
}
// Demand analytics
async getDemandTrends(params?: {
product_name?: string;
start_date?: string;
end_date?: string;
granularity?: 'daily' | 'weekly' | 'monthly';
}): Promise<ApiResponse<Array<{
date: string;
actual_demand: number;
predicted_demand: number;
confidence_lower: number;
confidence_upper: number;
accuracy: 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}/trends?${queryParams.toString()}`
: `${this.baseUrl}/trends`;
return apiClient.get(url);
}
async getSeasonalPatterns(productName?: string): Promise<ApiResponse<{
product_name: string;
seasonal_components: Array<{
period: 'monthly' | 'weekly' | 'daily';
strength: number;
pattern: number[];
}>;
holiday_effects: Array<{
holiday: string;
impact_factor: number;
confidence: number;
}>;
}>> {
const url = productName
? `${this.baseUrl}/patterns?product_name=${encodeURIComponent(productName)}`
: `${this.baseUrl}/patterns`;
return apiClient.get(url);
}
// External factors impact
async getWeatherImpact(params?: {
product_name?: string;
weather_conditions?: string[];
}): Promise<ApiResponse<Array<{
weather_condition: string;
impact_factor: number;
confidence: number;
affected_products: string[];
}>>> {
const queryParams = new URLSearchParams();
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
if (Array.isArray(value)) {
value.forEach(v => queryParams.append(key, v));
} else {
queryParams.append(key, value.toString());
}
}
});
}
const url = queryParams.toString()
? `${this.baseUrl}/weather-impact?${queryParams.toString()}`
: `${this.baseUrl}/weather-impact`;
return apiClient.get(url);
}
// Model management
async retrainModel(params?: {
model_type?: string;
data_range?: { start_date: string; end_date: string };
hyperparameters?: Record<string, any>;
}): Promise<ApiResponse<{ task_id: string; message: string }>> {
return apiClient.post(`${this.baseUrl}/models/retrain`, params);
}
async getTrainingStatus(taskId: string): Promise<ApiResponse<{
status: 'pending' | 'training' | 'completed' | 'failed';
progress: number;
message: string;
results?: {
accuracy_improvement: number;
training_time: number;
model_size: number;
};
}>> {
return apiClient.get(`${this.baseUrl}/models/training-status/${taskId}`);
}
// Configuration
async getForecastingSettings(): Promise<ApiResponse<{
default_forecast_horizon: number;
confidence_level: number;
retraining_frequency: number;
external_data_sources: string[];
notification_preferences: Record<string, boolean>;
}>> {
return apiClient.get(`${this.baseUrl}/settings`);
}
async updateForecastingSettings(settings: {
default_forecast_horizon?: number;
confidence_level?: number;
retraining_frequency?: number;
external_data_sources?: string[];
notification_preferences?: Record<string, boolean>;
}): Promise<ApiResponse<any>> {
return apiClient.put(`${this.baseUrl}/settings`, settings);
}
// Utility methods
getConfidenceLevels(): { value: number; label: string }[] {
return [
{ value: 0.80, label: '80%' },
{ value: 0.90, label: '90%' },
{ value: 0.95, label: '95%' },
{ value: 0.99, label: '99%' },
];
}
getForecastHorizons(): { value: number; label: string }[] {
return [
{ value: 1, label: '1 day' },
{ value: 7, label: '1 week' },
{ value: 14, label: '2 weeks' },
{ value: 30, label: '1 month' },
{ value: 90, label: '3 months' },
];
}
getGranularityOptions(): { value: string; label: string }[] {
return [
{ value: 'daily', label: 'Daily' },
{ value: 'weekly', label: 'Weekly' },
{ value: 'monthly', label: 'Monthly' },
];
}
}
export const forecastingService = new ForecastingService();

View File

@@ -0,0 +1,33 @@
// Export API client and types
export * from './client';
// Export all services
export * from './auth.service';
export * from './tenant.service';
export * from './inventory.service';
export * from './production.service';
export * from './sales.service';
export * from './forecasting.service';
export * from './orders.service';
export * from './procurement.service';
export * from './pos.service';
export * from './data.service';
export * from './training.service';
export * from './notification.service';
// Service instances for easy importing
export { authService } from './auth.service';
export { tenantService } from './tenant.service';
export { inventoryService } from './inventory.service';
export { productionService } from './production.service';
export { salesService } from './sales.service';
export { forecastingService } from './forecasting.service';
export { ordersService } from './orders.service';
export { procurementService } from './procurement.service';
export { posService } from './pos.service';
export { dataService } from './data.service';
export { trainingService } from './training.service';
export { notificationService } from './notification.service';
// API client instance
export { apiClient } from './client';

View File

@@ -0,0 +1,607 @@
import { apiClient, ApiResponse } from './client';
// 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 {
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;
is_perishable?: boolean;
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;
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 {
ingredient_id: string;
batch_number?: string;
lot_number?: string;
supplier_batch_ref?: string;
current_quantity: number;
received_date?: string;
expiration_date?: string;
best_before_date?: string;
unit_cost?: number;
storage_location?: string;
warehouse_zone?: string;
shelf_position?: string;
quality_status?: string;
}
export interface StockUpdate {
batch_number?: string;
lot_number?: string;
supplier_batch_ref?: string;
current_quantity?: number;
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 {
ingredient_id: string;
stock_id?: string;
movement_type: StockMovementType;
quantity: number;
unit_cost?: number;
reference_number?: string;
supplier_id?: string;
notes?: string;
reason_code?: string;
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;
}
class InventoryService {
private readonly baseUrl = '/inventory';
// Ingredient management
async getIngredients(params?: {
page?: number;
size?: number;
category?: string;
is_active?: boolean;
is_low_stock?: boolean;
needs_reorder?: boolean;
search?: string;
}): Promise<ApiResponse<PaginatedResponse<IngredientResponse>>> {
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}/ingredients?${queryParams.toString()}`
: `${this.baseUrl}/ingredients`;
return apiClient.get(url);
}
async getIngredient(ingredientId: string): Promise<ApiResponse<IngredientResponse>> {
return apiClient.get(`${this.baseUrl}/ingredients/${ingredientId}`);
}
async createIngredient(ingredientData: IngredientCreate): Promise<ApiResponse<IngredientResponse>> {
return apiClient.post(`${this.baseUrl}/ingredients`, ingredientData);
}
async updateIngredient(ingredientId: string, ingredientData: IngredientUpdate): Promise<ApiResponse<IngredientResponse>> {
return apiClient.put(`${this.baseUrl}/ingredients/${ingredientId}`, ingredientData);
}
async deleteIngredient(ingredientId: string): Promise<ApiResponse<{ message: string }>> {
return apiClient.delete(`${this.baseUrl}/ingredients/${ingredientId}`);
}
// Stock management
async getStock(params?: {
page?: number;
size?: number;
ingredient_id?: string;
is_available?: boolean;
is_expired?: boolean;
expiring_within_days?: number;
storage_location?: string;
quality_status?: string;
}): Promise<ApiResponse<PaginatedResponse<StockResponse>>> {
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}/stock?${queryParams.toString()}`
: `${this.baseUrl}/stock`;
return apiClient.get(url);
}
async getStockById(stockId: string): Promise<ApiResponse<StockResponse>> {
return apiClient.get(`${this.baseUrl}/stock/${stockId}`);
}
async getIngredientStock(ingredientId: string): Promise<ApiResponse<StockResponse[]>> {
return apiClient.get(`${this.baseUrl}/ingredients/${ingredientId}/stock`);
}
async createStock(stockData: StockCreate): Promise<ApiResponse<StockResponse>> {
return apiClient.post(`${this.baseUrl}/stock`, stockData);
}
async updateStock(stockId: string, stockData: StockUpdate): Promise<ApiResponse<StockResponse>> {
return apiClient.put(`${this.baseUrl}/stock/${stockId}`, stockData);
}
async deleteStock(stockId: string): Promise<ApiResponse<{ message: string }>> {
return apiClient.delete(`${this.baseUrl}/stock/${stockId}`);
}
// Stock movements
async getStockMovements(params?: {
page?: number;
size?: number;
ingredient_id?: string;
movement_type?: StockMovementType;
start_date?: string;
end_date?: string;
}): Promise<ApiResponse<PaginatedResponse<StockMovementResponse>>> {
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}/movements?${queryParams.toString()}`
: `${this.baseUrl}/movements`;
return apiClient.get(url);
}
async createStockMovement(movementData: StockMovementCreate): Promise<ApiResponse<StockMovementResponse>> {
return apiClient.post(`${this.baseUrl}/movements`, movementData);
}
async getIngredientMovements(ingredientId: string, params?: {
page?: number;
size?: number;
movement_type?: StockMovementType;
start_date?: string;
end_date?: string;
}): Promise<ApiResponse<PaginatedResponse<StockMovementResponse>>> {
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}/ingredients/${ingredientId}/movements?${queryParams.toString()}`
: `${this.baseUrl}/ingredients/${ingredientId}/movements`;
return apiClient.get(url);
}
// Alerts and notifications
async getStockAlerts(params?: {
page?: number;
size?: number;
alert_type?: string;
severity?: string;
is_active?: boolean;
is_acknowledged?: boolean;
is_resolved?: boolean;
}): Promise<ApiResponse<PaginatedResponse<StockAlertResponse>>> {
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}/alerts?${queryParams.toString()}`
: `${this.baseUrl}/alerts`;
return apiClient.get(url);
}
async acknowledgeAlert(alertId: string): Promise<ApiResponse<StockAlertResponse>> {
return apiClient.post(`${this.baseUrl}/alerts/${alertId}/acknowledge`);
}
async resolveAlert(alertId: string, resolutionNotes?: string): Promise<ApiResponse<StockAlertResponse>> {
return apiClient.post(`${this.baseUrl}/alerts/${alertId}/resolve`, {
resolution_notes: resolutionNotes
});
}
// Dashboard and summaries
async getInventorySummary(): Promise<ApiResponse<InventorySummary>> {
return apiClient.get(`${this.baseUrl}/summary`);
}
async getStockLevels(): Promise<ApiResponse<StockLevelSummary[]>> {
return apiClient.get(`${this.baseUrl}/stock-levels`);
}
async getLowStockItems(): Promise<ApiResponse<IngredientResponse[]>> {
return apiClient.get(`${this.baseUrl}/low-stock`);
}
async getExpiringItems(days: number = 7): Promise<ApiResponse<StockResponse[]>> {
return apiClient.get(`${this.baseUrl}/expiring?days=${days}`);
}
async getExpiredItems(): Promise<ApiResponse<StockResponse[]>> {
return apiClient.get(`${this.baseUrl}/expired`);
}
// Classification and categorization
async classifyProduct(name: string, description?: string): Promise<ApiResponse<{
category: string;
subcategory: string;
confidence: number;
suggested_unit: UnitOfMeasure;
is_perishable: boolean;
storage_requirements: {
requires_refrigeration: boolean;
requires_freezing: boolean;
estimated_shelf_life_days: number;
};
}>> {
return apiClient.post(`${this.baseUrl}/classify`, {
name,
description
});
}
// Food safety and compliance
async getFoodSafetyAlerts(): Promise<ApiResponse<any[]>> {
return apiClient.get(`${this.baseUrl}/food-safety/alerts`);
}
async getTemperatureLog(locationId: string, startDate: string, endDate: string): Promise<ApiResponse<any[]>> {
return apiClient.get(`${this.baseUrl}/food-safety/temperature-log`, {
location_id: locationId,
start_date: startDate,
end_date: endDate
});
}
// Batch operations
async bulkUpdateStock(updates: Array<{ stock_id: string; quantity: number; notes?: string }>): Promise<ApiResponse<{ updated: number; errors: any[] }>> {
return apiClient.post(`${this.baseUrl}/stock/bulk-update`, { updates });
}
async bulkCreateIngredients(ingredients: IngredientCreate[]): Promise<ApiResponse<{ created: number; errors: any[] }>> {
return apiClient.post(`${this.baseUrl}/ingredients/bulk-create`, { ingredients });
}
// Import/Export
async importInventory(file: File, progressCallback?: (progress: number) => void): Promise<ApiResponse<{
imported: number;
errors: any[];
warnings: any[];
}>> {
return apiClient.uploadFile(`${this.baseUrl}/import`, file, progressCallback);
}
async exportInventory(format: 'csv' | 'xlsx' = 'csv'): Promise<ApiResponse<{ download_url: string }>> {
return apiClient.get(`${this.baseUrl}/export?format=${format}`);
}
// Utility methods
getUnitOfMeasureOptions(): { value: UnitOfMeasure; label: string }[] {
return [
{ value: UnitOfMeasure.KILOGRAM, label: 'Kilogram (kg)' },
{ value: UnitOfMeasure.GRAM, label: 'Gram (g)' },
{ value: UnitOfMeasure.LITER, label: 'Liter (l)' },
{ value: UnitOfMeasure.MILLILITER, label: 'Milliliter (ml)' },
{ value: UnitOfMeasure.PIECE, label: 'Piece' },
{ value: UnitOfMeasure.PACKAGE, label: 'Package' },
{ value: UnitOfMeasure.BAG, label: 'Bag' },
{ value: UnitOfMeasure.BOX, label: 'Box' },
{ value: UnitOfMeasure.DOZEN, label: 'Dozen' },
];
}
getProductTypeOptions(): { value: ProductType; label: string }[] {
return [
{ value: ProductType.INGREDIENT, label: 'Ingredient' },
{ value: ProductType.FINISHED_PRODUCT, label: 'Finished Product' },
];
}
getMovementTypeOptions(): { value: StockMovementType; label: string }[] {
return [
{ value: StockMovementType.PURCHASE, label: 'Purchase' },
{ value: StockMovementType.SALE, label: 'Sale' },
{ value: StockMovementType.USAGE, label: 'Usage' },
{ value: StockMovementType.WASTE, label: 'Waste' },
{ value: StockMovementType.ADJUSTMENT, label: 'Adjustment' },
{ value: StockMovementType.TRANSFER, label: 'Transfer' },
{ value: StockMovementType.RETURN, label: 'Return' },
];
}
getQualityStatusOptions(): { value: string; label: string; color: string }[] {
return [
{ value: 'good', label: 'Good', color: 'green' },
{ value: 'fair', label: 'Fair', color: 'yellow' },
{ value: 'poor', label: 'Poor', color: 'orange' },
{ value: 'damaged', label: 'Damaged', color: 'red' },
{ value: 'expired', label: 'Expired', color: 'red' },
{ value: 'quarantine', label: 'Quarantine', color: 'purple' },
];
}
}
export const inventoryService = new InventoryService();

View File

@@ -0,0 +1,406 @@
import { apiClient, ApiResponse } from './client';
// Notification types
export interface Notification {
id: string;
tenant_id: string;
type: 'info' | 'warning' | 'error' | 'success';
category: 'system' | 'inventory' | 'production' | 'sales' | 'forecasting' | 'orders' | 'pos';
title: string;
message: string;
data?: Record<string, any>;
priority: 'low' | 'normal' | 'high' | 'urgent';
is_read: boolean;
is_dismissed: boolean;
read_at?: string;
dismissed_at?: string;
expires_at?: string;
created_at: string;
user_id?: string;
}
export interface NotificationTemplate {
id: string;
tenant_id: string;
name: string;
description?: string;
category: string;
type: string;
subject_template: string;
message_template: string;
channels: ('email' | 'sms' | 'push' | 'in_app' | 'whatsapp')[];
variables: Array<{
name: string;
type: 'string' | 'number' | 'date' | 'boolean';
required: boolean;
description?: string;
}>;
conditions?: Array<{
field: string;
operator: 'eq' | 'ne' | 'gt' | 'lt' | 'gte' | 'lte' | 'contains';
value: any;
}>;
is_active: boolean;
created_at: string;
updated_at: string;
}
export interface NotificationPreferences {
id: string;
user_id: string;
tenant_id: string;
email_notifications: boolean;
sms_notifications: boolean;
push_notifications: boolean;
whatsapp_notifications: boolean;
quiet_hours: {
enabled: boolean;
start_time: string;
end_time: string;
timezone: string;
};
categories: Record<string, {
enabled: boolean;
channels: string[];
min_priority: 'low' | 'normal' | 'high' | 'urgent';
}>;
updated_at: string;
}
export interface AlertRule {
id: string;
tenant_id: string;
name: string;
description?: string;
category: string;
rule_type: 'threshold' | 'trend' | 'anomaly' | 'schedule';
conditions: Array<{
metric: string;
operator: string;
value: any;
time_window?: string;
}>;
actions: Array<{
type: 'notification' | 'webhook' | 'email';
config: Record<string, any>;
}>;
is_active: boolean;
last_triggered?: string;
trigger_count: number;
created_at: string;
updated_at: string;
}
class NotificationService {
private readonly baseUrl = '/notifications';
// Notification management
async getNotifications(params?: {
page?: number;
size?: number;
category?: string;
type?: string;
priority?: string;
is_read?: boolean;
is_dismissed?: boolean;
}): Promise<ApiResponse<{ items: Notification[]; total: number; page: number; size: number; pages: number; unread_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}?${queryParams.toString()}`
: this.baseUrl;
return apiClient.get(url);
}
async getNotification(notificationId: string): Promise<ApiResponse<Notification>> {
return apiClient.get(`${this.baseUrl}/${notificationId}`);
}
async markAsRead(notificationId: string): Promise<ApiResponse<Notification>> {
return apiClient.post(`${this.baseUrl}/${notificationId}/read`);
}
async markAsUnread(notificationId: string): Promise<ApiResponse<Notification>> {
return apiClient.post(`${this.baseUrl}/${notificationId}/unread`);
}
async dismiss(notificationId: string): Promise<ApiResponse<Notification>> {
return apiClient.post(`${this.baseUrl}/${notificationId}/dismiss`);
}
async markAllAsRead(category?: string): Promise<ApiResponse<{ updated_count: number }>> {
const url = category
? `${this.baseUrl}/mark-all-read?category=${encodeURIComponent(category)}`
: `${this.baseUrl}/mark-all-read`;
return apiClient.post(url);
}
async dismissAll(category?: string): Promise<ApiResponse<{ updated_count: number }>> {
const url = category
? `${this.baseUrl}/dismiss-all?category=${encodeURIComponent(category)}`
: `${this.baseUrl}/dismiss-all`;
return apiClient.post(url);
}
async deleteNotification(notificationId: string): Promise<ApiResponse<{ message: string }>> {
return apiClient.delete(`${this.baseUrl}/${notificationId}`);
}
// Notification creation and sending
async createNotification(notificationData: {
type: Notification['type'];
category: string;
title: string;
message: string;
priority?: Notification['priority'];
data?: Record<string, any>;
user_id?: string;
expires_at?: string;
channels?: string[];
}): Promise<ApiResponse<Notification>> {
return apiClient.post(this.baseUrl, notificationData);
}
async sendBulkNotification(notificationData: {
type: Notification['type'];
category: string;
title: string;
message: string;
priority?: Notification['priority'];
data?: Record<string, any>;
user_ids?: string[];
user_roles?: string[];
channels?: string[];
}): Promise<ApiResponse<{ sent_count: number; failed_count: number }>> {
return apiClient.post(`${this.baseUrl}/bulk-send`, notificationData);
}
// Template management
async getTemplates(category?: string): Promise<ApiResponse<NotificationTemplate[]>> {
const url = category
? `${this.baseUrl}/templates?category=${encodeURIComponent(category)}`
: `${this.baseUrl}/templates`;
return apiClient.get(url);
}
async getTemplate(templateId: string): Promise<ApiResponse<NotificationTemplate>> {
return apiClient.get(`${this.baseUrl}/templates/${templateId}`);
}
async createTemplate(templateData: {
name: string;
description?: string;
category: string;
type: string;
subject_template: string;
message_template: string;
channels: string[];
variables: NotificationTemplate['variables'];
conditions?: NotificationTemplate['conditions'];
}): Promise<ApiResponse<NotificationTemplate>> {
return apiClient.post(`${this.baseUrl}/templates`, templateData);
}
async updateTemplate(templateId: string, templateData: Partial<NotificationTemplate>): Promise<ApiResponse<NotificationTemplate>> {
return apiClient.put(`${this.baseUrl}/templates/${templateId}`, templateData);
}
async deleteTemplate(templateId: string): Promise<ApiResponse<{ message: string }>> {
return apiClient.delete(`${this.baseUrl}/templates/${templateId}`);
}
async previewTemplate(templateId: string, variables: Record<string, any>): Promise<ApiResponse<{
subject: string;
message: string;
rendered_html?: string;
}>> {
return apiClient.post(`${this.baseUrl}/templates/${templateId}/preview`, { variables });
}
// User preferences
async getPreferences(userId?: string): Promise<ApiResponse<NotificationPreferences>> {
const url = userId
? `${this.baseUrl}/preferences/${userId}`
: `${this.baseUrl}/preferences`;
return apiClient.get(url);
}
async updatePreferences(preferencesData: Partial<NotificationPreferences>, userId?: string): Promise<ApiResponse<NotificationPreferences>> {
const url = userId
? `${this.baseUrl}/preferences/${userId}`
: `${this.baseUrl}/preferences`;
return apiClient.put(url, preferencesData);
}
// Alert rules
async getAlertRules(): Promise<ApiResponse<AlertRule[]>> {
return apiClient.get(`${this.baseUrl}/alert-rules`);
}
async getAlertRule(ruleId: string): Promise<ApiResponse<AlertRule>> {
return apiClient.get(`${this.baseUrl}/alert-rules/${ruleId}`);
}
async createAlertRule(ruleData: {
name: string;
description?: string;
category: string;
rule_type: AlertRule['rule_type'];
conditions: AlertRule['conditions'];
actions: AlertRule['actions'];
}): Promise<ApiResponse<AlertRule>> {
return apiClient.post(`${this.baseUrl}/alert-rules`, ruleData);
}
async updateAlertRule(ruleId: string, ruleData: Partial<AlertRule>): Promise<ApiResponse<AlertRule>> {
return apiClient.put(`${this.baseUrl}/alert-rules/${ruleId}`, ruleData);
}
async deleteAlertRule(ruleId: string): Promise<ApiResponse<{ message: string }>> {
return apiClient.delete(`${this.baseUrl}/alert-rules/${ruleId}`);
}
async testAlertRule(ruleId: string): Promise<ApiResponse<{
would_trigger: boolean;
current_values: Record<string, any>;
evaluation_result: string;
}>> {
return apiClient.post(`${this.baseUrl}/alert-rules/${ruleId}/test`);
}
// Real-time notifications (SSE)
async getSSEEndpoint(): Promise<ApiResponse<{ endpoint_url: string; auth_token: string }>> {
return apiClient.get(`${this.baseUrl}/sse/endpoint`);
}
connectSSE(onNotification: (notification: Notification) => void, onError?: (error: Error) => void): EventSource {
const token = localStorage.getItem('access_token');
const tenantId = localStorage.getItem('tenant_id');
const url = new URL(`${apiClient.getBaseURL()}/notifications/sse/stream`);
if (token) url.searchParams.append('token', token);
if (tenantId) url.searchParams.append('tenant_id', tenantId);
const eventSource = new EventSource(url.toString());
eventSource.onmessage = (event) => {
try {
const notification = JSON.parse(event.data);
onNotification(notification);
} catch (error) {
console.error('Failed to parse SSE notification:', error);
onError?.(error as Error);
}
};
eventSource.onerror = (error) => {
console.error('SSE connection error:', error);
onError?.(new Error('SSE connection failed'));
};
return eventSource;
}
// Analytics and reporting
async getNotificationStats(params?: {
start_date?: string;
end_date?: string;
category?: string;
}): Promise<ApiResponse<{
total_sent: number;
total_read: number;
total_dismissed: number;
read_rate: number;
dismiss_rate: number;
by_category: Array<{
category: string;
sent: number;
read: number;
dismissed: number;
}>;
by_channel: Array<{
channel: string;
sent: number;
delivered: number;
failed: number;
}>;
trends: Array<{
date: string;
sent: number;
read: 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}/stats?${queryParams.toString()}`
: `${this.baseUrl}/stats`;
return apiClient.get(url);
}
// Utility methods
getNotificationTypes(): { value: string; label: string; icon: string; color: string }[] {
return [
{ value: 'info', label: 'Information', icon: 'info', color: 'blue' },
{ value: 'success', label: 'Success', icon: 'check', color: 'green' },
{ value: 'warning', label: 'Warning', icon: 'warning', color: 'yellow' },
{ value: 'error', label: 'Error', icon: 'error', color: 'red' },
];
}
getNotificationCategories(): { value: string; label: string; description: string }[] {
return [
{ value: 'system', label: 'System', description: 'System-wide notifications and updates' },
{ value: 'inventory', label: 'Inventory', description: 'Stock levels, expiration alerts, reorder notifications' },
{ value: 'production', label: 'Production', description: 'Production schedules, quality checks, batch completion' },
{ value: 'sales', label: 'Sales', description: 'Sales targets, performance alerts, revenue notifications' },
{ value: 'forecasting', label: 'Forecasting', description: 'Demand predictions, model updates, accuracy alerts' },
{ value: 'orders', label: 'Orders', description: 'New orders, order updates, delivery notifications' },
{ value: 'pos', label: 'POS', description: 'Point of sale integration, sync status, transaction alerts' },
];
}
getPriorityLevels(): { value: string; label: string; color: string; urgency: number }[] {
return [
{ value: 'low', label: 'Low', color: 'gray', urgency: 1 },
{ value: 'normal', label: 'Normal', color: 'blue', urgency: 2 },
{ value: 'high', label: 'High', color: 'orange', urgency: 3 },
{ value: 'urgent', label: 'Urgent', color: 'red', urgency: 4 },
];
}
getNotificationChannels(): { value: string; label: string; description: string; requires_setup: boolean }[] {
return [
{ value: 'in_app', label: 'In-App', description: 'Notifications within the application', requires_setup: false },
{ value: 'email', label: 'Email', description: 'Email notifications', requires_setup: true },
{ value: 'sms', label: 'SMS', description: 'Text message notifications', requires_setup: true },
{ value: 'push', label: 'Push', description: 'Browser/mobile push notifications', requires_setup: true },
{ value: 'whatsapp', label: 'WhatsApp', description: 'WhatsApp notifications', requires_setup: true },
];
}
}
export const notificationService = new NotificationService();

View File

@@ -0,0 +1,312 @@
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;
}>;
}
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 }>> {
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}?${queryParams.toString()}`
: this.baseUrl;
return apiClient.get(url);
}
async getOrder(orderId: string): Promise<ApiResponse<OrderResponse>> {
return apiClient.get(`${this.baseUrl}/${orderId}`);
}
async createOrder(orderData: OrderCreate): Promise<ApiResponse<OrderResponse>> {
return apiClient.post(this.baseUrl, orderData);
}
async updateOrder(orderId: string, orderData: OrderUpdate): Promise<ApiResponse<OrderResponse>> {
return apiClient.put(`${this.baseUrl}/${orderId}`, orderData);
}
async cancelOrder(orderId: string, reason?: string): Promise<ApiResponse<OrderResponse>> {
return apiClient.post(`${this.baseUrl}/${orderId}/cancel`, { reason });
}
async confirmOrder(orderId: string): Promise<ApiResponse<OrderResponse>> {
return apiClient.post(`${this.baseUrl}/${orderId}/confirm`);
}
async startPreparation(orderId: string): Promise<ApiResponse<OrderResponse>> {
return apiClient.post(`${this.baseUrl}/${orderId}/start-preparation`);
}
async markReady(orderId: string): Promise<ApiResponse<OrderResponse>> {
return apiClient.post(`${this.baseUrl}/${orderId}/mark-ready`);
}
async markDelivered(orderId: string): Promise<ApiResponse<OrderResponse>> {
return apiClient.post(`${this.baseUrl}/${orderId}/mark-delivered`);
}
// Customer management
async getCustomers(params?: {
page?: number;
size?: number;
search?: string;
}): Promise<ApiResponse<{ items: Customer[]; total: number; page: number; size: number; pages: 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}/customers?${queryParams.toString()}`
: `${this.baseUrl}/customers`;
return apiClient.get(url);
}
async getCustomer(customerId: string): Promise<ApiResponse<Customer>> {
return apiClient.get(`${this.baseUrl}/customers/${customerId}`);
}
async getCustomerOrders(customerId: string): Promise<ApiResponse<OrderResponse[]>> {
return apiClient.get(`${this.baseUrl}/customers/${customerId}/orders`);
}
// Analytics and reporting
async getOrderAnalytics(params?: {
start_date?: string;
end_date?: string;
order_type?: OrderType;
}): Promise<ApiResponse<OrderAnalytics>> {
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 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;
}>>> {
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}/trends?${queryParams.toString()}`
: `${this.baseUrl}/trends`;
return apiClient.get(url);
}
// Queue management
async getOrderQueue(filterBy?: OrderStatus[]): Promise<ApiResponse<OrderResponse[]>> {
const queryParams = new URLSearchParams();
if (filterBy) {
filterBy.forEach(status => queryParams.append('status', status));
}
const url = queryParams.toString()
? `${this.baseUrl}/queue?${queryParams.toString()}`
: `${this.baseUrl}/queue`;
return apiClient.get(url);
}
async updateQueuePosition(orderId: string, newPosition: number): Promise<ApiResponse<OrderResponse>> {
return apiClient.post(`${this.baseUrl}/${orderId}/queue-position`, { position: newPosition });
}
// Utility methods
getOrderStatusOptions(): { value: OrderStatus; label: string; color: string }[] {
return [
{ value: OrderStatus.PENDING, label: 'Pending', color: 'gray' },
{ value: OrderStatus.CONFIRMED, label: 'Confirmed', color: 'blue' },
{ value: OrderStatus.IN_PREPARATION, label: 'In Preparation', color: 'yellow' },
{ value: OrderStatus.READY, label: 'Ready', color: 'green' },
{ value: OrderStatus.DELIVERED, label: 'Delivered', color: 'green' },
{ value: OrderStatus.CANCELLED, label: 'Cancelled', color: 'red' },
];
}
getOrderTypeOptions(): { value: OrderType; label: string }[] {
return [
{ value: OrderType.DINE_IN, label: 'Dine In' },
{ value: OrderType.TAKEAWAY, label: 'Takeaway' },
{ value: OrderType.DELIVERY, label: 'Delivery' },
{ value: OrderType.CATERING, label: 'Catering' },
];
}
getPaymentMethods(): { value: string; label: string }[] {
return [
{ value: 'cash', label: 'Cash' },
{ value: 'card', label: 'Card' },
{ value: 'digital_wallet', label: 'Digital Wallet' },
{ value: 'bank_transfer', label: 'Bank Transfer' },
];
}
}
export const ordersService = new OrdersService();

View File

@@ -0,0 +1,317 @@
import { apiClient, ApiResponse } from './client';
// Request/Response Types
export interface POSConfiguration {
id: string;
tenant_id: string;
provider: 'square' | 'stripe' | 'toast' | 'clover' | 'custom';
config_name: string;
is_active: boolean;
credentials: {
api_key?: string;
secret_key?: string;
application_id?: string;
location_id?: string;
webhook_signature?: string;
};
sync_settings: {
auto_sync_enabled: boolean;
sync_interval_minutes: number;
sync_sales: boolean;
sync_inventory: boolean;
sync_customers: boolean;
};
created_at: string;
updated_at: string;
last_sync_at?: string;
}
export interface POSTransaction {
id: string;
tenant_id: string;
pos_transaction_id: string;
provider: string;
location_id: string;
transaction_type: 'sale' | 'refund' | 'void';
amount: number;
tax_amount: number;
tip_amount?: number;
discount_amount?: number;
payment_method: string;
customer_id?: string;
items: Array<{
product_id: string;
product_name: string;
quantity: number;
unit_price: number;
total_price: number;
}>;
transaction_date: string;
created_at: string;
updated_at: string;
}
export interface SyncStatus {
id: string;
tenant_id: string;
sync_type: 'manual' | 'automatic';
status: 'pending' | 'in_progress' | 'completed' | 'failed';
progress: number;
total_records: number;
processed_records: number;
failed_records: number;
error_message?: string;
started_at: string;
completed_at?: string;
}
class POSService {
private readonly baseUrl = '/pos';
// Configuration management
async getPOSConfigs(): Promise<ApiResponse<POSConfiguration[]>> {
return apiClient.get(`${this.baseUrl}/config`);
}
async getPOSConfig(configId: string): Promise<ApiResponse<POSConfiguration>> {
return apiClient.get(`${this.baseUrl}/config/${configId}`);
}
async createPOSConfig(configData: {
provider: string;
config_name: string;
credentials: Record<string, string>;
sync_settings?: Partial<POSConfiguration['sync_settings']>;
}): Promise<ApiResponse<POSConfiguration>> {
return apiClient.post(`${this.baseUrl}/config`, configData);
}
async updatePOSConfig(configId: string, configData: Partial<POSConfiguration>): Promise<ApiResponse<POSConfiguration>> {
return apiClient.put(`${this.baseUrl}/config/${configId}`, configData);
}
async deletePOSConfig(configId: string): Promise<ApiResponse<{ message: string }>> {
return apiClient.delete(`${this.baseUrl}/config/${configId}`);
}
async testPOSConnection(configId: string): Promise<ApiResponse<{
success: boolean;
message: string;
connection_details?: Record<string, any>;
}>> {
return apiClient.post(`${this.baseUrl}/config/${configId}/test`);
}
// Synchronization
async startManualSync(configId: string, syncOptions?: {
sync_sales?: boolean;
sync_inventory?: boolean;
sync_customers?: boolean;
date_from?: string;
date_to?: string;
}): Promise<ApiResponse<{ sync_id: string; message: string }>> {
return apiClient.post(`${this.baseUrl}/sync/${configId}/start`, syncOptions);
}
async getSyncStatus(syncId: string): Promise<ApiResponse<SyncStatus>> {
return apiClient.get(`${this.baseUrl}/sync/status/${syncId}`);
}
async getSyncHistory(params?: {
page?: number;
size?: number;
config_id?: string;
status?: string;
}): Promise<ApiResponse<{ items: SyncStatus[]; total: number; page: number; size: number; pages: 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}/sync/history?${queryParams.toString()}`
: `${this.baseUrl}/sync/history`;
return apiClient.get(url);
}
async cancelSync(syncId: string): Promise<ApiResponse<{ message: string }>> {
return apiClient.post(`${this.baseUrl}/sync/${syncId}/cancel`);
}
// Transaction management
async getPOSTransactions(params?: {
page?: number;
size?: number;
config_id?: string;
start_date?: string;
end_date?: string;
transaction_type?: string;
}): Promise<ApiResponse<{ items: POSTransaction[]; total: number; page: number; size: number; pages: 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}/transactions?${queryParams.toString()}`
: `${this.baseUrl}/transactions`;
return apiClient.get(url);
}
async getPOSTransaction(transactionId: string): Promise<ApiResponse<POSTransaction>> {
return apiClient.get(`${this.baseUrl}/transactions/${transactionId}`);
}
// Webhook management
async getWebhooks(configId: string): Promise<ApiResponse<Array<{
id: string;
event_type: string;
endpoint_url: string;
is_active: boolean;
created_at: string;
}>>> {
return apiClient.get(`${this.baseUrl}/webhooks/${configId}`);
}
async createWebhook(configId: string, webhookData: {
event_types: string[];
endpoint_url?: string;
}): Promise<ApiResponse<{ webhook_id: string; endpoint_url: string }>> {
return apiClient.post(`${this.baseUrl}/webhooks/${configId}`, webhookData);
}
async deleteWebhook(configId: string, webhookId: string): Promise<ApiResponse<{ message: string }>> {
return apiClient.delete(`${this.baseUrl}/webhooks/${configId}/${webhookId}`);
}
// Analytics and reporting
async getPOSAnalytics(params?: {
config_id?: string;
start_date?: string;
end_date?: string;
}): Promise<ApiResponse<{
total_transactions: number;
total_revenue: number;
average_transaction_value: number;
refund_rate: number;
top_payment_methods: Array<{
method: string;
count: number;
total_amount: number;
}>;
hourly_sales: Array<{
hour: number;
transaction_count: number;
total_amount: 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);
}
// Product mapping
async getProductMappings(configId: string): Promise<ApiResponse<Array<{
pos_product_id: string;
pos_product_name: string;
internal_product_id?: string;
internal_product_name?: string;
is_mapped: boolean;
last_sync_at?: string;
}>>> {
return apiClient.get(`${this.baseUrl}/products/${configId}/mappings`);
}
async updateProductMapping(configId: string, mappingData: {
pos_product_id: string;
internal_product_id: string;
}): Promise<ApiResponse<{ message: string }>> {
return apiClient.put(`${this.baseUrl}/products/${configId}/mappings`, mappingData);
}
async bulkUpdateProductMappings(configId: string, mappings: Array<{
pos_product_id: string;
internal_product_id: string;
}>): Promise<ApiResponse<{ updated: number; errors: any[] }>> {
return apiClient.post(`${this.baseUrl}/products/${configId}/mappings/bulk`, { mappings });
}
// Utility methods
getSupportedProviders(): { value: string; label: string; features: string[] }[] {
return [
{
value: 'square',
label: 'Square',
features: ['sales_sync', 'inventory_sync', 'customer_sync', 'webhooks']
},
{
value: 'stripe',
label: 'Stripe',
features: ['sales_sync', 'customer_sync', 'webhooks']
},
{
value: 'toast',
label: 'Toast',
features: ['sales_sync', 'inventory_sync', 'webhooks']
},
{
value: 'clover',
label: 'Clover',
features: ['sales_sync', 'inventory_sync', 'customer_sync']
},
{
value: 'custom',
label: 'Custom Integration',
features: ['api_integration']
}
];
}
getSyncIntervalOptions(): { value: number; label: string }[] {
return [
{ value: 5, label: 'Every 5 minutes' },
{ value: 15, label: 'Every 15 minutes' },
{ value: 30, label: 'Every 30 minutes' },
{ value: 60, label: 'Every hour' },
{ value: 240, label: 'Every 4 hours' },
{ value: 1440, label: 'Daily' },
];
}
getWebhookEventTypes(): { value: string; label: string; description: string }[] {
return [
{ value: 'payment.created', label: 'Payment Created', description: 'Triggered when a new payment is processed' },
{ value: 'payment.updated', label: 'Payment Updated', description: 'Triggered when a payment is updated' },
{ value: 'order.created', label: 'Order Created', description: 'Triggered when a new order is created' },
{ value: 'order.updated', label: 'Order Updated', description: 'Triggered when an order is updated' },
{ value: 'inventory.updated', label: 'Inventory Updated', description: 'Triggered when inventory levels change' },
{ value: 'customer.created', label: 'Customer Created', description: 'Triggered when a new customer is created' },
];
}
}
export const posService = new POSService();

View File

@@ -0,0 +1,408 @@
import { apiClient, ApiResponse } from './client';
// 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;
size?: number;
status?: PurchaseOrderStatus;
supplier_id?: string;
start_date?: string;
end_date?: string;
}): Promise<ApiResponse<{ items: PurchaseOrderResponse[]; total: number; page: number; size: number; pages: 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}/purchase-orders?${queryParams.toString()}`
: `${this.baseUrl}/purchase-orders`;
return apiClient.get(url);
}
async getPurchaseOrder(orderId: string): Promise<ApiResponse<PurchaseOrderResponse>> {
return apiClient.get(`${this.baseUrl}/purchase-orders/${orderId}`);
}
async createPurchaseOrder(orderData: PurchaseOrderCreate): Promise<ApiResponse<PurchaseOrderResponse>> {
return apiClient.post(`${this.baseUrl}/purchase-orders`, orderData);
}
async updatePurchaseOrder(orderId: string, orderData: PurchaseOrderUpdate): Promise<ApiResponse<PurchaseOrderResponse>> {
return apiClient.put(`${this.baseUrl}/purchase-orders/${orderId}`, orderData);
}
async approvePurchaseOrder(orderId: string): Promise<ApiResponse<PurchaseOrderResponse>> {
return apiClient.post(`${this.baseUrl}/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 });
}
async cancelPurchaseOrder(orderId: string, reason?: string): Promise<ApiResponse<PurchaseOrderResponse>> {
return apiClient.post(`${this.baseUrl}/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 }>> {
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}/suppliers?${queryParams.toString()}`
: `${this.baseUrl}/suppliers`;
return apiClient.get(url);
}
async getSupplier(supplierId: string): Promise<ApiResponse<Supplier>> {
return apiClient.get(`${this.baseUrl}/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 updateSupplier(supplierId: string, supplierData: Partial<Supplier>): Promise<ApiResponse<Supplier>> {
return apiClient.put(`${this.baseUrl}/suppliers/${supplierId}`, supplierData);
}
async deleteSupplier(supplierId: string): Promise<ApiResponse<{ message: string }>> {
return apiClient.delete(`${this.baseUrl}/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`);
}
// Delivery management
async getDeliveries(params?: {
page?: number;
size?: number;
status?: DeliveryStatus;
supplier_id?: string;
purchase_order_id?: string;
start_date?: string;
end_date?: string;
}): Promise<ApiResponse<{ items: DeliveryResponse[]; total: number; page: number; size: number; pages: 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}/deliveries?${queryParams.toString()}`
: `${this.baseUrl}/deliveries`;
return apiClient.get(url);
}
async getDelivery(deliveryId: string): Promise<ApiResponse<DeliveryResponse>> {
return apiClient.get(`${this.baseUrl}/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 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);
}
// 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 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);
}
// 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 }[] {
return [
{ value: PurchaseOrderStatus.DRAFT, label: 'Draft', color: 'gray' },
{ value: PurchaseOrderStatus.PENDING, label: 'Pending', color: 'yellow' },
{ value: PurchaseOrderStatus.APPROVED, label: 'Approved', color: 'blue' },
{ value: PurchaseOrderStatus.SENT, label: 'Sent', color: 'purple' },
{ value: PurchaseOrderStatus.PARTIALLY_RECEIVED, label: 'Partially Received', color: 'orange' },
{ value: PurchaseOrderStatus.RECEIVED, label: 'Received', color: 'green' },
{ value: PurchaseOrderStatus.CANCELLED, label: 'Cancelled', color: 'red' },
];
}
getDeliveryStatusOptions(): { value: DeliveryStatus; label: string; color: string }[] {
return [
{ value: DeliveryStatus.SCHEDULED, label: 'Scheduled', color: 'blue' },
{ value: DeliveryStatus.IN_TRANSIT, label: 'In Transit', color: 'yellow' },
{ value: DeliveryStatus.DELIVERED, label: 'Delivered', color: 'green' },
{ value: DeliveryStatus.FAILED, label: 'Failed', color: 'red' },
{ value: DeliveryStatus.RETURNED, label: 'Returned', color: 'orange' },
];
}
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

@@ -0,0 +1,473 @@
import { apiClient, ApiResponse } from './client';
// 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',
}
// Request/Response Types
export interface ProductionBatchCreate {
recipe_id: string;
planned_quantity: number;
planned_start_date: string;
planned_end_date?: string;
priority?: ProductionPriority;
notes?: string;
assigned_staff?: string[];
equipment_required?: string[];
}
export interface ProductionBatchUpdate {
planned_quantity?: number;
actual_quantity?: number;
planned_start_date?: string;
planned_end_date?: string;
actual_start_date?: string;
actual_end_date?: string;
status?: ProductionBatchStatus;
priority?: ProductionPriority;
notes?: string;
assigned_staff?: string[];
equipment_required?: string[];
}
export interface ProductionBatchResponse {
id: string;
tenant_id: string;
recipe_id: string;
batch_number: string;
planned_quantity: number;
actual_quantity?: number;
planned_start_date: string;
planned_end_date?: string;
actual_start_date?: string;
actual_end_date?: string;
status: ProductionBatchStatus;
priority: ProductionPriority;
notes?: string;
assigned_staff: string[];
equipment_required: string[];
cost_per_unit?: number;
total_cost?: number;
yield_percentage?: number;
created_at: string;
updated_at: string;
created_by: string;
recipe?: any; // Recipe details
}
export interface ProductionScheduleEntry {
id: string;
tenant_id: string;
batch_id: string;
scheduled_date: string;
scheduled_start_time: string;
scheduled_end_time: string;
estimated_duration_minutes: number;
equipment_reservations: string[];
staff_assignments: string[];
dependencies: string[];
is_locked: boolean;
created_at: string;
updated_at: string;
batch?: ProductionBatchResponse;
}
export interface QualityCheckCreate {
batch_id: string;
check_type: string;
criteria: Record<string, any>;
inspector?: string;
scheduled_date?: string;
}
export interface QualityCheckResponse {
id: string;
tenant_id: string;
batch_id: string;
check_type: string;
status: QualityCheckStatus;
criteria: Record<string, any>;
results: Record<string, any>;
inspector?: string;
scheduled_date?: string;
completed_date?: string;
notes?: string;
corrective_actions?: string[];
created_at: string;
updated_at: string;
batch?: ProductionBatchResponse;
}
export interface ProductionCapacity {
id: string;
tenant_id: string;
resource_type: 'equipment' | 'staff' | 'facility';
resource_id: string;
resource_name: string;
daily_capacity: number;
hourly_capacity?: number;
utilization_rate: number;
maintenance_schedule?: Array<{
start_date: string;
end_date: string;
type: string;
}>;
availability_windows: Array<{
day_of_week: string;
start_time: string;
end_time: string;
}>;
}
export interface ProductionMetrics {
total_batches: number;
completed_batches: number;
in_progress_batches: number;
average_yield: number;
on_time_delivery_rate: number;
quality_pass_rate: number;
equipment_utilization: number;
production_efficiency: number;
waste_percentage: number;
cost_per_unit_average: number;
}
export interface ProductionAlert {
id: string;
tenant_id: string;
alert_type: string;
severity: 'low' | 'medium' | 'high' | 'critical';
title: string;
message: string;
batch_id?: string;
equipment_id?: string;
is_active: boolean;
acknowledged_at?: string;
resolved_at?: string;
created_at: string;
}
class ProductionService {
private readonly baseUrl = '/production';
// Production batch management
async getProductionBatches(params?: {
page?: number;
size?: number;
status?: ProductionBatchStatus;
priority?: ProductionPriority;
start_date?: string;
end_date?: string;
recipe_id?: string;
}): Promise<ApiResponse<{ items: ProductionBatchResponse[]; total: number; page: number; size: number; pages: 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}/batches?${queryParams.toString()}`
: `${this.baseUrl}/batches`;
return apiClient.get(url);
}
async getProductionBatch(batchId: string): Promise<ApiResponse<ProductionBatchResponse>> {
return apiClient.get(`${this.baseUrl}/batches/${batchId}`);
}
async createProductionBatch(batchData: ProductionBatchCreate): Promise<ApiResponse<ProductionBatchResponse>> {
return apiClient.post(`${this.baseUrl}/batches`, batchData);
}
async updateProductionBatch(batchId: string, batchData: ProductionBatchUpdate): Promise<ApiResponse<ProductionBatchResponse>> {
return apiClient.put(`${this.baseUrl}/batches/${batchId}`, batchData);
}
async deleteProductionBatch(batchId: string): Promise<ApiResponse<{ message: string }>> {
return apiClient.delete(`${this.baseUrl}/batches/${batchId}`);
}
async startProductionBatch(batchId: string): Promise<ApiResponse<ProductionBatchResponse>> {
return apiClient.post(`${this.baseUrl}/batches/${batchId}/start`);
}
async completeProductionBatch(batchId: string, completionData: {
actual_quantity: number;
yield_notes?: string;
quality_notes?: string;
}): Promise<ApiResponse<ProductionBatchResponse>> {
return apiClient.post(`${this.baseUrl}/batches/${batchId}/complete`, completionData);
}
// Production scheduling
async getProductionSchedule(params?: {
start_date?: string;
end_date?: string;
equipment_id?: string;
staff_id?: string;
}): Promise<ApiResponse<ProductionScheduleEntry[]>> {
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}/schedule?${queryParams.toString()}`
: `${this.baseUrl}/schedule`;
return apiClient.get(url);
}
async scheduleProductionBatch(scheduleData: {
batch_id: string;
scheduled_date: string;
scheduled_start_time: string;
scheduled_end_time: string;
equipment_reservations?: string[];
staff_assignments?: string[];
}): Promise<ApiResponse<ProductionScheduleEntry>> {
return apiClient.post(`${this.baseUrl}/schedule`, scheduleData);
}
async updateScheduleEntry(entryId: string, updateData: {
scheduled_date?: string;
scheduled_start_time?: string;
scheduled_end_time?: string;
equipment_reservations?: string[];
staff_assignments?: string[];
}): Promise<ApiResponse<ProductionScheduleEntry>> {
return apiClient.put(`${this.baseUrl}/schedule/${entryId}`, updateData);
}
// Quality control
async getQualityChecks(params?: {
page?: number;
size?: number;
batch_id?: string;
status?: QualityCheckStatus;
check_type?: string;
}): Promise<ApiResponse<{ items: QualityCheckResponse[]; total: number; page: number; size: number; pages: 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}/quality-checks?${queryParams.toString()}`
: `${this.baseUrl}/quality-checks`;
return apiClient.get(url);
}
async createQualityCheck(checkData: QualityCheckCreate): Promise<ApiResponse<QualityCheckResponse>> {
return apiClient.post(`${this.baseUrl}/quality-checks`, checkData);
}
async completeQualityCheck(checkId: string, results: {
status: QualityCheckStatus;
results: Record<string, any>;
notes?: string;
corrective_actions?: string[];
}): Promise<ApiResponse<QualityCheckResponse>> {
return apiClient.put(`${this.baseUrl}/quality-checks/${checkId}/complete`, results);
}
// Capacity management
async getProductionCapacity(): Promise<ApiResponse<ProductionCapacity[]>> {
return apiClient.get(`${this.baseUrl}/capacity`);
}
async updateCapacity(capacityId: string, capacityData: Partial<ProductionCapacity>): Promise<ApiResponse<ProductionCapacity>> {
return apiClient.put(`${this.baseUrl}/capacity/${capacityId}`, capacityData);
}
async getCapacityUtilization(params?: {
start_date?: string;
end_date?: string;
resource_type?: string;
}): Promise<ApiResponse<Array<{
resource_id: string;
resource_name: string;
utilization_rate: number;
available_capacity: number;
used_capacity: number;
bottleneck_risk: 'low' | 'medium' | 'high';
}>>> {
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}/capacity/utilization?${queryParams.toString()}`
: `${this.baseUrl}/capacity/utilization`;
return apiClient.get(url);
}
// Production metrics and analytics
async getProductionMetrics(params?: {
start_date?: string;
end_date?: string;
recipe_id?: string;
}): Promise<ApiResponse<ProductionMetrics>> {
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}/metrics?${queryParams.toString()}`
: `${this.baseUrl}/metrics`;
return apiClient.get(url);
}
async getProductionTrends(params?: {
start_date?: string;
end_date?: string;
granularity?: 'daily' | 'weekly' | 'monthly';
}): Promise<ApiResponse<Array<{
date: string;
total_production: number;
efficiency_rate: number;
quality_rate: number;
on_time_rate: 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}/trends?${queryParams.toString()}`
: `${this.baseUrl}/trends`;
return apiClient.get(url);
}
// Alerts and notifications
async getProductionAlerts(params?: {
is_active?: boolean;
severity?: string;
}): Promise<ApiResponse<ProductionAlert[]>> {
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}/alerts?${queryParams.toString()}`
: `${this.baseUrl}/alerts`;
return apiClient.get(url);
}
async acknowledgeAlert(alertId: string): Promise<ApiResponse<ProductionAlert>> {
return apiClient.post(`${this.baseUrl}/alerts/${alertId}/acknowledge`);
}
async resolveAlert(alertId: string, resolutionNotes?: string): Promise<ApiResponse<ProductionAlert>> {
return apiClient.post(`${this.baseUrl}/alerts/${alertId}/resolve`, {
resolution_notes: resolutionNotes
});
}
// Utility methods
getBatchStatusOptions(): { value: ProductionBatchStatus; label: string; color: string }[] {
return [
{ value: ProductionBatchStatus.PLANNED, label: 'Planned', color: 'blue' },
{ value: ProductionBatchStatus.IN_PROGRESS, label: 'In Progress', color: 'yellow' },
{ value: ProductionBatchStatus.COMPLETED, label: 'Completed', color: 'green' },
{ value: ProductionBatchStatus.CANCELLED, label: 'Cancelled', color: 'red' },
{ value: ProductionBatchStatus.ON_HOLD, label: 'On Hold', color: 'orange' },
];
}
getPriorityOptions(): { value: ProductionPriority; label: string; color: string }[] {
return [
{ value: ProductionPriority.LOW, label: 'Low', color: 'gray' },
{ value: ProductionPriority.NORMAL, label: 'Normal', color: 'blue' },
{ value: ProductionPriority.HIGH, label: 'High', color: 'orange' },
{ value: ProductionPriority.URGENT, label: 'Urgent', color: 'red' },
];
}
getQualityCheckStatusOptions(): { value: QualityCheckStatus; label: string; color: string }[] {
return [
{ value: QualityCheckStatus.PENDING, label: 'Pending', color: 'gray' },
{ value: QualityCheckStatus.PASSED, label: 'Passed', color: 'green' },
{ value: QualityCheckStatus.FAILED, label: 'Failed', color: 'red' },
{ value: QualityCheckStatus.REQUIRES_REVIEW, label: 'Requires Review', color: 'orange' },
];
}
getQualityCheckTypes(): { value: string; label: string }[] {
return [
{ value: 'visual_inspection', label: 'Visual Inspection' },
{ value: 'weight_check', label: 'Weight Check' },
{ value: 'temperature_check', label: 'Temperature Check' },
{ value: 'texture_assessment', label: 'Texture Assessment' },
{ value: 'taste_test', label: 'Taste Test' },
{ value: 'packaging_quality', label: 'Packaging Quality' },
{ value: 'food_safety', label: 'Food Safety' },
];
}
}
export const productionService = new ProductionService();

View File

@@ -0,0 +1,443 @@
import { apiClient, ApiResponse } from './client';
// Request/Response Types
export interface SalesData {
id: string;
tenant_id: string;
date: string;
product_id?: string;
product_name: string;
category?: string;
quantity_sold: number;
unit_price: number;
total_revenue: number;
cost_of_goods: number;
gross_profit: number;
discount_applied: number;
tax_amount: number;
weather_condition?: string;
temperature?: number;
day_of_week: string;
is_holiday: boolean;
special_event?: string;
created_at: string;
updated_at: string;
}
export interface SalesCreate {
date: string;
product_id?: string;
product_name: string;
category?: string;
quantity_sold: number;
unit_price: number;
discount_applied?: number;
tax_amount?: number;
weather_condition?: string;
temperature?: number;
special_event?: string;
}
export interface SalesUpdate {
product_name?: string;
category?: string;
quantity_sold?: number;
unit_price?: number;
discount_applied?: number;
tax_amount?: number;
weather_condition?: string;
temperature?: number;
special_event?: string;
}
export interface SalesSummary {
total_revenue: number;
total_quantity: number;
average_order_value: number;
total_orders: number;
gross_profit: number;
profit_margin: number;
growth_rate?: number;
period_comparison?: {
revenue_change: number;
quantity_change: number;
order_change: number;
};
}
export interface SalesAnalytics {
daily_sales: Array<{
date: string;
revenue: number;
quantity: number;
orders: number;
}>;
product_performance: Array<{
product_name: string;
total_revenue: number;
total_quantity: number;
growth_rate: number;
}>;
hourly_patterns: Array<{
hour: number;
average_sales: number;
peak_day: string;
}>;
weather_impact: Array<{
condition: string;
average_revenue: number;
impact_factor: number;
}>;
}
export interface SalesImportResult {
imported_records: number;
failed_records: number;
errors: Array<{
row: number;
field: string;
message: string;
}>;
summary: SalesSummary;
}
export interface PaginatedResponse<T> {
items: T[];
total: number;
page: number;
size: number;
pages: number;
}
class SalesService {
private readonly baseUrl = '/sales';
// Sales data CRUD operations
async getSales(params?: {
page?: number;
size?: number;
start_date?: string;
end_date?: string;
product_name?: string;
category?: string;
min_revenue?: number;
max_revenue?: number;
}): Promise<ApiResponse<PaginatedResponse<SalesData>>> {
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}?${queryParams.toString()}`
: this.baseUrl;
return apiClient.get(url);
}
async getSalesRecord(salesId: string): Promise<ApiResponse<SalesData>> {
return apiClient.get(`${this.baseUrl}/${salesId}`);
}
async createSalesRecord(salesData: SalesCreate): Promise<ApiResponse<SalesData>> {
return apiClient.post(this.baseUrl, salesData);
}
async updateSalesRecord(salesId: string, salesData: SalesUpdate): Promise<ApiResponse<SalesData>> {
return apiClient.put(`${this.baseUrl}/${salesId}`, salesData);
}
async deleteSalesRecord(salesId: string): Promise<ApiResponse<{ message: string }>> {
return apiClient.delete(`${this.baseUrl}/${salesId}`);
}
// Analytics and reporting
async getSalesSummary(params?: {
start_date?: string;
end_date?: string;
groupBy?: 'day' | 'week' | 'month' | 'year';
}): Promise<ApiResponse<SalesSummary>> {
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}/summary?${queryParams.toString()}`
: `${this.baseUrl}/summary`;
return apiClient.get(url);
}
async getSalesAnalytics(params?: {
start_date?: string;
end_date?: string;
granularity?: 'daily' | 'weekly' | 'monthly';
}): Promise<ApiResponse<SalesAnalytics>> {
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 getProductPerformance(params?: {
start_date?: string;
end_date?: string;
limit?: number;
sort_by?: 'revenue' | 'quantity' | 'growth';
}): Promise<ApiResponse<Array<{
product_name: string;
category?: string;
total_revenue: number;
total_quantity: number;
average_price: number;
growth_rate: number;
rank: 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}/products/performance?${queryParams.toString()}`
: `${this.baseUrl}/products/performance`;
return apiClient.get(url);
}
async getDailySalesTrends(params?: {
start_date?: string;
end_date?: string;
product_name?: string;
category?: string;
}): Promise<ApiResponse<Array<{
date: string;
revenue: number;
quantity: number;
orders: number;
average_order_value: 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}/trends/daily?${queryParams.toString()}`
: `${this.baseUrl}/trends/daily`;
return apiClient.get(url);
}
// Data import and export
async importSalesData(file: File, progressCallback?: (progress: number) => void): Promise<ApiResponse<SalesImportResult>> {
return apiClient.uploadFile(`${this.baseUrl}/import`, file, progressCallback);
}
async validateSalesData(file: File): Promise<ApiResponse<{
valid_records: number;
invalid_records: number;
errors: Array<{
row: number;
field: string;
message: string;
}>;
preview: SalesData[];
}>> {
return apiClient.uploadFile(`${this.baseUrl}/validate`, file);
}
async exportSalesData(params?: {
format?: 'csv' | 'xlsx';
start_date?: string;
end_date?: string;
include_analytics?: boolean;
}): Promise<ApiResponse<{ download_url: string }>> {
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}/export?${queryParams.toString()}`
: `${this.baseUrl}/export`;
return apiClient.get(url);
}
// AI and onboarding
async startOnboardingAnalysis(): Promise<ApiResponse<{ task_id: string }>> {
return apiClient.post(`${this.baseUrl}/onboarding/analyze`);
}
async getOnboardingStatus(taskId: string): Promise<ApiResponse<{
status: 'pending' | 'processing' | 'completed' | 'failed';
progress: number;
message: string;
results?: {
data_quality_score: number;
recommendations: string[];
detected_patterns: string[];
suggested_categories: string[];
};
}>> {
return apiClient.get(`${this.baseUrl}/onboarding/status/${taskId}`);
}
async getDataQualityReport(): Promise<ApiResponse<{
overall_score: number;
issues: Array<{
type: string;
severity: 'low' | 'medium' | 'high';
description: string;
affected_records: number;
suggestions: string[];
}>;
completeness: {
required_fields: number;
optional_fields: number;
};
}>> {
return apiClient.get(`${this.baseUrl}/data-quality`);
}
// Comparative analysis
async comparePeriods(params: {
current_start: string;
current_end: string;
comparison_start: string;
comparison_end: string;
metrics?: string[];
}): Promise<ApiResponse<{
current_period: SalesSummary;
comparison_period: SalesSummary;
changes: {
revenue_change: number;
quantity_change: number;
orders_change: number;
profit_change: number;
};
significant_changes: Array<{
metric: string;
change: number;
significance: 'positive' | 'negative' | 'neutral';
}>;
}>> {
return apiClient.post(`${this.baseUrl}/compare-periods`, params);
}
// Weather and external factor analysis
async getWeatherImpact(params?: {
start_date?: string;
end_date?: string;
weather_conditions?: string[];
}): Promise<ApiResponse<Array<{
condition: string;
average_revenue: number;
average_quantity: number;
impact_factor: number;
confidence: number;
}>>> {
const queryParams = new URLSearchParams();
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
if (Array.isArray(value)) {
value.forEach(v => queryParams.append(key, v));
} else {
queryParams.append(key, value.toString());
}
}
});
}
const url = queryParams.toString()
? `${this.baseUrl}/weather-impact?${queryParams.toString()}`
: `${this.baseUrl}/weather-impact`;
return apiClient.get(url);
}
// Utility methods
getWeatherConditions(): { value: string; label: string }[] {
return [
{ value: 'sunny', label: 'Sunny' },
{ value: 'cloudy', label: 'Cloudy' },
{ value: 'rainy', label: 'Rainy' },
{ value: 'stormy', label: 'Stormy' },
{ value: 'snowy', label: 'Snowy' },
{ value: 'foggy', label: 'Foggy' },
{ value: 'windy', label: 'Windy' },
];
}
getDaysOfWeek(): { value: string; label: string }[] {
return [
{ value: 'monday', label: 'Monday' },
{ value: 'tuesday', label: 'Tuesday' },
{ value: 'wednesday', label: 'Wednesday' },
{ value: 'thursday', label: 'Thursday' },
{ value: 'friday', label: 'Friday' },
{ value: 'saturday', label: 'Saturday' },
{ value: 'sunday', label: 'Sunday' },
];
}
getAnalyticsMetrics(): { value: string; label: string }[] {
return [
{ value: 'revenue', label: 'Revenue' },
{ value: 'quantity', label: 'Quantity Sold' },
{ value: 'orders', label: 'Number of Orders' },
{ value: 'average_order_value', label: 'Average Order Value' },
{ value: 'profit_margin', label: 'Profit Margin' },
{ value: 'customer_count', label: 'Customer Count' },
];
}
getExportFormats(): { value: string; label: string }[] {
return [
{ value: 'csv', label: 'CSV' },
{ value: 'xlsx', label: 'Excel (XLSX)' },
];
}
}
export const salesService = new SalesService();

View File

@@ -0,0 +1,297 @@
import { apiClient, ApiResponse } from './client';
// Request/Response Types based on backend schemas
export interface BakeryRegistration {
name: string;
address: string;
city?: string;
postal_code: string;
phone: string;
business_type?: string;
business_model?: string;
}
export interface TenantResponse {
id: string;
name: string;
subdomain?: string;
business_type: string;
business_model?: string;
address: string;
city: string;
postal_code: string;
phone?: string;
is_active: boolean;
subscription_tier: string;
model_trained: boolean;
last_training_date?: string;
owner_id: string;
created_at: string;
}
export interface TenantUpdate {
name?: string;
address?: string;
phone?: string;
business_type?: string;
business_model?: string;
}
export interface TenantAccessResponse {
has_access: boolean;
role: string;
permissions: string[];
}
export interface TenantMemberResponse {
id: string;
user_id: string;
role: string;
is_active: boolean;
joined_at?: string;
}
export interface TenantMemberInvitation {
email: string;
role: 'admin' | 'member' | 'viewer';
message?: string;
}
export interface TenantMemberUpdate {
role?: 'owner' | 'admin' | 'member' | 'viewer';
is_active?: boolean;
}
export interface TenantSubscriptionUpdate {
plan: 'basic' | 'professional' | 'enterprise';
billing_cycle?: 'monthly' | 'yearly';
}
export interface TenantStatsResponse {
tenant_id: string;
total_members: number;
active_members: number;
total_predictions: number;
models_trained: number;
last_training_date?: string;
subscription_plan: string;
subscription_status: string;
}
export interface TenantListResponse {
tenants: TenantResponse[];
total: number;
page: number;
per_page: number;
has_next: boolean;
has_prev: boolean;
}
export interface TenantSearchRequest {
query?: string;
business_type?: string;
city?: string;
status?: string;
limit?: number;
offset?: number;
}
class TenantService {
private readonly baseUrl = '/tenants';
// Tenant CRUD operations
async createTenant(tenantData: BakeryRegistration): Promise<ApiResponse<TenantResponse>> {
return apiClient.post(`${this.baseUrl}`, tenantData);
}
async getTenant(tenantId: string): Promise<ApiResponse<TenantResponse>> {
return apiClient.get(`${this.baseUrl}/${tenantId}`);
}
async getCurrentTenant(): Promise<ApiResponse<TenantResponse>> {
return apiClient.get(`${this.baseUrl}/current`);
}
async updateTenant(tenantId: string, tenantData: TenantUpdate): Promise<ApiResponse<TenantResponse>> {
return apiClient.put(`${this.baseUrl}/${tenantId}`, tenantData);
}
async deleteTenant(tenantId: string): Promise<ApiResponse<{ message: string }>> {
return apiClient.delete(`${this.baseUrl}/${tenantId}`);
}
async listTenants(params?: {
page?: number;
per_page?: number;
search?: string;
business_type?: string;
city?: string;
status?: string;
}): Promise<ApiResponse<TenantListResponse>> {
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}?${queryParams.toString()}`
: this.baseUrl;
return apiClient.get(url);
}
// Tenant access and verification
async checkTenantAccess(tenantId: string): Promise<ApiResponse<TenantAccessResponse>> {
return apiClient.get(`${this.baseUrl}/${tenantId}/access`);
}
async switchTenant(tenantId: string): Promise<ApiResponse<{ message: string; tenant: TenantResponse }>> {
const response = await apiClient.post(`${this.baseUrl}/${tenantId}/switch`);
if (response.success && response.data?.tenant) {
// Update local tenant context
localStorage.setItem('tenant_id', tenantId);
apiClient.setTenantId(tenantId);
}
return response;
}
// Member management
async getTenantMembers(tenantId: string): Promise<ApiResponse<TenantMemberResponse[]>> {
return apiClient.get(`${this.baseUrl}/${tenantId}/members`);
}
async inviteMember(tenantId: string, invitation: TenantMemberInvitation): Promise<ApiResponse<{ message: string; invitation_id: string }>> {
return apiClient.post(`${this.baseUrl}/${tenantId}/members/invite`, invitation);
}
async updateMember(tenantId: string, memberId: string, memberData: TenantMemberUpdate): Promise<ApiResponse<TenantMemberResponse>> {
return apiClient.put(`${this.baseUrl}/${tenantId}/members/${memberId}`, memberData);
}
async removeMember(tenantId: string, memberId: string): Promise<ApiResponse<{ message: string }>> {
return apiClient.delete(`${this.baseUrl}/${tenantId}/members/${memberId}`);
}
async acceptInvitation(invitationToken: string): Promise<ApiResponse<{ message: string; tenant: TenantResponse }>> {
return apiClient.post(`${this.baseUrl}/invitations/${invitationToken}/accept`);
}
async rejectInvitation(invitationToken: string): Promise<ApiResponse<{ message: string }>> {
return apiClient.post(`${this.baseUrl}/invitations/${invitationToken}/reject`);
}
// Subscription management
async updateSubscription(tenantId: string, subscriptionData: TenantSubscriptionUpdate): Promise<ApiResponse<{ message: string; subscription: any }>> {
return apiClient.put(`${this.baseUrl}/${tenantId}/subscription`, subscriptionData);
}
async getTenantStats(tenantId: string): Promise<ApiResponse<TenantStatsResponse>> {
return apiClient.get(`${this.baseUrl}/${tenantId}/stats`);
}
// Settings and configuration
async getTenantSettings(tenantId: string): Promise<ApiResponse<any>> {
return apiClient.get(`${this.baseUrl}/${tenantId}/settings`);
}
async updateTenantSettings(tenantId: string, settings: any): Promise<ApiResponse<any>> {
return apiClient.put(`${this.baseUrl}/${tenantId}/settings`, settings);
}
// Search and filtering
async searchTenants(searchParams: TenantSearchRequest): Promise<ApiResponse<TenantListResponse>> {
const queryParams = new URLSearchParams();
Object.entries(searchParams).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
queryParams.append(key, value.toString());
}
});
return apiClient.get(`${this.baseUrl}/search?${queryParams.toString()}`);
}
// Health and status checks
async getTenantHealth(tenantId: string): Promise<ApiResponse<{
status: string;
last_activity: string;
services_status: Record<string, string>;
}>> {
return apiClient.get(`${this.baseUrl}/${tenantId}/health`);
}
// Utility methods
async getUserTenants(): Promise<ApiResponse<TenantResponse[]>> {
return apiClient.get(`${this.baseUrl}/my-tenants`);
}
async validateTenantSlug(slug: string): Promise<ApiResponse<{ available: boolean; suggestions?: string[] }>> {
return apiClient.get(`${this.baseUrl}/validate-slug/${slug}`);
}
// Local state management helpers
getCurrentTenantId(): string | null {
return localStorage.getItem('tenant_id');
}
getCurrentTenantData(): TenantResponse | null {
const tenantData = localStorage.getItem('tenant_data');
return tenantData ? JSON.parse(tenantData) : null;
}
setCurrentTenant(tenant: TenantResponse) {
localStorage.setItem('tenant_id', tenant.id);
localStorage.setItem('tenant_data', JSON.stringify(tenant));
apiClient.setTenantId(tenant.id);
}
clearCurrentTenant() {
localStorage.removeItem('tenant_id');
localStorage.removeItem('tenant_data');
}
// Business type helpers
getBusinessTypes(): { value: string; label: string }[] {
return [
{ value: 'bakery', label: 'Bakery' },
{ value: 'coffee_shop', label: 'Coffee Shop' },
{ value: 'pastry_shop', label: 'Pastry Shop' },
{ value: 'restaurant', label: 'Restaurant' }
];
}
getBusinessModels(): { value: string; label: string }[] {
return [
{ value: 'individual_bakery', label: 'Individual Bakery' },
{ value: 'central_baker_satellite', label: 'Central Baker with Satellites' },
{ value: 'retail_bakery', label: 'Retail Bakery' },
{ value: 'hybrid_bakery', label: 'Hybrid Bakery' }
];
}
getSubscriptionTiers(): { value: string; label: string; description: string }[] {
return [
{ value: 'basic', label: 'Basic', description: 'Essential features for small bakeries' },
{ value: 'professional', label: 'Professional', description: 'Advanced features for growing businesses' },
{ value: 'enterprise', label: 'Enterprise', description: 'Full suite for large operations' }
];
}
getMemberRoles(): { value: string; label: string; description: string }[] {
return [
{ value: 'owner', label: 'Owner', description: 'Full access to all features and settings' },
{ value: 'admin', label: 'Admin', description: 'Manage users and most settings' },
{ value: 'member', label: 'Member', description: 'Access to operational features' },
{ value: 'viewer', label: 'Viewer', description: 'Read-only access to reports and data' }
];
}
}
export const tenantService = new TenantService();

View File

@@ -0,0 +1,447 @@
import { apiClient, ApiResponse } from './client';
// Model and training types
export interface TrainingJob {
id: string;
tenant_id: string;
name: string;
model_type: 'demand_forecasting' | 'sales_prediction' | 'inventory_optimization' | 'production_planning';
status: 'pending' | 'initializing' | 'training' | 'validating' | 'completed' | 'failed' | 'cancelled';
progress: number;
parameters: {
data_start_date: string;
data_end_date: string;
validation_split: number;
hyperparameters: Record<string, any>;
};
metrics?: {
accuracy: number;
mse: number;
mae: number;
r2_score: number;
validation_accuracy: number;
};
training_duration_seconds?: number;
model_size_mb?: number;
created_at: string;
started_at?: string;
completed_at?: string;
error_message?: string;
created_by: string;
}
export interface ModelInfo {
id: string;
tenant_id: string;
name: string;
model_type: string;
version: string;
status: 'training' | 'active' | 'deprecated' | 'archived';
is_production: boolean;
performance_metrics: {
accuracy: number;
precision: number;
recall: number;
f1_score: number;
last_evaluated: string;
};
training_data_info: {
record_count: number;
date_range: {
start: string;
end: string;
};
features_used: string[];
};
deployment_info?: {
deployed_at: string;
prediction_count: number;
avg_response_time_ms: number;
};
created_at: string;
updated_at: string;
}
export interface TrainingConfiguration {
id: string;
tenant_id: string;
model_type: string;
name: string;
description?: string;
is_default: boolean;
parameters: {
algorithm: string;
hyperparameters: Record<string, any>;
feature_selection: string[];
validation_method: string;
cross_validation_folds?: number;
};
data_requirements: {
minimum_records: number;
required_columns: string[];
date_range_days: number;
};
created_at: string;
updated_at: string;
}
class TrainingService {
private readonly baseUrl = '/training';
// Training job management
async getTrainingJobs(params?: {
page?: number;
size?: number;
status?: string;
model_type?: string;
}): Promise<ApiResponse<{ items: TrainingJob[]; total: number; page: number; size: number; pages: 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}/jobs?${queryParams.toString()}`
: `${this.baseUrl}/jobs`;
return apiClient.get(url);
}
async getTrainingJob(jobId: string): Promise<ApiResponse<TrainingJob>> {
return apiClient.get(`${this.baseUrl}/jobs/${jobId}`);
}
async createTrainingJob(jobData: {
name: string;
model_type: string;
config_id?: string;
parameters: {
data_start_date: string;
data_end_date: string;
validation_split?: number;
hyperparameters?: Record<string, any>;
};
}): Promise<ApiResponse<TrainingJob>> {
return apiClient.post(`${this.baseUrl}/jobs`, jobData);
}
async cancelTrainingJob(jobId: string): Promise<ApiResponse<{ message: string }>> {
return apiClient.post(`${this.baseUrl}/jobs/${jobId}/cancel`);
}
async retryTrainingJob(jobId: string): Promise<ApiResponse<TrainingJob>> {
return apiClient.post(`${this.baseUrl}/jobs/${jobId}/retry`);
}
async getTrainingLogs(jobId: string, params?: {
level?: 'debug' | 'info' | 'warning' | 'error';
limit?: number;
}): Promise<ApiResponse<Array<{
timestamp: string;
level: string;
message: string;
details?: Record<string, any>;
}>>> {
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}/jobs/${jobId}/logs?${queryParams.toString()}`
: `${this.baseUrl}/jobs/${jobId}/logs`;
return apiClient.get(url);
}
// Model management
async getModels(params?: {
page?: number;
size?: number;
model_type?: string;
status?: string;
is_production?: boolean;
}): Promise<ApiResponse<{ items: ModelInfo[]; total: number; page: number; size: number; pages: 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}/models?${queryParams.toString()}`
: `${this.baseUrl}/models`;
return apiClient.get(url);
}
async getModel(modelId: string): Promise<ApiResponse<ModelInfo>> {
return apiClient.get(`${this.baseUrl}/models/${modelId}`);
}
async deployModel(modelId: string): Promise<ApiResponse<{ message: string; deployment_id: string }>> {
return apiClient.post(`${this.baseUrl}/models/${modelId}/deploy`);
}
async undeployModel(modelId: string): Promise<ApiResponse<{ message: string }>> {
return apiClient.post(`${this.baseUrl}/models/${modelId}/undeploy`);
}
async deleteModel(modelId: string): Promise<ApiResponse<{ message: string }>> {
return apiClient.delete(`${this.baseUrl}/models/${modelId}`);
}
async compareModels(modelIds: string[]): Promise<ApiResponse<{
models: ModelInfo[];
comparison: {
accuracy_comparison: Array<{ model_id: string; accuracy: number; rank: number }>;
performance_metrics: Record<string, Array<{ model_id: string; value: number }>>;
recommendation: {
best_model_id: string;
reason: string;
confidence: number;
};
};
}>> {
return apiClient.post(`${this.baseUrl}/models/compare`, { model_ids: modelIds });
}
// Model evaluation
async evaluateModel(modelId: string, evaluationData?: {
test_data_start?: string;
test_data_end?: string;
metrics?: string[];
}): Promise<ApiResponse<{
evaluation_id: string;
status: 'pending' | 'running' | 'completed' | 'failed';
}>> {
return apiClient.post(`${this.baseUrl}/models/${modelId}/evaluate`, evaluationData);
}
async getEvaluationResults(evaluationId: string): Promise<ApiResponse<{
model_id: string;
status: string;
metrics: Record<string, number>;
predictions_sample: Array<{
actual: number;
predicted: number;
date: string;
error: number;
}>;
feature_importance?: Array<{
feature: string;
importance: number;
}>;
completed_at: string;
}>> {
return apiClient.get(`${this.baseUrl}/evaluations/${evaluationId}`);
}
// Training configuration
async getTrainingConfigs(modelType?: string): Promise<ApiResponse<TrainingConfiguration[]>> {
const url = modelType
? `${this.baseUrl}/configs?model_type=${encodeURIComponent(modelType)}`
: `${this.baseUrl}/configs`;
return apiClient.get(url);
}
async getTrainingConfig(configId: string): Promise<ApiResponse<TrainingConfiguration>> {
return apiClient.get(`${this.baseUrl}/configs/${configId}`);
}
async createTrainingConfig(configData: {
model_type: string;
name: string;
description?: string;
parameters: TrainingConfiguration['parameters'];
data_requirements?: Partial<TrainingConfiguration['data_requirements']>;
}): Promise<ApiResponse<TrainingConfiguration>> {
return apiClient.post(`${this.baseUrl}/configs`, configData);
}
async updateTrainingConfig(configId: string, configData: Partial<TrainingConfiguration>): Promise<ApiResponse<TrainingConfiguration>> {
return apiClient.put(`${this.baseUrl}/configs/${configId}`, configData);
}
async deleteTrainingConfig(configId: string): Promise<ApiResponse<{ message: string }>> {
return apiClient.delete(`${this.baseUrl}/configs/${configId}`);
}
// Data analysis and preparation
async analyzeTrainingData(params: {
model_type: string;
data_start_date: string;
data_end_date: string;
}): Promise<ApiResponse<{
data_quality: {
total_records: number;
complete_records: number;
missing_data_percentage: number;
duplicate_records: number;
};
feature_analysis: Array<{
feature: string;
data_type: string;
completeness: number;
unique_values: number;
correlation_with_target?: number;
}>;
recommendations: Array<{
type: 'data_cleaning' | 'feature_engineering' | 'model_selection';
message: string;
priority: 'high' | 'medium' | 'low';
}>;
training_feasibility: {
can_train: boolean;
minimum_requirements_met: boolean;
estimated_training_time_minutes: number;
};
}>> {
return apiClient.post(`${this.baseUrl}/analyze-data`, params);
}
async getDataAlignmentStatus(): Promise<ApiResponse<{
status: 'aligned' | 'misaligned' | 'processing';
last_alignment: string;
data_sources: Array<{
source: string;
status: 'synced' | 'out_of_sync' | 'error';
last_sync: string;
record_count: number;
}>;
issues?: Array<{
source: string;
issue_type: string;
description: string;
severity: 'low' | 'medium' | 'high';
}>;
}>> {
return apiClient.get(`${this.baseUrl}/data-alignment/status`);
}
async triggerDataAlignment(): Promise<ApiResponse<{ message: string; task_id: string }>> {
return apiClient.post(`${this.baseUrl}/data-alignment/trigger`);
}
// Training insights and recommendations
async getTrainingInsights(): Promise<ApiResponse<{
model_performance_trends: Array<{
model_type: string;
accuracy_trend: 'improving' | 'stable' | 'declining';
last_training_date: string;
recommendation: string;
}>;
training_frequency_suggestions: Array<{
model_type: string;
current_frequency: string;
suggested_frequency: string;
reason: string;
}>;
data_quality_alerts: Array<{
alert_type: string;
severity: 'info' | 'warning' | 'critical';
message: string;
affected_models: string[];
}>;
}>> {
return apiClient.get(`${this.baseUrl}/insights`);
}
// Utility methods
getModelTypes(): { value: string; label: string; description: string }[] {
return [
{
value: 'demand_forecasting',
label: 'Demand Forecasting',
description: 'Predict future demand for products based on historical sales and external factors'
},
{
value: 'sales_prediction',
label: 'Sales Prediction',
description: 'Forecast sales revenue and patterns'
},
{
value: 'inventory_optimization',
label: 'Inventory Optimization',
description: 'Optimize inventory levels and reorder points'
},
{
value: 'production_planning',
label: 'Production Planning',
description: 'Optimize production schedules and capacity planning'
}
];
}
getAlgorithmOptions(): { value: string; label: string; suitable_for: string[] }[] {
return [
{
value: 'prophet',
label: 'Prophet',
suitable_for: ['demand_forecasting', 'sales_prediction']
},
{
value: 'arima',
label: 'ARIMA',
suitable_for: ['demand_forecasting', 'sales_prediction']
},
{
value: 'random_forest',
label: 'Random Forest',
suitable_for: ['inventory_optimization', 'production_planning']
},
{
value: 'xgboost',
label: 'XGBoost',
suitable_for: ['demand_forecasting', 'inventory_optimization']
},
{
value: 'lstm',
label: 'LSTM Neural Network',
suitable_for: ['demand_forecasting', 'sales_prediction']
}
];
}
getValidationMethods(): { value: string; label: string; description: string }[] {
return [
{
value: 'time_series_split',
label: 'Time Series Split',
description: 'Split data chronologically for time series validation'
},
{
value: 'k_fold',
label: 'K-Fold Cross Validation',
description: 'Standard k-fold cross validation'
},
{
value: 'stratified_k_fold',
label: 'Stratified K-Fold',
description: 'Stratified sampling for cross validation'
},
{
value: 'holdout',
label: 'Holdout Validation',
description: 'Simple train/validation split'
}
];
}
}
export const trainingService = new TrainingService();