From 0fb9f9d0f0ecb016d303b3672472515461b07198 Mon Sep 17 00:00:00 2001 From: Urtzi Alfaro Date: Tue, 2 Sep 2025 19:05:48 +0200 Subject: [PATCH] Add onboarding.service.ts --- .../src/services/api/onboarding.service.ts | 400 ++++++++++++++++++ 1 file changed, 400 insertions(+) create mode 100644 frontend/src/services/api/onboarding.service.ts diff --git a/frontend/src/services/api/onboarding.service.ts b/frontend/src/services/api/onboarding.service.ts new file mode 100644 index 00000000..0ff74031 --- /dev/null +++ b/frontend/src/services/api/onboarding.service.ts @@ -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 { + try { + const formData = new FormData(); + formData.append('file', file); + + const response = await apiClient.post( + `${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 { + try { + const formData = new FormData(); + formData.append('file', file); + formData.append('product_list', JSON.stringify(productList)); + + const response = await apiClient.post( + `${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 { + try { + const response = await apiClient.post( + `${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 { + try { + const formData = new FormData(); + formData.append('file', file); + formData.append('inventory_mapping', JSON.stringify(inventoryMapping)); + + const response = await apiClient.post( + `${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 { + try { + const response = await apiClient.get( + `${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 { + try { + const formData = new FormData(); + formData.append('file', file); + + const response = await apiClient.post( + `${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 { + try { + const formData = new FormData(); + formData.append('file', file); + formData.append('update_existing', updateExisting.toString()); + + const response = await apiClient.post( + `${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 { + try { + const response = await apiClient.get( + `${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( + `${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 { + 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; \ No newline at end of file