Fix new services implementation 1
This commit is contained in:
@@ -10,11 +10,12 @@ from uuid import UUID
|
||||
from pydantic import BaseModel, Field
|
||||
import structlog
|
||||
|
||||
from app.services.onboarding_import_service import (
|
||||
OnboardingImportService,
|
||||
OnboardingImportResult,
|
||||
InventoryCreationRequest,
|
||||
get_onboarding_import_service
|
||||
from app.services.ai_onboarding_service import (
|
||||
AIOnboardingService,
|
||||
OnboardingValidationResult,
|
||||
ProductSuggestionsResult,
|
||||
OnboardingImportResult,
|
||||
get_ai_onboarding_service
|
||||
)
|
||||
from shared.auth.decorators import get_current_user_dep, get_current_tenant_id_dep
|
||||
|
||||
@@ -22,16 +23,6 @@ router = APIRouter(tags=["onboarding"])
|
||||
logger = structlog.get_logger()
|
||||
|
||||
|
||||
class OnboardingAnalysisResponse(BaseModel):
|
||||
"""Response for onboarding analysis"""
|
||||
total_products_found: int
|
||||
inventory_suggestions: List[Dict[str, Any]]
|
||||
business_model_analysis: Dict[str, Any]
|
||||
import_job_id: str
|
||||
status: str
|
||||
processed_rows: int
|
||||
errors: List[str]
|
||||
warnings: List[str]
|
||||
|
||||
|
||||
class InventoryApprovalRequest(BaseModel):
|
||||
@@ -58,23 +49,22 @@ class SalesImportResponse(BaseModel):
|
||||
warnings: List[str]
|
||||
|
||||
|
||||
@router.post("/tenants/{tenant_id}/onboarding/analyze", response_model=OnboardingAnalysisResponse)
|
||||
async def analyze_onboarding_data(
|
||||
@router.post("/tenants/{tenant_id}/onboarding/validate-file", response_model=FileValidationResponse)
|
||||
async def validate_onboarding_file(
|
||||
file: UploadFile = File(..., description="Sales data CSV/Excel file"),
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
current_tenant: str = Depends(get_current_tenant_id_dep),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
onboarding_service: OnboardingImportService = Depends(get_onboarding_import_service)
|
||||
onboarding_service: AIOnboardingService = Depends(get_ai_onboarding_service)
|
||||
):
|
||||
"""
|
||||
Step 1: Analyze uploaded sales data and suggest inventory items
|
||||
Step 1: Validate uploaded file and extract unique products
|
||||
|
||||
This endpoint:
|
||||
1. Parses the uploaded sales file
|
||||
2. Extracts unique products and sales metrics
|
||||
3. Uses AI to classify products and suggest inventory items
|
||||
4. Analyzes business model (production vs retail)
|
||||
5. Returns suggestions for user review
|
||||
1. Validates the file format and content
|
||||
2. Checks for required columns (date, product, etc.)
|
||||
3. Extracts unique products from sales data
|
||||
4. Returns validation results and product list
|
||||
"""
|
||||
try:
|
||||
# Verify tenant access
|
||||
@@ -89,34 +79,42 @@ async def analyze_onboarding_data(
|
||||
if not any(file.filename.lower().endswith(ext) for ext in allowed_extensions):
|
||||
raise HTTPException(status_code=400, detail=f"Unsupported file format. Allowed: {allowed_extensions}")
|
||||
|
||||
# Determine file format
|
||||
file_format = "csv" if file.filename.lower().endswith('.csv') else "excel"
|
||||
|
||||
# Read file content
|
||||
file_content = await file.read()
|
||||
if not file_content:
|
||||
raise HTTPException(status_code=400, detail="File is empty")
|
||||
|
||||
# Analyze the data
|
||||
result = await onboarding_service.analyze_sales_data_for_onboarding(
|
||||
file_content=file_content,
|
||||
filename=file.filename,
|
||||
tenant_id=tenant_id,
|
||||
user_id=UUID(current_user['user_id'])
|
||||
# Convert bytes to string for CSV
|
||||
if file_format == "csv":
|
||||
file_data = file_content.decode('utf-8')
|
||||
else:
|
||||
import base64
|
||||
file_data = base64.b64encode(file_content).decode('utf-8')
|
||||
|
||||
# Validate and extract products
|
||||
result = await onboarding_service.validate_and_extract_products(
|
||||
file_data=file_data,
|
||||
file_format=file_format,
|
||||
tenant_id=tenant_id
|
||||
)
|
||||
|
||||
response = OnboardingAnalysisResponse(
|
||||
total_products_found=result.total_products_found,
|
||||
inventory_suggestions=result.inventory_suggestions,
|
||||
business_model_analysis=result.business_model_analysis,
|
||||
import_job_id=str(result.import_job_id),
|
||||
status=result.status,
|
||||
processed_rows=result.processed_rows,
|
||||
errors=result.errors,
|
||||
warnings=result.warnings
|
||||
response = FileValidationResponse(
|
||||
is_valid=result.is_valid,
|
||||
total_records=result.total_records,
|
||||
unique_products=result.unique_products,
|
||||
product_list=result.product_list,
|
||||
validation_errors=result.validation_details.errors,
|
||||
validation_warnings=result.validation_details.warnings,
|
||||
summary=result.summary
|
||||
)
|
||||
|
||||
logger.info("Onboarding analysis complete",
|
||||
logger.info("File validation complete",
|
||||
filename=file.filename,
|
||||
products_found=result.total_products_found,
|
||||
business_model=result.business_model_analysis.get('model'),
|
||||
is_valid=result.is_valid,
|
||||
unique_products=result.unique_products,
|
||||
tenant_id=tenant_id)
|
||||
|
||||
return response
|
||||
@@ -124,9 +122,120 @@ async def analyze_onboarding_data(
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error("Failed onboarding analysis",
|
||||
logger.error("Failed file validation",
|
||||
error=str(e), filename=file.filename if file else None, tenant_id=tenant_id)
|
||||
raise HTTPException(status_code=500, detail=f"Analysis failed: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=f"Validation failed: {str(e)}")
|
||||
|
||||
|
||||
@router.post("/tenants/{tenant_id}/onboarding/generate-suggestions", response_model=ProductSuggestionsResponse)
|
||||
async def generate_inventory_suggestions(
|
||||
file: UploadFile = File(..., description="Same sales data file from step 1"),
|
||||
product_list: str = Form(..., description="JSON array of product names to classify"),
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
current_tenant: str = Depends(get_current_tenant_id_dep),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
onboarding_service: AIOnboardingService = Depends(get_ai_onboarding_service)
|
||||
):
|
||||
"""
|
||||
Step 2: Generate AI-powered inventory suggestions
|
||||
|
||||
This endpoint:
|
||||
1. Takes the validated file and product list from step 1
|
||||
2. Uses AI to classify products into inventory categories
|
||||
3. Analyzes business model (production vs retail)
|
||||
4. Returns detailed suggestions for user review
|
||||
"""
|
||||
try:
|
||||
# Verify tenant access
|
||||
if str(tenant_id) != current_tenant:
|
||||
raise HTTPException(status_code=403, detail="Access denied to this tenant")
|
||||
|
||||
# Parse product list
|
||||
import json
|
||||
try:
|
||||
products = json.loads(product_list)
|
||||
except json.JSONDecodeError as e:
|
||||
raise HTTPException(status_code=400, detail=f"Invalid product list format: {str(e)}")
|
||||
|
||||
if not products:
|
||||
raise HTTPException(status_code=400, detail="No products provided")
|
||||
|
||||
# Determine file format
|
||||
file_format = "csv" if file.filename.lower().endswith('.csv') else "excel"
|
||||
|
||||
# Read file content
|
||||
file_content = await file.read()
|
||||
if not file_content:
|
||||
raise HTTPException(status_code=400, detail="File is empty")
|
||||
|
||||
# Convert bytes to string for CSV
|
||||
if file_format == "csv":
|
||||
file_data = file_content.decode('utf-8')
|
||||
else:
|
||||
import base64
|
||||
file_data = base64.b64encode(file_content).decode('utf-8')
|
||||
|
||||
# Generate suggestions
|
||||
result = await onboarding_service.generate_inventory_suggestions(
|
||||
product_list=products,
|
||||
file_data=file_data,
|
||||
file_format=file_format,
|
||||
tenant_id=tenant_id
|
||||
)
|
||||
|
||||
# Convert suggestions to dict format
|
||||
suggestions_dict = []
|
||||
for suggestion in result.suggestions:
|
||||
suggestion_dict = {
|
||||
"suggestion_id": suggestion.suggestion_id,
|
||||
"original_name": suggestion.original_name,
|
||||
"suggested_name": suggestion.suggested_name,
|
||||
"product_type": suggestion.product_type,
|
||||
"category": suggestion.category,
|
||||
"unit_of_measure": suggestion.unit_of_measure,
|
||||
"confidence_score": suggestion.confidence_score,
|
||||
"estimated_shelf_life_days": suggestion.estimated_shelf_life_days,
|
||||
"requires_refrigeration": suggestion.requires_refrigeration,
|
||||
"requires_freezing": suggestion.requires_freezing,
|
||||
"is_seasonal": suggestion.is_seasonal,
|
||||
"suggested_supplier": suggestion.suggested_supplier,
|
||||
"notes": suggestion.notes,
|
||||
"sales_data": suggestion.sales_data
|
||||
}
|
||||
suggestions_dict.append(suggestion_dict)
|
||||
|
||||
business_model_dict = {
|
||||
"model": result.business_model_analysis.model,
|
||||
"confidence": result.business_model_analysis.confidence,
|
||||
"ingredient_count": result.business_model_analysis.ingredient_count,
|
||||
"finished_product_count": result.business_model_analysis.finished_product_count,
|
||||
"ingredient_ratio": result.business_model_analysis.ingredient_ratio,
|
||||
"recommendations": result.business_model_analysis.recommendations
|
||||
}
|
||||
|
||||
response = ProductSuggestionsResponse(
|
||||
suggestions=suggestions_dict,
|
||||
business_model_analysis=business_model_dict,
|
||||
total_products=result.total_products,
|
||||
high_confidence_count=result.high_confidence_count,
|
||||
low_confidence_count=result.low_confidence_count,
|
||||
processing_time_seconds=result.processing_time_seconds
|
||||
)
|
||||
|
||||
logger.info("AI suggestions generated",
|
||||
total_products=result.total_products,
|
||||
business_model=result.business_model_analysis.model,
|
||||
high_confidence=result.high_confidence_count,
|
||||
tenant_id=tenant_id)
|
||||
|
||||
return response
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error("Failed to generate suggestions",
|
||||
error=str(e), tenant_id=tenant_id)
|
||||
raise HTTPException(status_code=500, detail=f"Suggestion generation failed: {str(e)}")
|
||||
|
||||
|
||||
@router.post("/tenants/{tenant_id}/onboarding/create-inventory", response_model=InventoryCreationResponse)
|
||||
@@ -135,16 +244,16 @@ async def create_inventory_from_suggestions(
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
current_tenant: str = Depends(get_current_tenant_id_dep),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
onboarding_service: OnboardingImportService = Depends(get_onboarding_import_service)
|
||||
onboarding_service: AIOnboardingService = Depends(get_ai_onboarding_service)
|
||||
):
|
||||
"""
|
||||
Step 2: Create inventory items from approved suggestions
|
||||
Step 3: Create inventory items from approved suggestions
|
||||
|
||||
This endpoint:
|
||||
1. Takes user-approved inventory suggestions
|
||||
2. Applies any user modifications
|
||||
1. Takes user-approved inventory suggestions from step 2
|
||||
2. Applies any user modifications to suggestions
|
||||
3. Creates inventory items via inventory service
|
||||
4. Returns creation results
|
||||
4. Returns creation results for final import step
|
||||
"""
|
||||
try:
|
||||
# Verify tenant access
|
||||
@@ -154,18 +263,9 @@ async def create_inventory_from_suggestions(
|
||||
if not request.suggestions:
|
||||
raise HTTPException(status_code=400, detail="No suggestions provided")
|
||||
|
||||
# Convert to internal format
|
||||
approval_requests = []
|
||||
for suggestion in request.suggestions:
|
||||
approval_requests.append(InventoryCreationRequest(
|
||||
suggestion_id=suggestion.get('suggestion_id'),
|
||||
approved=suggestion.get('approved', False),
|
||||
modifications=suggestion.get('modifications', {})
|
||||
))
|
||||
|
||||
# Create inventory items
|
||||
# Create inventory items using new service
|
||||
result = await onboarding_service.create_inventory_from_suggestions(
|
||||
suggestions_approval=approval_requests,
|
||||
approved_suggestions=request.suggestions,
|
||||
tenant_id=tenant_id,
|
||||
user_id=UUID(current_user['user_id'])
|
||||
)
|
||||
@@ -199,16 +299,16 @@ async def import_sales_with_inventory(
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
current_tenant: str = Depends(get_current_tenant_id_dep),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
onboarding_service: OnboardingImportService = Depends(get_onboarding_import_service)
|
||||
onboarding_service: AIOnboardingService = Depends(get_ai_onboarding_service)
|
||||
):
|
||||
"""
|
||||
Step 3: Import sales data using created inventory items
|
||||
Step 4: Final sales data import using created inventory items
|
||||
|
||||
This endpoint:
|
||||
1. Takes the same sales file from step 1
|
||||
2. Uses the inventory mapping from step 2
|
||||
3. Imports sales records with proper inventory product references
|
||||
4. Returns import results
|
||||
1. Takes the same validated sales file from step 1
|
||||
2. Uses the inventory mapping from step 3
|
||||
3. Imports sales records using detailed processing from DataImportService
|
||||
4. Returns final import results - onboarding complete!
|
||||
"""
|
||||
try:
|
||||
# Verify tenant access
|
||||
@@ -223,41 +323,51 @@ async def import_sales_with_inventory(
|
||||
import json
|
||||
try:
|
||||
mapping = json.loads(inventory_mapping)
|
||||
# Convert string UUIDs to UUID objects
|
||||
inventory_mapping_uuids = {
|
||||
product_name: UUID(inventory_id)
|
||||
# Convert to string mapping for the new service
|
||||
inventory_mapping_dict = {
|
||||
product_name: str(inventory_id)
|
||||
for product_name, inventory_id in mapping.items()
|
||||
}
|
||||
except (json.JSONDecodeError, ValueError) as e:
|
||||
except json.JSONDecodeError as e:
|
||||
raise HTTPException(status_code=400, detail=f"Invalid inventory mapping format: {str(e)}")
|
||||
|
||||
# Determine file format
|
||||
file_format = "csv" if file.filename.lower().endswith('.csv') else "excel"
|
||||
|
||||
# Read file content
|
||||
file_content = await file.read()
|
||||
if not file_content:
|
||||
raise HTTPException(status_code=400, detail="File is empty")
|
||||
|
||||
# Import sales data
|
||||
# Convert bytes to string for CSV
|
||||
if file_format == "csv":
|
||||
file_data = file_content.decode('utf-8')
|
||||
else:
|
||||
import base64
|
||||
file_data = base64.b64encode(file_content).decode('utf-8')
|
||||
|
||||
# Import sales data using new service
|
||||
result = await onboarding_service.import_sales_data_with_inventory(
|
||||
file_content=file_content,
|
||||
filename=file.filename,
|
||||
file_data=file_data,
|
||||
file_format=file_format,
|
||||
inventory_mapping=inventory_mapping_dict,
|
||||
tenant_id=tenant_id,
|
||||
user_id=UUID(current_user['user_id']),
|
||||
inventory_mapping=inventory_mapping_uuids
|
||||
filename=file.filename
|
||||
)
|
||||
|
||||
response = SalesImportResponse(
|
||||
import_job_id=str(result.import_job_id),
|
||||
status=result.status,
|
||||
processed_rows=result.processed_rows,
|
||||
successful_imports=result.successful_imports,
|
||||
failed_imports=result.failed_imports,
|
||||
errors=result.errors,
|
||||
warnings=result.warnings
|
||||
import_job_id="onboarding-" + str(tenant_id), # Generate a simple job ID
|
||||
status="completed" if result.success else "failed",
|
||||
processed_rows=result.import_details.records_processed,
|
||||
successful_imports=result.import_details.records_created,
|
||||
failed_imports=result.import_details.records_failed,
|
||||
errors=[error.get("message", str(error)) for error in result.import_details.errors],
|
||||
warnings=[warning.get("message", str(warning)) for warning in result.import_details.warnings]
|
||||
)
|
||||
|
||||
logger.info("Sales import complete",
|
||||
successful=result.successful_imports,
|
||||
failed=result.failed_imports,
|
||||
successful=result.import_details.records_created,
|
||||
failed=result.import_details.records_failed,
|
||||
filename=file.filename,
|
||||
tenant_id=tenant_id)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user