Start integrating the onboarding flow with backend 10
This commit is contained in:
@@ -65,7 +65,14 @@ export const useInventorySetup = () => {
|
||||
createdItems?: any[];
|
||||
inventoryMapping?: { [productName: string]: string };
|
||||
}> => {
|
||||
console.log('useInventorySetup - createInventoryFromSuggestions called with:', {
|
||||
suggestionsCount: suggestions?.length,
|
||||
suggestions: suggestions?.slice(0, 3), // Log first 3 for debugging
|
||||
tenantId: currentTenant?.id
|
||||
});
|
||||
|
||||
if (!suggestions || suggestions.length === 0) {
|
||||
console.error('useInventorySetup - No suggestions provided');
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
error: 'No hay sugerencias para crear el inventario',
|
||||
@@ -74,6 +81,7 @@ export const useInventorySetup = () => {
|
||||
}
|
||||
|
||||
if (!currentTenant?.id) {
|
||||
console.error('useInventorySetup - No tenant ID available');
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
error: 'No se pudo obtener información del tenant',
|
||||
@@ -91,31 +99,54 @@ export const useInventorySetup = () => {
|
||||
const createdItems = [];
|
||||
const inventoryMapping: { [key: string]: string } = {};
|
||||
|
||||
console.log('useInventorySetup - Creating ingredients from suggestions...');
|
||||
|
||||
// Create ingredients from approved suggestions
|
||||
for (const suggestion of suggestions) {
|
||||
try {
|
||||
const ingredientData = {
|
||||
name: suggestion.name,
|
||||
name: suggestion.suggested_name || suggestion.original_name,
|
||||
category: suggestion.category || 'Sin categoría',
|
||||
description: suggestion.description || '',
|
||||
description: suggestion.notes || '',
|
||||
unit_of_measure: suggestion.unit_of_measure || 'unidad',
|
||||
cost_per_unit: suggestion.cost_per_unit || 0,
|
||||
supplier_info: suggestion.supplier_info || {},
|
||||
nutritional_info: suggestion.nutritional_info || {},
|
||||
storage_requirements: suggestion.storage_requirements || {},
|
||||
allergen_info: suggestion.allergen_info || {},
|
||||
is_active: true,
|
||||
minimum_stock_level: 1, // Default minimum stock
|
||||
maximum_stock_level: 100, // Default maximum stock
|
||||
reorder_point: 5, // Default reorder point
|
||||
shelf_life_days: suggestion.estimated_shelf_life_days || 30,
|
||||
requires_refrigeration: suggestion.requires_refrigeration || false,
|
||||
requires_freezing: suggestion.requires_freezing || false,
|
||||
is_seasonal: suggestion.is_seasonal || false,
|
||||
cost_per_unit: 0, // Will be set by user later
|
||||
notes: suggestion.notes || `Producto clasificado automáticamente desde: ${suggestion.original_name}`,
|
||||
};
|
||||
|
||||
console.log('useInventorySetup - Creating ingredient:', {
|
||||
name: ingredientData.name,
|
||||
category: ingredientData.category,
|
||||
original_name: suggestion.original_name,
|
||||
ingredientData: ingredientData,
|
||||
tenantId: currentTenant.id,
|
||||
apiUrl: `/tenants/${currentTenant.id}/ingredients`
|
||||
});
|
||||
|
||||
const createdItem = await createIngredientMutation.mutateAsync({
|
||||
tenantId: currentTenant.id,
|
||||
ingredientData,
|
||||
});
|
||||
|
||||
console.log('useInventorySetup - Created ingredient successfully:', {
|
||||
id: createdItem.id,
|
||||
name: createdItem.name
|
||||
});
|
||||
|
||||
createdItems.push(createdItem);
|
||||
inventoryMapping[suggestion.name] = createdItem.id;
|
||||
// Map both original and suggested names to the same ingredient ID for flexibility
|
||||
inventoryMapping[suggestion.original_name] = createdItem.id;
|
||||
if (suggestion.suggested_name && suggestion.suggested_name !== suggestion.original_name) {
|
||||
inventoryMapping[suggestion.suggested_name] = createdItem.id;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error creating ingredient ${suggestion.name}:`, error);
|
||||
console.error(`Error creating ingredient ${suggestion.suggested_name || suggestion.original_name}:`, error);
|
||||
// Continue with other ingredients even if one fails
|
||||
}
|
||||
}
|
||||
@@ -123,6 +154,12 @@ export const useInventorySetup = () => {
|
||||
if (createdItems.length === 0) {
|
||||
throw new Error('No se pudo crear ningún elemento del inventario');
|
||||
}
|
||||
|
||||
console.log('useInventorySetup - Successfully created ingredients:', {
|
||||
createdCount: createdItems.length,
|
||||
totalSuggestions: suggestions.length,
|
||||
inventoryMapping
|
||||
});
|
||||
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
@@ -139,6 +176,7 @@ export const useInventorySetup = () => {
|
||||
};
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Error creando el inventario';
|
||||
console.error('useInventorySetup - Error in createInventoryFromSuggestions:', error);
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
isLoading: false,
|
||||
|
||||
@@ -20,35 +20,6 @@ import type {
|
||||
} from './types';
|
||||
import type { BakeryRegistration } from '../../../api';
|
||||
|
||||
interface OnboardingActions {
|
||||
// Navigation
|
||||
nextStep: () => boolean;
|
||||
previousStep: () => boolean;
|
||||
goToStep: (stepIndex: number) => boolean;
|
||||
|
||||
// Data Management
|
||||
updateStepData: (stepId: string, data: Partial<OnboardingData>) => void;
|
||||
validateCurrentStep: () => string | null;
|
||||
|
||||
// Step-specific Actions
|
||||
createTenant: (bakeryData: BakeryRegistration) => Promise<boolean>;
|
||||
processSalesFile: (file: File, onProgress?: ProgressCallback) => Promise<boolean>;
|
||||
generateInventorySuggestions: (productList: string[]) => Promise<ProductSuggestionResponse[] | null>;
|
||||
createInventoryFromSuggestions: (suggestions: ProductSuggestionResponse[]) => Promise<boolean>;
|
||||
importSalesData: (salesData: any, inventoryMapping: { [productName: string]: string }) => Promise<boolean>;
|
||||
startTraining: (options?: {
|
||||
products?: string[];
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
}) => Promise<boolean>;
|
||||
|
||||
// Completion
|
||||
completeOnboarding: () => Promise<boolean>;
|
||||
|
||||
// Utilities
|
||||
clearError: () => void;
|
||||
reset: () => void;
|
||||
}
|
||||
|
||||
export const useOnboarding = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -63,28 +34,6 @@ export const useOnboarding = () => {
|
||||
const inventorySetup = useInventorySetup();
|
||||
const trainingOrchestration = useTrainingOrchestration();
|
||||
|
||||
// Navigation actions
|
||||
const nextStep = useCallback((): boolean => {
|
||||
const validation = validateCurrentStep();
|
||||
if (validation) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (flow.nextStep()) {
|
||||
flow.markStepCompleted(flow.currentStep - 1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, [flow]);
|
||||
|
||||
const previousStep = useCallback((): boolean => {
|
||||
return flow.previousStep();
|
||||
}, [flow]);
|
||||
|
||||
const goToStep = useCallback((stepIndex: number): boolean => {
|
||||
return flow.goToStep(stepIndex);
|
||||
}, [flow]);
|
||||
|
||||
// Data management
|
||||
const updateStepData = useCallback((stepId: string, stepData: Partial<OnboardingData>) => {
|
||||
data.updateStepData(stepId, stepData);
|
||||
@@ -104,10 +53,69 @@ export const useOnboarding = () => {
|
||||
const createTenant = useCallback(async (bakeryData: BakeryRegistration): Promise<boolean> => {
|
||||
const success = await tenantCreation.createTenant(bakeryData);
|
||||
if (success) {
|
||||
updateStepData('setup', { bakery: bakeryData });
|
||||
// Store the bakery data with tenant creation success flag and tenant ID
|
||||
updateStepData('setup', {
|
||||
bakery: {
|
||||
...bakeryData,
|
||||
tenantCreated: true,
|
||||
tenant_id: currentTenant?.id || 'created'
|
||||
} as any // Type assertion to allow the additional properties
|
||||
});
|
||||
console.log('useOnboarding - Tenant created successfully, updated step data with tenant ID:', currentTenant?.id);
|
||||
}
|
||||
return success;
|
||||
}, [tenantCreation, updateStepData]);
|
||||
}, [tenantCreation, updateStepData, currentTenant]);
|
||||
|
||||
// Navigation actions
|
||||
const nextStep = useCallback(async (): Promise<boolean> => {
|
||||
try {
|
||||
const currentStep = flow.getCurrentStep();
|
||||
console.log('useOnboarding - nextStep called from step:', currentStep.id);
|
||||
|
||||
const validation = validateCurrentStep();
|
||||
if (validation) {
|
||||
console.log('useOnboarding - Validation failed:', validation);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handle step-specific actions before moving to next step
|
||||
if (currentStep.id === 'setup') {
|
||||
console.log('useOnboarding - Creating tenant before leaving setup step');
|
||||
const allStepData = data.getAllStepData();
|
||||
const bakeryData = allStepData?.setup?.bakery;
|
||||
|
||||
if (bakeryData && !tenantCreation.isSuccess) {
|
||||
console.log('useOnboarding - Tenant data found, creating tenant:', bakeryData);
|
||||
const tenantSuccess = await createTenant(bakeryData);
|
||||
console.log('useOnboarding - Tenant creation result:', tenantSuccess);
|
||||
|
||||
if (!tenantSuccess) {
|
||||
console.log('useOnboarding - Tenant creation failed, stopping navigation');
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
console.log('useOnboarding - No tenant data found or tenant already created');
|
||||
}
|
||||
}
|
||||
|
||||
if (flow.nextStep()) {
|
||||
flow.markStepCompleted(flow.currentStep - 1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error('useOnboarding - Error in nextStep:', error);
|
||||
return false;
|
||||
}
|
||||
}, [flow, validateCurrentStep, data, createTenant, tenantCreation]);
|
||||
|
||||
const previousStep = useCallback((): boolean => {
|
||||
return flow.previousStep();
|
||||
}, [flow]);
|
||||
|
||||
const goToStep = useCallback((stepIndex: number): boolean => {
|
||||
return flow.goToStep(stepIndex);
|
||||
}, [flow]);
|
||||
|
||||
const processSalesFile = useCallback(async (
|
||||
file: File,
|
||||
@@ -299,8 +307,5 @@ export const useOnboarding = () => {
|
||||
completeOnboarding,
|
||||
clearError,
|
||||
reset,
|
||||
} satisfies ReturnType<typeof useOnboardingFlow> &
|
||||
ReturnType<typeof useOnboardingData> &
|
||||
{ [key: string]: any } &
|
||||
OnboardingActions;
|
||||
};
|
||||
};
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useClassifyProductsBatch, useValidateAndImportFile } from '../../../api';
|
||||
import { useClassifyProductsBatch, useValidateFileOnly } from '../../../api';
|
||||
import { useCurrentTenant } from '../../../stores';
|
||||
import type { ProductSuggestionResponse, ProgressCallback } from './types';
|
||||
|
||||
@@ -41,7 +41,7 @@ export const useSalesProcessing = () => {
|
||||
|
||||
const classifyProductsMutation = useClassifyProductsBatch();
|
||||
const currentTenant = useCurrentTenant();
|
||||
const { processFile: processFileImport } = useValidateAndImportFile();
|
||||
const { validateFile } = useValidateFileOnly();
|
||||
|
||||
const updateProgress = useCallback((progress: number, stage: string, message: string, onProgress?: ProgressCallback) => {
|
||||
setState(prev => ({
|
||||
@@ -76,21 +76,35 @@ export const useSalesProcessing = () => {
|
||||
updateProgress(20, 'validating', 'Validando estructura del archivo...', onProgress);
|
||||
|
||||
// Use the actual data import hook for file validation
|
||||
console.log('useSalesProcessing - currentTenant check:', {
|
||||
currentTenant,
|
||||
tenantId: currentTenant?.id,
|
||||
hasTenant: !!currentTenant,
|
||||
hasId: !!currentTenant?.id
|
||||
});
|
||||
|
||||
if (!currentTenant?.id) {
|
||||
throw new Error('No se pudo obtener información del tenant');
|
||||
throw new Error(`No se pudo obtener información del tenant. Tenant: ${JSON.stringify(currentTenant)}`);
|
||||
}
|
||||
|
||||
const result = await processFileImport(
|
||||
const result = await validateFile(
|
||||
currentTenant.id,
|
||||
file,
|
||||
{
|
||||
skipValidation: false,
|
||||
onProgress: (stage, progress, message) => {
|
||||
updateProgress(progress, stage, message, onProgress);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
console.log('useSalesProcessing - Backend result:', {
|
||||
success: result.success,
|
||||
hasValidationResult: !!result.validationResult,
|
||||
validationResult: result.validationResult,
|
||||
error: result.error,
|
||||
fullResult: result
|
||||
});
|
||||
|
||||
if (!result.success || !result.validationResult) {
|
||||
throw new Error(result.error || 'Error en la validación del archivo');
|
||||
}
|
||||
@@ -100,9 +114,21 @@ export const useSalesProcessing = () => {
|
||||
product_list: extractProductList(result.validationResult),
|
||||
};
|
||||
|
||||
console.log('useSalesProcessing - Processed validation result:', {
|
||||
validationResult,
|
||||
hasProductList: !!validationResult.product_list,
|
||||
productListLength: validationResult.product_list?.length || 0,
|
||||
productListSample: validationResult.product_list?.slice(0, 5)
|
||||
});
|
||||
|
||||
updateProgress(40, 'validating', 'Verificando integridad de datos...', onProgress);
|
||||
|
||||
if (!validationResult || !validationResult.product_list || validationResult.product_list.length === 0) {
|
||||
console.error('useSalesProcessing - No products found:', {
|
||||
hasValidationResult: !!validationResult,
|
||||
hasProductList: !!validationResult?.product_list,
|
||||
productListLength: validationResult?.product_list?.length
|
||||
});
|
||||
throw new Error('No se encontraron productos válidos en el archivo');
|
||||
}
|
||||
|
||||
@@ -144,11 +170,24 @@ export const useSalesProcessing = () => {
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
}, [updateProgress, currentTenant, processFileImport]);
|
||||
}, [updateProgress, currentTenant, validateFile]);
|
||||
|
||||
// Helper to extract product list from validation result
|
||||
const extractProductList = useCallback((validationResult: any): string[] => {
|
||||
// Extract unique product names from sample records
|
||||
console.log('extractProductList - Input validation result:', {
|
||||
hasProductList: !!validationResult?.product_list,
|
||||
productList: validationResult?.product_list,
|
||||
hasSampleRecords: !!validationResult?.sample_records,
|
||||
keys: Object.keys(validationResult || {})
|
||||
});
|
||||
|
||||
// First try to use the direct product_list from backend response
|
||||
if (validationResult.product_list && Array.isArray(validationResult.product_list)) {
|
||||
console.log('extractProductList - Using direct product_list:', validationResult.product_list);
|
||||
return validationResult.product_list;
|
||||
}
|
||||
|
||||
// Fallback: Extract unique product names from sample records
|
||||
if (validationResult.sample_records && Array.isArray(validationResult.sample_records)) {
|
||||
const productSet = new Set<string>();
|
||||
validationResult.sample_records.forEach((record: any) => {
|
||||
@@ -156,8 +195,12 @@ export const useSalesProcessing = () => {
|
||||
productSet.add(record.product_name);
|
||||
}
|
||||
});
|
||||
return Array.from(productSet);
|
||||
const extractedList = Array.from(productSet);
|
||||
console.log('extractProductList - Extracted from sample_records:', extractedList);
|
||||
return extractedList;
|
||||
}
|
||||
|
||||
console.log('extractProductList - No products found, returning empty array');
|
||||
return [];
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useRegisterBakery } from '../../../api';
|
||||
import type { BakeryRegistration } from '../../../api';
|
||||
import type { BakeryRegistration, TenantResponse } from '../../../api';
|
||||
import { useTenantStore } from '../../../stores/tenant.store';
|
||||
|
||||
interface TenantCreationState {
|
||||
isLoading: boolean;
|
||||
@@ -28,6 +29,7 @@ export const useTenantCreation = () => {
|
||||
});
|
||||
|
||||
const registerBakeryMutation = useRegisterBakery();
|
||||
const { setCurrentTenant, loadUserTenants } = useTenantStore();
|
||||
|
||||
const createTenant = useCallback(async (bakeryData: BakeryRegistration): Promise<boolean> => {
|
||||
if (!bakeryData) {
|
||||
@@ -46,7 +48,14 @@ export const useTenantCreation = () => {
|
||||
}));
|
||||
|
||||
try {
|
||||
await registerBakeryMutation.mutateAsync(bakeryData);
|
||||
const tenantResponse: TenantResponse = await registerBakeryMutation.mutateAsync(bakeryData);
|
||||
|
||||
// Update the tenant store with the newly created tenant
|
||||
console.log('useTenantCreation - Setting current tenant:', tenantResponse);
|
||||
setCurrentTenant(tenantResponse);
|
||||
|
||||
// Reload user tenants to ensure the list is up to date
|
||||
await loadUserTenants();
|
||||
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
@@ -55,6 +64,7 @@ export const useTenantCreation = () => {
|
||||
tenantData: bakeryData,
|
||||
}));
|
||||
|
||||
console.log('useTenantCreation - Tenant created and set successfully');
|
||||
return true;
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Error al crear la panadería';
|
||||
|
||||
Reference in New Issue
Block a user