Add onboarding.service.ts
This commit is contained in:
400
frontend/src/services/api/onboarding.service.ts
Normal file
400
frontend/src/services/api/onboarding.service.ts
Normal file
@@ -0,0 +1,400 @@
|
||||
/**
|
||||
* Enhanced Onboarding API Service
|
||||
* Provides integration with backend AI-powered onboarding endpoints
|
||||
*/
|
||||
|
||||
import { apiClient } from './client';
|
||||
|
||||
export interface OnboardingFileValidationResponse {
|
||||
is_valid: boolean;
|
||||
total_records: number;
|
||||
unique_products: number;
|
||||
product_list: string[];
|
||||
validation_errors: any[];
|
||||
validation_warnings: any[];
|
||||
summary: any;
|
||||
}
|
||||
|
||||
export interface ProductSuggestion {
|
||||
suggestion_id: string;
|
||||
original_name: string;
|
||||
suggested_name: string;
|
||||
product_type: 'ingredient' | 'finished_product';
|
||||
category: string;
|
||||
unit_of_measure: string;
|
||||
confidence_score: number;
|
||||
estimated_shelf_life_days: number;
|
||||
requires_refrigeration: boolean;
|
||||
requires_freezing: boolean;
|
||||
is_seasonal: boolean;
|
||||
suggested_supplier?: string;
|
||||
notes: string;
|
||||
sales_data: {
|
||||
total_quantity: number;
|
||||
average_daily_sales: number;
|
||||
peak_day: string;
|
||||
frequency: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface BusinessModelAnalysis {
|
||||
model: 'production' | 'retail' | 'hybrid';
|
||||
confidence: number;
|
||||
ingredient_count: number;
|
||||
finished_product_count: number;
|
||||
ingredient_ratio: number;
|
||||
recommendations: string[];
|
||||
}
|
||||
|
||||
export interface ProductSuggestionsResponse {
|
||||
suggestions: ProductSuggestion[];
|
||||
business_model_analysis: BusinessModelAnalysis;
|
||||
total_products: number;
|
||||
high_confidence_count: number;
|
||||
low_confidence_count: number;
|
||||
processing_time_seconds: number;
|
||||
}
|
||||
|
||||
export interface InventoryCreationResponse {
|
||||
created_items: any[];
|
||||
failed_items: any[];
|
||||
total_approved: number;
|
||||
success_rate: number;
|
||||
inventory_mapping?: { [productName: string]: string };
|
||||
}
|
||||
|
||||
export interface SalesImportResponse {
|
||||
import_job_id: string;
|
||||
status: 'completed' | 'failed' | 'partial';
|
||||
processed_rows: number;
|
||||
successful_imports: number;
|
||||
failed_imports: number;
|
||||
errors: string[];
|
||||
warnings: string[];
|
||||
processing_time?: number;
|
||||
}
|
||||
|
||||
export interface BusinessModelGuide {
|
||||
title: string;
|
||||
description: string;
|
||||
next_steps: string[];
|
||||
recommended_features: string[];
|
||||
sample_workflows: string[];
|
||||
}
|
||||
|
||||
class OnboardingApiService {
|
||||
private readonly basePath = '/tenants';
|
||||
private readonly salesBasePath = '/tenants';
|
||||
|
||||
/**
|
||||
* Step 1: Validate uploaded file and extract unique products
|
||||
*/
|
||||
async validateOnboardingFile(
|
||||
tenantId: string,
|
||||
file: File
|
||||
): Promise<OnboardingFileValidationResponse> {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
const response = await apiClient.post<OnboardingFileValidationResponse>(
|
||||
`${this.basePath}/${tenantId}/onboarding/validate-file`,
|
||||
formData,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.success) {
|
||||
throw new Error(`Validation failed: ${response.error || 'Unknown error'}`);
|
||||
}
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('File validation failed:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 2: Generate AI-powered inventory suggestions
|
||||
*/
|
||||
async generateInventorySuggestions(
|
||||
tenantId: string,
|
||||
file: File,
|
||||
productList: string[]
|
||||
): Promise<ProductSuggestionsResponse> {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('product_list', JSON.stringify(productList));
|
||||
|
||||
const response = await apiClient.post<ProductSuggestionsResponse>(
|
||||
`${this.basePath}/${tenantId}/onboarding/generate-suggestions`,
|
||||
formData,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.success) {
|
||||
throw new Error(`Suggestion generation failed: ${response.error || 'Unknown error'}`);
|
||||
}
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Suggestion generation failed:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 3: Create inventory items from approved suggestions
|
||||
*/
|
||||
async createInventoryFromSuggestions(
|
||||
tenantId: string,
|
||||
approvedSuggestions: any[]
|
||||
): Promise<InventoryCreationResponse> {
|
||||
try {
|
||||
const response = await apiClient.post<InventoryCreationResponse>(
|
||||
`${this.basePath}/${tenantId}/onboarding/create-inventory`,
|
||||
{
|
||||
suggestions: approvedSuggestions
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.success) {
|
||||
throw new Error(`Inventory creation failed: ${response.error || 'Unknown error'}`);
|
||||
}
|
||||
|
||||
// Create inventory mapping if not provided
|
||||
if (!response.data.inventory_mapping) {
|
||||
response.data.inventory_mapping = {};
|
||||
response.data.created_items.forEach((item, index) => {
|
||||
if (approvedSuggestions[index]) {
|
||||
response.data.inventory_mapping![approvedSuggestions[index].original_name] = item.id;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Inventory creation failed:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 4: Import sales data with inventory mapping
|
||||
*/
|
||||
async importSalesWithInventory(
|
||||
tenantId: string,
|
||||
file: File,
|
||||
inventoryMapping: { [productName: string]: string }
|
||||
): Promise<SalesImportResponse> {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('inventory_mapping', JSON.stringify(inventoryMapping));
|
||||
|
||||
const response = await apiClient.post<SalesImportResponse>(
|
||||
`${this.basePath}/${tenantId}/onboarding/import-sales`,
|
||||
formData,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.success) {
|
||||
throw new Error(`Sales import failed: ${response.error || 'Unknown error'}`);
|
||||
}
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Sales import failed:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get business model specific recommendations
|
||||
*/
|
||||
async getBusinessModelGuide(
|
||||
tenantId: string,
|
||||
model: 'production' | 'retail' | 'hybrid'
|
||||
): Promise<BusinessModelGuide> {
|
||||
try {
|
||||
const response = await apiClient.get<BusinessModelGuide>(
|
||||
`${this.basePath}/${tenantId}/onboarding/business-model-guide?model=${model}`
|
||||
);
|
||||
|
||||
if (!response.success) {
|
||||
throw new Error(`Failed to get business model guide: ${response.error || 'Unknown error'}`);
|
||||
}
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Failed to get business model guide:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate sales data using the sales service (fallback)
|
||||
*/
|
||||
async validateSalesData(
|
||||
tenantId: string,
|
||||
file: File
|
||||
): Promise<any> {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
const response = await apiClient.post<any>(
|
||||
`${this.salesBasePath}/${tenantId}/sales/import/validate`,
|
||||
formData,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.success) {
|
||||
throw new Error(`Sales validation failed: ${response.error || 'Unknown error'}`);
|
||||
}
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Sales validation failed:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Import sales data using the sales service (fallback)
|
||||
*/
|
||||
async importSalesData(
|
||||
tenantId: string,
|
||||
file: File,
|
||||
updateExisting: boolean = false
|
||||
): Promise<any> {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('update_existing', updateExisting.toString());
|
||||
|
||||
const response = await apiClient.post<any>(
|
||||
`${this.salesBasePath}/${tenantId}/sales/import`,
|
||||
formData,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.success) {
|
||||
throw new Error(`Sales import failed: ${response.error || 'Unknown error'}`);
|
||||
}
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Sales import failed:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sales data import template
|
||||
*/
|
||||
async getSalesImportTemplate(
|
||||
tenantId: string,
|
||||
format: 'csv' | 'json' = 'csv'
|
||||
): Promise<any> {
|
||||
try {
|
||||
const response = await apiClient.get<any>(
|
||||
`${this.salesBasePath}/${tenantId}/sales/import/template?format=${format}`
|
||||
);
|
||||
|
||||
if (!response.success) {
|
||||
throw new Error(`Failed to get template: ${response.error || 'Unknown error'}`);
|
||||
}
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Failed to get template:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download template file (utility method)
|
||||
*/
|
||||
downloadTemplate(templateData: any, filename: string, format: 'csv' | 'json' = 'csv'): void {
|
||||
let content: string;
|
||||
let mimeType: string;
|
||||
|
||||
if (format === 'csv') {
|
||||
content = templateData.template;
|
||||
mimeType = 'text/csv';
|
||||
} else {
|
||||
content = JSON.stringify(templateData.template, null, 2);
|
||||
mimeType = 'application/json';
|
||||
}
|
||||
|
||||
const blob = new Blob([content], { type: mimeType });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
|
||||
link.href = url;
|
||||
link.download = filename;
|
||||
link.style.display = 'none';
|
||||
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility: Check if a tenant has completed onboarding
|
||||
*/
|
||||
async checkOnboardingStatus(tenantId: string): Promise<{ completed: boolean; steps_completed: string[] }> {
|
||||
try {
|
||||
const response = await apiClient.get<any>(
|
||||
`${this.basePath}/${tenantId}/onboarding/status`
|
||||
);
|
||||
|
||||
return response.data || { completed: false, steps_completed: [] };
|
||||
} catch (error) {
|
||||
console.warn('Could not check onboarding status:', error);
|
||||
return { completed: false, steps_completed: [] };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility: Mark onboarding as complete
|
||||
*/
|
||||
async completeOnboarding(tenantId: string, metadata?: any): Promise<void> {
|
||||
try {
|
||||
await apiClient.post(
|
||||
`${this.basePath}/${tenantId}/onboarding/complete`,
|
||||
{ metadata }
|
||||
);
|
||||
} catch (error) {
|
||||
console.warn('Could not mark onboarding as complete:', error);
|
||||
// Don't throw error, this is not critical
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const onboardingApiService = new OnboardingApiService();
|
||||
export default OnboardingApiService;
|
||||
Reference in New Issue
Block a user