Improve the sales import
This commit is contained in:
@@ -365,3 +365,93 @@ async def classify_products_batch(
|
||||
logger.error("Failed batch classification",
|
||||
error=str(e), products_count=len(request.products), tenant_id=tenant_id)
|
||||
raise HTTPException(status_code=500, detail=f"Batch classification failed: {str(e)}")
|
||||
|
||||
|
||||
class BatchProductResolutionRequest(BaseModel):
|
||||
"""Request for batch product resolution or creation"""
|
||||
products: List[Dict[str, Any]] = Field(..., description="Products to resolve or create")
|
||||
|
||||
|
||||
class BatchProductResolutionResponse(BaseModel):
|
||||
"""Response with product name to inventory ID mappings"""
|
||||
product_mappings: Dict[str, str] = Field(..., description="Product name to inventory product ID mapping")
|
||||
created_count: int = Field(..., description="Number of products created")
|
||||
resolved_count: int = Field(..., description="Number of existing products resolved")
|
||||
failed_count: int = Field(0, description="Number of products that failed")
|
||||
|
||||
|
||||
@router.post(
|
||||
route_builder.build_operations_route("resolve-or-create-products-batch"),
|
||||
response_model=BatchProductResolutionResponse
|
||||
)
|
||||
async def resolve_or_create_products_batch(
|
||||
request: BatchProductResolutionRequest,
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Resolve or create multiple products in a single optimized operation for sales import"""
|
||||
try:
|
||||
if not request.products:
|
||||
raise HTTPException(status_code=400, detail="No products provided")
|
||||
|
||||
service = InventoryService()
|
||||
product_mappings = {}
|
||||
created_count = 0
|
||||
resolved_count = 0
|
||||
failed_count = 0
|
||||
|
||||
for product_data in request.products:
|
||||
product_name = product_data.get('name', product_data.get('product_name', ''))
|
||||
if not product_name:
|
||||
failed_count += 1
|
||||
continue
|
||||
|
||||
try:
|
||||
existing = await service.search_ingredients_by_name(product_name, tenant_id, db)
|
||||
|
||||
if existing:
|
||||
product_mappings[product_name] = str(existing.id)
|
||||
resolved_count += 1
|
||||
logger.debug("Resolved existing product", product=product_name, tenant_id=tenant_id)
|
||||
else:
|
||||
category = product_data.get('category', 'general')
|
||||
ingredient_data = {
|
||||
'name': product_name,
|
||||
'type': 'finished_product',
|
||||
'unit': 'unit',
|
||||
'current_stock': 0,
|
||||
'reorder_point': 0,
|
||||
'cost_per_unit': 0,
|
||||
'category': category
|
||||
}
|
||||
|
||||
created = await service.create_ingredient_fast(ingredient_data, tenant_id, db)
|
||||
product_mappings[product_name] = str(created.id)
|
||||
created_count += 1
|
||||
logger.debug("Created new product", product=product_name, tenant_id=tenant_id)
|
||||
|
||||
except Exception as e:
|
||||
logger.warning("Failed to resolve/create product",
|
||||
product=product_name, error=str(e), tenant_id=tenant_id)
|
||||
failed_count += 1
|
||||
continue
|
||||
|
||||
logger.info("Batch product resolution complete",
|
||||
total=len(request.products),
|
||||
created=created_count,
|
||||
resolved=resolved_count,
|
||||
failed=failed_count,
|
||||
tenant_id=tenant_id)
|
||||
|
||||
return BatchProductResolutionResponse(
|
||||
product_mappings=product_mappings,
|
||||
created_count=created_count,
|
||||
resolved_count=resolved_count,
|
||||
failed_count=failed_count
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Batch product resolution failed",
|
||||
error=str(e), tenant_id=tenant_id)
|
||||
raise HTTPException(status_code=500, detail=f"Batch resolution failed: {str(e)}")
|
||||
|
||||
@@ -753,6 +753,67 @@ class InventoryService:
|
||||
)
|
||||
raise
|
||||
|
||||
# ===== BATCH OPERATIONS FOR SALES IMPORT =====
|
||||
|
||||
async def search_ingredients_by_name(
|
||||
self,
|
||||
product_name: str,
|
||||
tenant_id: UUID,
|
||||
db
|
||||
) -> Optional[Ingredient]:
|
||||
"""Search for an ingredient by name (case-insensitive exact match)"""
|
||||
try:
|
||||
repository = IngredientRepository(db)
|
||||
ingredients = await repository.search_ingredients(
|
||||
tenant_id=tenant_id,
|
||||
search_term=product_name,
|
||||
skip=0,
|
||||
limit=10
|
||||
)
|
||||
|
||||
product_name_lower = product_name.lower().strip()
|
||||
for ingredient in ingredients:
|
||||
if ingredient.name.lower().strip() == product_name_lower:
|
||||
return ingredient
|
||||
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.warning("Error searching ingredients by name",
|
||||
product_name=product_name, error=str(e), tenant_id=tenant_id)
|
||||
return None
|
||||
|
||||
async def create_ingredient_fast(
|
||||
self,
|
||||
ingredient_data: Dict[str, Any],
|
||||
tenant_id: UUID,
|
||||
db
|
||||
) -> Ingredient:
|
||||
"""Create ingredient without full validation for batch operations"""
|
||||
try:
|
||||
repository = IngredientRepository(db)
|
||||
|
||||
ingredient_create = IngredientCreate(
|
||||
name=ingredient_data.get('name'),
|
||||
product_type=ingredient_data.get('type', 'finished_product'),
|
||||
unit_of_measure=ingredient_data.get('unit', 'units'),
|
||||
low_stock_threshold=ingredient_data.get('current_stock', 0),
|
||||
reorder_point=max(ingredient_data.get('reorder_point', 1),
|
||||
ingredient_data.get('current_stock', 0) + 1),
|
||||
average_cost=ingredient_data.get('cost_per_unit', 0.0),
|
||||
ingredient_category=ingredient_data.get('category') if ingredient_data.get('type') == 'ingredient' else None,
|
||||
product_category=ingredient_data.get('category') if ingredient_data.get('type') == 'finished_product' else None
|
||||
)
|
||||
|
||||
ingredient = await repository.create_ingredient(ingredient_create, tenant_id)
|
||||
logger.debug("Created ingredient fast", ingredient_id=ingredient.id, name=ingredient.name)
|
||||
return ingredient
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to create ingredient fast",
|
||||
error=str(e), ingredient_data=ingredient_data, tenant_id=tenant_id)
|
||||
raise
|
||||
|
||||
# ===== PRIVATE HELPER METHODS =====
|
||||
|
||||
async def _validate_ingredient_data(self, ingredient_data: IngredientCreate, tenant_id: UUID):
|
||||
|
||||
Reference in New Issue
Block a user