Add POI feature and imporve the overall backend implementation

This commit is contained in:
Urtzi Alfaro
2025-11-12 15:34:10 +01:00
parent e8096cd979
commit 5783c7ed05
173 changed files with 16862 additions and 9078 deletions

View File

@@ -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)

View File

@@ -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]]: