Add POI feature and imporve the overall backend implementation
This commit is contained in:
@@ -283,9 +283,19 @@ class SalesRepository(BaseRepository[SalesData, SalesDataCreate, SalesDataUpdate
|
||||
async def create_sales_records_bulk(
|
||||
self,
|
||||
sales_data_list: List[SalesDataCreate],
|
||||
tenant_id: UUID
|
||||
) -> int:
|
||||
"""Bulk insert sales records for performance optimization"""
|
||||
tenant_id: UUID,
|
||||
return_records: bool = False
|
||||
) -> int | List[SalesData]:
|
||||
"""Bulk insert sales records for performance optimization
|
||||
|
||||
Args:
|
||||
sales_data_list: List of sales data to create
|
||||
tenant_id: Tenant ID
|
||||
return_records: If True, returns list of created records instead of count
|
||||
|
||||
Returns:
|
||||
Either count of created records (int) or list of created records (List[SalesData])
|
||||
"""
|
||||
try:
|
||||
from uuid import uuid4
|
||||
|
||||
@@ -317,7 +327,8 @@ class SalesRepository(BaseRepository[SalesData, SalesDataCreate, SalesDataUpdate
|
||||
count=len(records),
|
||||
tenant_id=tenant_id
|
||||
)
|
||||
return len(records)
|
||||
|
||||
return records if return_records else len(records)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to bulk create sales records", error=str(e), tenant_id=tenant_id)
|
||||
|
||||
@@ -273,19 +273,180 @@ class SalesService:
|
||||
async def _post_create_actions(self, record: SalesData):
|
||||
"""Actions to perform after creating a sales record"""
|
||||
try:
|
||||
# Here you could:
|
||||
# Decrease inventory for the sale
|
||||
if record.inventory_product_id and record.quantity_sold and record.quantity_sold > 0:
|
||||
await self._decrease_inventory_for_sale(record)
|
||||
|
||||
# Here you could also:
|
||||
# - Send notifications
|
||||
# - Update analytics caches
|
||||
# - Trigger ML model updates
|
||||
# - Update inventory levels (future integration)
|
||||
|
||||
|
||||
logger.info("Post-create actions completed", record_id=record.id)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
# Don't fail the main operation for auxiliary actions
|
||||
logger.warning("Failed to execute post-create actions", error=str(e), record_id=record.id)
|
||||
|
||||
|
||||
|
||||
async def _decrease_inventory_for_sale(self, sales_record: SalesData) -> Optional[Dict[str, Any]]:
|
||||
"""Decrease inventory stock for a sales record"""
|
||||
try:
|
||||
if not sales_record.inventory_product_id:
|
||||
logger.debug("No inventory_product_id for sales record, skipping stock decrease",
|
||||
record_id=sales_record.id)
|
||||
return None
|
||||
|
||||
if not sales_record.quantity_sold or sales_record.quantity_sold <= 0:
|
||||
logger.debug("Invalid quantity for sales record, skipping stock decrease",
|
||||
record_id=sales_record.id, quantity=sales_record.quantity_sold)
|
||||
return None
|
||||
|
||||
consumption_data = {
|
||||
"ingredient_id": str(sales_record.inventory_product_id),
|
||||
"quantity": float(sales_record.quantity_sold),
|
||||
"reference_number": str(sales_record.id),
|
||||
"notes": f"Sales: {sales_record.product_name} - {sales_record.sales_channel}",
|
||||
"fifo": True # Use FIFO method for stock consumption
|
||||
}
|
||||
|
||||
result = await self.inventory_client._shared_client.consume_stock(
|
||||
consumption_data,
|
||||
str(sales_record.tenant_id)
|
||||
)
|
||||
|
||||
if result:
|
||||
logger.info("Inventory decreased for sale",
|
||||
sales_record_id=sales_record.id,
|
||||
inventory_product_id=sales_record.inventory_product_id,
|
||||
quantity=sales_record.quantity_sold,
|
||||
method="FIFO")
|
||||
|
||||
# Check if stock level is now low (after successful decrease)
|
||||
await self._check_low_stock_threshold(
|
||||
sales_record.tenant_id,
|
||||
sales_record.inventory_product_id,
|
||||
sales_record.product_name,
|
||||
result
|
||||
)
|
||||
else:
|
||||
logger.warning("Failed to decrease inventory for sale (no result)",
|
||||
sales_record_id=sales_record.id)
|
||||
|
||||
return result
|
||||
|
||||
except ValueError as e:
|
||||
# Insufficient stock - log warning but don't fail the sale
|
||||
logger.warning("Insufficient stock for sale",
|
||||
sales_record_id=sales_record.id,
|
||||
error=str(e),
|
||||
product_id=sales_record.inventory_product_id,
|
||||
quantity_requested=sales_record.quantity_sold)
|
||||
|
||||
# Trigger low stock alert
|
||||
await self._trigger_low_stock_alert(
|
||||
sales_record.tenant_id,
|
||||
sales_record.inventory_product_id,
|
||||
sales_record.product_name,
|
||||
error_message=str(e)
|
||||
)
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
# Other errors - log but don't fail the sale
|
||||
logger.error("Failed to decrease inventory for sale",
|
||||
sales_record_id=sales_record.id,
|
||||
error=str(e),
|
||||
product_id=sales_record.inventory_product_id)
|
||||
return None
|
||||
|
||||
async def _check_low_stock_threshold(
|
||||
self,
|
||||
tenant_id: UUID,
|
||||
product_id: UUID,
|
||||
product_name: str,
|
||||
consume_result: Dict[str, Any]
|
||||
):
|
||||
"""Check if stock level is below threshold after decrease"""
|
||||
try:
|
||||
# Get product details to check current stock and reorder point
|
||||
product = await self.inventory_client.get_product_by_id(product_id, tenant_id)
|
||||
|
||||
if not product:
|
||||
return
|
||||
|
||||
# Check if product has reorder point configured
|
||||
reorder_point = product.get("reorder_point", 0)
|
||||
current_stock = product.get("current_stock", 0)
|
||||
|
||||
# Trigger alert if stock is below reorder point
|
||||
if reorder_point > 0 and current_stock <= reorder_point:
|
||||
logger.warning("Stock below reorder point",
|
||||
product_id=product_id,
|
||||
product_name=product_name,
|
||||
current_stock=current_stock,
|
||||
reorder_point=reorder_point,
|
||||
tenant_id=tenant_id)
|
||||
|
||||
await self._trigger_low_stock_alert(
|
||||
tenant_id,
|
||||
product_id,
|
||||
product_name,
|
||||
current_stock=current_stock,
|
||||
reorder_point=reorder_point
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
# Don't fail the operation if alert fails
|
||||
logger.error("Failed to check low stock threshold",
|
||||
error=str(e),
|
||||
product_id=product_id)
|
||||
|
||||
async def _trigger_low_stock_alert(
|
||||
self,
|
||||
tenant_id: UUID,
|
||||
product_id: UUID,
|
||||
product_name: str,
|
||||
error_message: Optional[str] = None,
|
||||
current_stock: Optional[float] = None,
|
||||
reorder_point: Optional[float] = None
|
||||
):
|
||||
"""Trigger low stock alert notification"""
|
||||
try:
|
||||
# For now, just log the alert
|
||||
# In production, this could:
|
||||
# - Send email notification
|
||||
# - Create in-app notification
|
||||
# - Trigger webhook
|
||||
# - Create alert record in database
|
||||
# - Send to external alerting system (PagerDuty, Slack, etc.)
|
||||
|
||||
alert_data = {
|
||||
"type": "low_stock",
|
||||
"severity": "warning" if current_stock is not None else "critical",
|
||||
"tenant_id": str(tenant_id),
|
||||
"product_id": str(product_id),
|
||||
"product_name": product_name,
|
||||
"current_stock": current_stock,
|
||||
"reorder_point": reorder_point,
|
||||
"error_message": error_message,
|
||||
"timestamp": datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
logger.warning("LOW_STOCK_ALERT",
|
||||
**alert_data)
|
||||
|
||||
# TODO: Implement actual notification delivery
|
||||
# Examples:
|
||||
# - await notification_service.send_alert(alert_data)
|
||||
# - await event_publisher.publish('inventory.low_stock', alert_data)
|
||||
# - await email_service.send_low_stock_email(tenant_id, alert_data)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to trigger low stock alert",
|
||||
error=str(e),
|
||||
product_id=product_id)
|
||||
|
||||
|
||||
# New inventory integration methods
|
||||
async def search_inventory_products(self, search_term: str, tenant_id: UUID,
|
||||
product_type: Optional[str] = None) -> List[Dict[str, Any]]:
|
||||
|
||||
Reference in New Issue
Block a user