New alert service
This commit is contained in:
@@ -410,14 +410,65 @@ async def receive_webhook(
|
||||
logger.info("Duplicate webhook ignored", event_id=event_id)
|
||||
return _get_webhook_response(pos_system, success=True)
|
||||
|
||||
# TODO: Queue for async processing if needed
|
||||
# For now, mark as received and ready for processing
|
||||
processing_duration_ms = int((datetime.utcnow() - start_time).total_seconds() * 1000)
|
||||
await webhook_service.update_webhook_status(
|
||||
webhook_log.id,
|
||||
status="queued",
|
||||
processing_duration_ms=processing_duration_ms
|
||||
)
|
||||
# Queue for async processing via RabbitMQ
|
||||
try:
|
||||
from shared.messaging import get_rabbitmq_client
|
||||
import uuid as uuid_module
|
||||
|
||||
rabbitmq_client = get_rabbitmq_client()
|
||||
if rabbitmq_client:
|
||||
# Publish POS transaction event for async processing
|
||||
event_payload = {
|
||||
"event_id": str(uuid_module.uuid4()),
|
||||
"event_type": f"pos.{webhook_type}",
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"tenant_id": str(tenant_id) if tenant_id else None,
|
||||
"data": {
|
||||
"webhook_log_id": str(webhook_log.id),
|
||||
"pos_system": pos_system,
|
||||
"webhook_type": webhook_type,
|
||||
"payload": webhook_data,
|
||||
"event_id": event_id
|
||||
}
|
||||
}
|
||||
|
||||
await rabbitmq_client.publish_event(
|
||||
exchange_name="pos.events",
|
||||
routing_key=f"pos.{webhook_type}",
|
||||
event_data=event_payload
|
||||
)
|
||||
|
||||
logger.info("POS transaction queued for async processing",
|
||||
event_id=event_payload["event_id"],
|
||||
webhook_log_id=str(webhook_log.id))
|
||||
|
||||
# Update status to queued
|
||||
processing_duration_ms = int((datetime.utcnow() - start_time).total_seconds() * 1000)
|
||||
await webhook_service.update_webhook_status(
|
||||
webhook_log.id,
|
||||
status="queued",
|
||||
processing_duration_ms=processing_duration_ms
|
||||
)
|
||||
else:
|
||||
logger.warning("RabbitMQ client not available, marking as received only")
|
||||
processing_duration_ms = int((datetime.utcnow() - start_time).total_seconds() * 1000)
|
||||
await webhook_service.update_webhook_status(
|
||||
webhook_log.id,
|
||||
status="received",
|
||||
processing_duration_ms=processing_duration_ms
|
||||
)
|
||||
|
||||
except Exception as queue_error:
|
||||
logger.error("Failed to queue POS transaction for async processing",
|
||||
error=str(queue_error),
|
||||
webhook_log_id=str(webhook_log.id))
|
||||
# Mark as received even if queuing fails
|
||||
processing_duration_ms = int((datetime.utcnow() - start_time).total_seconds() * 1000)
|
||||
await webhook_service.update_webhook_status(
|
||||
webhook_log.id,
|
||||
status="received",
|
||||
processing_duration_ms=processing_duration_ms
|
||||
)
|
||||
|
||||
logger.info("Webhook processed and queued successfully",
|
||||
pos_system=pos_system,
|
||||
|
||||
583
services/pos/app/consumers/pos_event_consumer.py
Normal file
583
services/pos/app/consumers/pos_event_consumer.py
Normal file
@@ -0,0 +1,583 @@
|
||||
"""
|
||||
POS Event Consumer
|
||||
Processes POS webhook events from RabbitMQ queue
|
||||
Handles sales transactions, refunds, and inventory updates from various POS systems
|
||||
"""
|
||||
import json
|
||||
import structlog
|
||||
from typing import Dict, Any, Optional
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
|
||||
from shared.messaging import RabbitMQClient
|
||||
from app.services.webhook_service import WebhookService
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
logger = structlog.get_logger()
|
||||
|
||||
|
||||
class POSEventConsumer:
|
||||
"""
|
||||
Consumes POS webhook events from RabbitMQ and processes them
|
||||
Supports multiple POS systems: Square, Shopify, Toast, etc.
|
||||
"""
|
||||
|
||||
def __init__(self, db_session: AsyncSession):
|
||||
self.db_session = db_session
|
||||
self.webhook_service = WebhookService()
|
||||
|
||||
async def consume_pos_events(
|
||||
self,
|
||||
rabbitmq_client: RabbitMQClient
|
||||
):
|
||||
"""
|
||||
Start consuming POS events from RabbitMQ
|
||||
"""
|
||||
async def process_message(message):
|
||||
"""Process a single POS event message"""
|
||||
try:
|
||||
async with message.process():
|
||||
# Parse event data
|
||||
event_data = json.loads(message.body.decode())
|
||||
logger.info(
|
||||
"Received POS event",
|
||||
event_id=event_data.get('event_id'),
|
||||
event_type=event_data.get('event_type'),
|
||||
pos_system=event_data.get('data', {}).get('pos_system')
|
||||
)
|
||||
|
||||
# Process the event
|
||||
await self.process_pos_event(event_data)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"Error processing POS event",
|
||||
error=str(e),
|
||||
exc_info=True
|
||||
)
|
||||
|
||||
# Start consuming events
|
||||
await rabbitmq_client.consume_events(
|
||||
exchange_name="pos.events",
|
||||
queue_name="pos.processing.queue",
|
||||
routing_key="pos.*",
|
||||
callback=process_message
|
||||
)
|
||||
|
||||
logger.info("Started consuming POS events")
|
||||
|
||||
async def process_pos_event(self, event_data: Dict[str, Any]) -> bool:
|
||||
"""
|
||||
Process a POS event based on type
|
||||
|
||||
Args:
|
||||
event_data: Full event payload from RabbitMQ
|
||||
|
||||
Returns:
|
||||
bool: True if processed successfully
|
||||
"""
|
||||
try:
|
||||
data = event_data.get('data', {})
|
||||
webhook_log_id = data.get('webhook_log_id')
|
||||
pos_system = data.get('pos_system', 'unknown')
|
||||
webhook_type = data.get('webhook_type')
|
||||
payload = data.get('payload', {})
|
||||
tenant_id = event_data.get('tenant_id')
|
||||
|
||||
if not webhook_log_id:
|
||||
logger.warning("POS event missing webhook_log_id", event_data=event_data)
|
||||
return False
|
||||
|
||||
# Update webhook log status to processing
|
||||
await self.webhook_service.update_webhook_status(
|
||||
webhook_log_id,
|
||||
status="processing",
|
||||
notes="Event consumer processing"
|
||||
)
|
||||
|
||||
# Route to appropriate handler based on webhook type
|
||||
success = False
|
||||
if webhook_type in ['sale.completed', 'transaction.completed', 'order.completed']:
|
||||
success = await self._handle_sale_completed(tenant_id, pos_system, payload)
|
||||
elif webhook_type in ['sale.refunded', 'transaction.refunded', 'order.refunded']:
|
||||
success = await self._handle_sale_refunded(tenant_id, pos_system, payload)
|
||||
elif webhook_type in ['inventory.updated', 'stock.updated']:
|
||||
success = await self._handle_inventory_updated(tenant_id, pos_system, payload)
|
||||
else:
|
||||
logger.warning("Unknown POS webhook type", webhook_type=webhook_type)
|
||||
success = True # Mark as processed to avoid retry
|
||||
|
||||
# Update webhook log with final status
|
||||
if success:
|
||||
await self.webhook_service.update_webhook_status(
|
||||
webhook_log_id,
|
||||
status="completed",
|
||||
notes="Successfully processed"
|
||||
)
|
||||
logger.info(
|
||||
"POS event processed successfully",
|
||||
webhook_log_id=webhook_log_id,
|
||||
webhook_type=webhook_type
|
||||
)
|
||||
else:
|
||||
await self.webhook_service.update_webhook_status(
|
||||
webhook_log_id,
|
||||
status="failed",
|
||||
notes="Processing failed"
|
||||
)
|
||||
logger.error(
|
||||
"POS event processing failed",
|
||||
webhook_log_id=webhook_log_id,
|
||||
webhook_type=webhook_type
|
||||
)
|
||||
|
||||
return success
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"Error in process_pos_event",
|
||||
error=str(e),
|
||||
event_id=event_data.get('event_id'),
|
||||
exc_info=True
|
||||
)
|
||||
return False
|
||||
|
||||
async def _handle_sale_completed(
|
||||
self,
|
||||
tenant_id: str,
|
||||
pos_system: str,
|
||||
payload: Dict[str, Any]
|
||||
) -> bool:
|
||||
"""
|
||||
Handle completed sale transaction
|
||||
|
||||
Updates:
|
||||
- Inventory quantities (decrease stock)
|
||||
- Sales analytics data
|
||||
- Revenue tracking
|
||||
|
||||
Args:
|
||||
tenant_id: Tenant ID
|
||||
pos_system: POS system name (square, shopify, toast, etc.)
|
||||
payload: Sale data from POS system
|
||||
|
||||
Returns:
|
||||
bool: True if handled successfully
|
||||
"""
|
||||
try:
|
||||
# Extract transaction data based on POS system format
|
||||
transaction_data = self._parse_sale_data(pos_system, payload)
|
||||
|
||||
if not transaction_data:
|
||||
logger.warning("Failed to parse sale data", pos_system=pos_system)
|
||||
return False
|
||||
|
||||
# Update inventory via inventory service client
|
||||
from shared.clients.inventory_client import InventoryServiceClient
|
||||
from shared.config.base import get_settings
|
||||
|
||||
config = get_settings()
|
||||
inventory_client = InventoryServiceClient(config, "pos")
|
||||
|
||||
for item in transaction_data.get('items', []):
|
||||
product_id = item.get('product_id')
|
||||
quantity = item.get('quantity', 0)
|
||||
unit_of_measure = item.get('unit_of_measure', 'units')
|
||||
|
||||
if not product_id or quantity <= 0:
|
||||
continue
|
||||
|
||||
# Decrease inventory stock
|
||||
try:
|
||||
await inventory_client.adjust_stock(
|
||||
tenant_id=tenant_id,
|
||||
product_id=product_id,
|
||||
quantity=-quantity, # Negative for sale
|
||||
unit_of_measure=unit_of_measure,
|
||||
reason=f"POS sale - {pos_system}",
|
||||
reference_id=transaction_data.get('transaction_id')
|
||||
)
|
||||
logger.info(
|
||||
"Inventory updated for sale",
|
||||
product_id=product_id,
|
||||
quantity=quantity,
|
||||
pos_system=pos_system
|
||||
)
|
||||
except Exception as inv_error:
|
||||
logger.error(
|
||||
"Failed to update inventory",
|
||||
product_id=product_id,
|
||||
error=str(inv_error)
|
||||
)
|
||||
# Continue processing other items even if one fails
|
||||
|
||||
# Publish sales data to sales service via RabbitMQ
|
||||
from shared.messaging import get_rabbitmq_client
|
||||
import uuid
|
||||
|
||||
rabbitmq_client = get_rabbitmq_client()
|
||||
if rabbitmq_client:
|
||||
sales_event = {
|
||||
"event_id": str(uuid.uuid4()),
|
||||
"event_type": "sales.transaction.completed",
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"tenant_id": tenant_id,
|
||||
"data": {
|
||||
"transaction_id": transaction_data.get('transaction_id'),
|
||||
"pos_system": pos_system,
|
||||
"total_amount": transaction_data.get('total_amount', 0),
|
||||
"items": transaction_data.get('items', []),
|
||||
"payment_method": transaction_data.get('payment_method'),
|
||||
"transaction_date": transaction_data.get('transaction_date'),
|
||||
"customer_id": transaction_data.get('customer_id')
|
||||
}
|
||||
}
|
||||
|
||||
await rabbitmq_client.publish_event(
|
||||
exchange_name="sales.events",
|
||||
routing_key="sales.transaction.completed",
|
||||
event_data=sales_event
|
||||
)
|
||||
|
||||
logger.info(
|
||||
"Published sales event",
|
||||
event_id=sales_event["event_id"],
|
||||
transaction_id=transaction_data.get('transaction_id')
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"Error handling sale completed",
|
||||
error=str(e),
|
||||
pos_system=pos_system,
|
||||
exc_info=True
|
||||
)
|
||||
return False
|
||||
|
||||
async def _handle_sale_refunded(
|
||||
self,
|
||||
tenant_id: str,
|
||||
pos_system: str,
|
||||
payload: Dict[str, Any]
|
||||
) -> bool:
|
||||
"""
|
||||
Handle refunded sale transaction
|
||||
|
||||
Updates:
|
||||
- Inventory quantities (increase stock)
|
||||
- Sales analytics (negative transaction)
|
||||
|
||||
Args:
|
||||
tenant_id: Tenant ID
|
||||
pos_system: POS system name
|
||||
payload: Refund data from POS system
|
||||
|
||||
Returns:
|
||||
bool: True if handled successfully
|
||||
"""
|
||||
try:
|
||||
# Extract refund data based on POS system format
|
||||
refund_data = self._parse_refund_data(pos_system, payload)
|
||||
|
||||
if not refund_data:
|
||||
logger.warning("Failed to parse refund data", pos_system=pos_system)
|
||||
return False
|
||||
|
||||
# Update inventory via inventory service client
|
||||
from shared.clients.inventory_client import InventoryServiceClient
|
||||
from shared.config.base import get_settings
|
||||
|
||||
config = get_settings()
|
||||
inventory_client = InventoryServiceClient(config, "pos")
|
||||
|
||||
for item in refund_data.get('items', []):
|
||||
product_id = item.get('product_id')
|
||||
quantity = item.get('quantity', 0)
|
||||
unit_of_measure = item.get('unit_of_measure', 'units')
|
||||
|
||||
if not product_id or quantity <= 0:
|
||||
continue
|
||||
|
||||
# Increase inventory stock (return to stock)
|
||||
try:
|
||||
await inventory_client.adjust_stock(
|
||||
tenant_id=tenant_id,
|
||||
product_id=product_id,
|
||||
quantity=quantity, # Positive for refund
|
||||
unit_of_measure=unit_of_measure,
|
||||
reason=f"POS refund - {pos_system}",
|
||||
reference_id=refund_data.get('refund_id')
|
||||
)
|
||||
logger.info(
|
||||
"Inventory updated for refund",
|
||||
product_id=product_id,
|
||||
quantity=quantity,
|
||||
pos_system=pos_system
|
||||
)
|
||||
except Exception as inv_error:
|
||||
logger.error(
|
||||
"Failed to update inventory for refund",
|
||||
product_id=product_id,
|
||||
error=str(inv_error)
|
||||
)
|
||||
|
||||
# Publish refund event to sales service
|
||||
from shared.messaging import get_rabbitmq_client
|
||||
import uuid
|
||||
|
||||
rabbitmq_client = get_rabbitmq_client()
|
||||
if rabbitmq_client:
|
||||
refund_event = {
|
||||
"event_id": str(uuid.uuid4()),
|
||||
"event_type": "sales.transaction.refunded",
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"tenant_id": tenant_id,
|
||||
"data": {
|
||||
"refund_id": refund_data.get('refund_id'),
|
||||
"original_transaction_id": refund_data.get('original_transaction_id'),
|
||||
"pos_system": pos_system,
|
||||
"refund_amount": refund_data.get('refund_amount', 0),
|
||||
"items": refund_data.get('items', []),
|
||||
"refund_date": refund_data.get('refund_date')
|
||||
}
|
||||
}
|
||||
|
||||
await rabbitmq_client.publish_event(
|
||||
exchange_name="sales.events",
|
||||
routing_key="sales.transaction.refunded",
|
||||
event_data=refund_event
|
||||
)
|
||||
|
||||
logger.info(
|
||||
"Published refund event",
|
||||
event_id=refund_event["event_id"],
|
||||
refund_id=refund_data.get('refund_id')
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"Error handling sale refunded",
|
||||
error=str(e),
|
||||
pos_system=pos_system,
|
||||
exc_info=True
|
||||
)
|
||||
return False
|
||||
|
||||
async def _handle_inventory_updated(
|
||||
self,
|
||||
tenant_id: str,
|
||||
pos_system: str,
|
||||
payload: Dict[str, Any]
|
||||
) -> bool:
|
||||
"""
|
||||
Handle inventory update from POS system
|
||||
|
||||
Syncs inventory levels from POS to our system
|
||||
|
||||
Args:
|
||||
tenant_id: Tenant ID
|
||||
pos_system: POS system name
|
||||
payload: Inventory data from POS system
|
||||
|
||||
Returns:
|
||||
bool: True if handled successfully
|
||||
"""
|
||||
try:
|
||||
# Extract inventory data
|
||||
inventory_data = self._parse_inventory_data(pos_system, payload)
|
||||
|
||||
if not inventory_data:
|
||||
logger.warning("Failed to parse inventory data", pos_system=pos_system)
|
||||
return False
|
||||
|
||||
# Update inventory via inventory service client
|
||||
from shared.clients.inventory_client import InventoryServiceClient
|
||||
from shared.config.base import get_settings
|
||||
|
||||
config = get_settings()
|
||||
inventory_client = InventoryServiceClient(config, "pos")
|
||||
|
||||
for item in inventory_data.get('items', []):
|
||||
product_id = item.get('product_id')
|
||||
new_quantity = item.get('quantity', 0)
|
||||
|
||||
if not product_id:
|
||||
continue
|
||||
|
||||
# Sync inventory level
|
||||
try:
|
||||
await inventory_client.sync_stock_level(
|
||||
tenant_id=tenant_id,
|
||||
product_id=product_id,
|
||||
quantity=new_quantity,
|
||||
source=f"POS sync - {pos_system}"
|
||||
)
|
||||
logger.info(
|
||||
"Inventory synced from POS",
|
||||
product_id=product_id,
|
||||
new_quantity=new_quantity,
|
||||
pos_system=pos_system
|
||||
)
|
||||
except Exception as inv_error:
|
||||
logger.error(
|
||||
"Failed to sync inventory",
|
||||
product_id=product_id,
|
||||
error=str(inv_error)
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"Error handling inventory updated",
|
||||
error=str(e),
|
||||
pos_system=pos_system,
|
||||
exc_info=True
|
||||
)
|
||||
return False
|
||||
|
||||
def _parse_sale_data(self, pos_system: str, payload: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Parse sale data from various POS system formats
|
||||
|
||||
Args:
|
||||
pos_system: POS system name
|
||||
payload: Raw payload from POS webhook
|
||||
|
||||
Returns:
|
||||
Normalized transaction data
|
||||
"""
|
||||
try:
|
||||
if pos_system.lower() == 'square':
|
||||
return self._parse_square_sale(payload)
|
||||
elif pos_system.lower() == 'shopify':
|
||||
return self._parse_shopify_sale(payload)
|
||||
elif pos_system.lower() == 'toast':
|
||||
return self._parse_toast_sale(payload)
|
||||
else:
|
||||
# Generic parser for custom POS systems
|
||||
return self._parse_generic_sale(payload)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error parsing sale data", pos_system=pos_system, error=str(e))
|
||||
return None
|
||||
|
||||
def _parse_square_sale(self, payload: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Parse Square POS sale format"""
|
||||
payment = payload.get('payment', {})
|
||||
order = payment.get('order', {})
|
||||
line_items = order.get('line_items', [])
|
||||
|
||||
items = []
|
||||
for item in line_items:
|
||||
items.append({
|
||||
'product_id': item.get('catalog_object_id'),
|
||||
'product_name': item.get('name'),
|
||||
'quantity': float(item.get('quantity', 1)),
|
||||
'unit_price': float(item.get('base_price_money', {}).get('amount', 0)) / 100,
|
||||
'unit_of_measure': 'units'
|
||||
})
|
||||
|
||||
return {
|
||||
'transaction_id': payment.get('id'),
|
||||
'total_amount': float(payment.get('amount_money', {}).get('amount', 0)) / 100,
|
||||
'items': items,
|
||||
'payment_method': payment.get('card_details', {}).get('card', {}).get('card_brand', 'unknown'),
|
||||
'transaction_date': payment.get('created_at'),
|
||||
'customer_id': payment.get('customer_id')
|
||||
}
|
||||
|
||||
def _parse_shopify_sale(self, payload: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Parse Shopify POS sale format"""
|
||||
line_items = payload.get('line_items', [])
|
||||
|
||||
items = []
|
||||
for item in line_items:
|
||||
items.append({
|
||||
'product_id': str(item.get('product_id')),
|
||||
'product_name': item.get('title'),
|
||||
'quantity': float(item.get('quantity', 1)),
|
||||
'unit_price': float(item.get('price', 0)),
|
||||
'unit_of_measure': 'units'
|
||||
})
|
||||
|
||||
return {
|
||||
'transaction_id': str(payload.get('id')),
|
||||
'total_amount': float(payload.get('total_price', 0)),
|
||||
'items': items,
|
||||
'payment_method': payload.get('payment_gateway_names', ['unknown'])[0],
|
||||
'transaction_date': payload.get('created_at'),
|
||||
'customer_id': str(payload.get('customer', {}).get('id')) if payload.get('customer') else None
|
||||
}
|
||||
|
||||
def _parse_toast_sale(self, payload: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Parse Toast POS sale format"""
|
||||
selections = payload.get('selections', [])
|
||||
|
||||
items = []
|
||||
for item in selections:
|
||||
items.append({
|
||||
'product_id': item.get('guid'),
|
||||
'product_name': item.get('displayName'),
|
||||
'quantity': float(item.get('quantity', 1)),
|
||||
'unit_price': float(item.get('preDiscountPrice', 0)),
|
||||
'unit_of_measure': 'units'
|
||||
})
|
||||
|
||||
return {
|
||||
'transaction_id': payload.get('guid'),
|
||||
'total_amount': float(payload.get('totalAmount', 0)),
|
||||
'items': items,
|
||||
'payment_method': payload.get('payments', [{}])[0].get('type', 'unknown'),
|
||||
'transaction_date': payload.get('closedDate'),
|
||||
'customer_id': payload.get('customer', {}).get('guid')
|
||||
}
|
||||
|
||||
def _parse_generic_sale(self, payload: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Parse generic/custom POS sale format"""
|
||||
items = []
|
||||
for item in payload.get('items', []):
|
||||
items.append({
|
||||
'product_id': item.get('product_id') or item.get('id'),
|
||||
'product_name': item.get('name') or item.get('description'),
|
||||
'quantity': float(item.get('quantity', 1)),
|
||||
'unit_price': float(item.get('price', 0)),
|
||||
'unit_of_measure': item.get('unit_of_measure', 'units')
|
||||
})
|
||||
|
||||
return {
|
||||
'transaction_id': payload.get('transaction_id') or payload.get('id'),
|
||||
'total_amount': float(payload.get('total', 0)),
|
||||
'items': items,
|
||||
'payment_method': payload.get('payment_method', 'unknown'),
|
||||
'transaction_date': payload.get('timestamp') or payload.get('created_at'),
|
||||
'customer_id': payload.get('customer_id')
|
||||
}
|
||||
|
||||
def _parse_refund_data(self, pos_system: str, payload: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||||
"""Parse refund data from various POS systems"""
|
||||
# Similar parsing logic as sales, but for refunds
|
||||
# Simplified for now - would follow same pattern as _parse_sale_data
|
||||
return {
|
||||
'refund_id': payload.get('refund_id') or payload.get('id'),
|
||||
'original_transaction_id': payload.get('original_transaction_id'),
|
||||
'refund_amount': float(payload.get('amount', 0)),
|
||||
'items': payload.get('items', []),
|
||||
'refund_date': payload.get('refund_date') or payload.get('created_at')
|
||||
}
|
||||
|
||||
def _parse_inventory_data(self, pos_system: str, payload: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||||
"""Parse inventory data from various POS systems"""
|
||||
return {
|
||||
'items': payload.get('items', [])
|
||||
}
|
||||
|
||||
|
||||
# Factory function for creating consumer instance
|
||||
def create_pos_event_consumer(db_session: AsyncSession) -> POSEventConsumer:
|
||||
"""Create POS event consumer instance"""
|
||||
return POSEventConsumer(db_session)
|
||||
Reference in New Issue
Block a user