Add POI feature and imporve the overall backend implementation
This commit is contained in:
@@ -237,3 +237,246 @@ class POSTransactionService:
|
||||
except Exception as e:
|
||||
logger.error("Failed to calculate transaction analytics", error=str(e), tenant_id=tenant_id)
|
||||
raise
|
||||
|
||||
async def sync_transaction_to_sales(
|
||||
self,
|
||||
transaction_id: UUID,
|
||||
tenant_id: UUID
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Sync a single POS transaction to the sales service
|
||||
|
||||
Args:
|
||||
transaction_id: Transaction UUID
|
||||
tenant_id: Tenant UUID
|
||||
|
||||
Returns:
|
||||
Dict with sync status and details
|
||||
"""
|
||||
try:
|
||||
from shared.clients.sales_client import SalesServiceClient
|
||||
from app.core.config import settings
|
||||
|
||||
async with get_db_transaction() as db:
|
||||
transaction_repo = POSTransactionRepository(db)
|
||||
items_repo = POSTransactionItemRepository(db)
|
||||
|
||||
# Get transaction
|
||||
transaction = await transaction_repo.get_by_id(transaction_id)
|
||||
if not transaction or transaction.tenant_id != tenant_id:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Transaction not found or unauthorized"
|
||||
}
|
||||
|
||||
# Check if already synced
|
||||
if transaction.is_synced_to_sales:
|
||||
logger.info("Transaction already synced to sales",
|
||||
transaction_id=transaction_id,
|
||||
sales_record_id=transaction.sales_record_id)
|
||||
return {
|
||||
"success": True,
|
||||
"already_synced": True,
|
||||
"sales_record_id": str(transaction.sales_record_id)
|
||||
}
|
||||
|
||||
# Get transaction items
|
||||
items = await items_repo.get_by_transaction_id(transaction_id)
|
||||
|
||||
# Initialize sales client
|
||||
sales_client = SalesServiceClient(settings, calling_service_name="pos")
|
||||
|
||||
# Create sales records for each item
|
||||
sales_record_ids = []
|
||||
failed_items = []
|
||||
|
||||
for item in items:
|
||||
try:
|
||||
sales_data = {
|
||||
"inventory_product_id": str(item.product_id) if item.product_id else None,
|
||||
"product_name": item.product_name,
|
||||
"product_category": "finished_product",
|
||||
"quantity_sold": float(item.quantity),
|
||||
"unit_price": float(item.unit_price),
|
||||
"total_amount": float(item.subtotal),
|
||||
"sale_date": transaction.transaction_date.strftime("%Y-%m-%d"),
|
||||
"sales_channel": "pos",
|
||||
"source": f"pos_sync_{transaction.pos_system}",
|
||||
"payment_method": transaction.payment_method or "unknown",
|
||||
"notes": f"POS Transaction: {transaction.external_transaction_id or transaction_id}"
|
||||
}
|
||||
|
||||
result = await sales_client.create_sales_record(
|
||||
tenant_id=str(tenant_id),
|
||||
sales_data=sales_data
|
||||
)
|
||||
|
||||
if result and result.get("id"):
|
||||
sales_record_ids.append(result["id"])
|
||||
logger.info("Synced item to sales",
|
||||
transaction_id=transaction_id,
|
||||
item_id=item.id,
|
||||
sales_record_id=result["id"])
|
||||
else:
|
||||
failed_items.append({
|
||||
"item_id": str(item.id),
|
||||
"product_name": item.product_name,
|
||||
"error": "No sales record ID returned"
|
||||
})
|
||||
|
||||
except Exception as item_error:
|
||||
logger.error("Failed to sync item to sales",
|
||||
error=str(item_error),
|
||||
transaction_id=transaction_id,
|
||||
item_id=item.id)
|
||||
failed_items.append({
|
||||
"item_id": str(item.id),
|
||||
"product_name": item.product_name,
|
||||
"error": str(item_error)
|
||||
})
|
||||
|
||||
# Update transaction sync status
|
||||
if sales_record_ids and len(failed_items) == 0:
|
||||
# Full success
|
||||
transaction.is_synced_to_sales = True
|
||||
transaction.sales_record_id = UUID(sales_record_ids[0]) # Store first record ID
|
||||
transaction.sync_completed_at = datetime.utcnow()
|
||||
await db.commit()
|
||||
|
||||
logger.info("Transaction fully synced to sales",
|
||||
transaction_id=transaction_id,
|
||||
items_synced=len(sales_record_ids))
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"items_synced": len(sales_record_ids),
|
||||
"sales_record_ids": sales_record_ids,
|
||||
"failed_items": []
|
||||
}
|
||||
|
||||
elif sales_record_ids and len(failed_items) > 0:
|
||||
# Partial success
|
||||
transaction.sync_attempted_at = datetime.utcnow()
|
||||
transaction.sync_error = f"Partial sync: {len(failed_items)} items failed"
|
||||
transaction.sync_retry_count = (transaction.sync_retry_count or 0) + 1
|
||||
await db.commit()
|
||||
|
||||
logger.warning("Transaction partially synced to sales",
|
||||
transaction_id=transaction_id,
|
||||
items_synced=len(sales_record_ids),
|
||||
items_failed=len(failed_items))
|
||||
|
||||
return {
|
||||
"success": False,
|
||||
"partial_success": True,
|
||||
"items_synced": len(sales_record_ids),
|
||||
"sales_record_ids": sales_record_ids,
|
||||
"failed_items": failed_items
|
||||
}
|
||||
|
||||
else:
|
||||
# Complete failure
|
||||
transaction.sync_attempted_at = datetime.utcnow()
|
||||
transaction.sync_error = "All items failed to sync"
|
||||
transaction.sync_retry_count = (transaction.sync_retry_count or 0) + 1
|
||||
await db.commit()
|
||||
|
||||
logger.error("Transaction sync failed completely",
|
||||
transaction_id=transaction_id,
|
||||
items_failed=len(failed_items))
|
||||
|
||||
return {
|
||||
"success": False,
|
||||
"items_synced": 0,
|
||||
"failed_items": failed_items
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to sync transaction to sales",
|
||||
error=str(e),
|
||||
transaction_id=transaction_id,
|
||||
tenant_id=tenant_id)
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
async def sync_unsynced_transactions(
|
||||
self,
|
||||
tenant_id: UUID,
|
||||
limit: int = 50
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Sync all unsynced transactions to the sales service
|
||||
|
||||
Args:
|
||||
tenant_id: Tenant UUID
|
||||
limit: Maximum number of transactions to sync in one batch
|
||||
|
||||
Returns:
|
||||
Dict with sync summary
|
||||
"""
|
||||
try:
|
||||
async with get_db_transaction() as db:
|
||||
repository = POSTransactionRepository(db)
|
||||
|
||||
# Get unsynced transactions
|
||||
unsynced_transactions = await repository.get_transactions_by_tenant(
|
||||
tenant_id=tenant_id,
|
||||
is_synced=False,
|
||||
status="completed", # Only sync completed transactions
|
||||
limit=limit
|
||||
)
|
||||
|
||||
if not unsynced_transactions:
|
||||
logger.info("No unsynced transactions found", tenant_id=tenant_id)
|
||||
return {
|
||||
"success": True,
|
||||
"total_transactions": 0,
|
||||
"synced": 0,
|
||||
"failed": 0
|
||||
}
|
||||
|
||||
synced_count = 0
|
||||
failed_count = 0
|
||||
results = []
|
||||
|
||||
for transaction in unsynced_transactions:
|
||||
result = await self.sync_transaction_to_sales(
|
||||
transaction.id,
|
||||
tenant_id
|
||||
)
|
||||
|
||||
if result.get("success"):
|
||||
synced_count += 1
|
||||
else:
|
||||
failed_count += 1
|
||||
|
||||
results.append({
|
||||
"transaction_id": str(transaction.id),
|
||||
"external_id": transaction.external_transaction_id,
|
||||
"result": result
|
||||
})
|
||||
|
||||
logger.info("Batch sync completed",
|
||||
tenant_id=tenant_id,
|
||||
total=len(unsynced_transactions),
|
||||
synced=synced_count,
|
||||
failed=failed_count)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"total_transactions": len(unsynced_transactions),
|
||||
"synced": synced_count,
|
||||
"failed": failed_count,
|
||||
"results": results
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to batch sync transactions",
|
||||
error=str(e),
|
||||
tenant_id=tenant_id)
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user