demo seed change
This commit is contained in:
@@ -1,285 +0,0 @@
|
||||
"""
|
||||
Internal Demo API Endpoints for POS Service
|
||||
Used by demo_session service to clone data for virtual demo tenants
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Header
|
||||
from typing import Dict, Any
|
||||
from uuid import UUID
|
||||
import structlog
|
||||
import os
|
||||
|
||||
from app.core.database import get_db
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, delete, func
|
||||
from app.models.pos_config import POSConfiguration
|
||||
from app.models.pos_transaction import POSTransaction, POSTransactionItem
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
from typing import Optional
|
||||
|
||||
from app.core.config import settings
|
||||
|
||||
router = APIRouter()
|
||||
logger = structlog.get_logger()
|
||||
|
||||
|
||||
def verify_internal_api_key(x_internal_api_key: str = Header(...)):
|
||||
"""Verify internal API key for service-to-service communication"""
|
||||
if x_internal_api_key != settings.INTERNAL_API_KEY:
|
||||
raise HTTPException(status_code=403, detail="Invalid internal API key")
|
||||
return True
|
||||
|
||||
|
||||
@router.post("/internal/demo/clone")
|
||||
async def clone_demo_data(
|
||||
base_tenant_id: str,
|
||||
virtual_tenant_id: str,
|
||||
demo_account_type: str,
|
||||
session_id: Optional[str] = None,
|
||||
session_created_at: Optional[str] = None,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_: bool = Depends(verify_internal_api_key)
|
||||
):
|
||||
"""
|
||||
Clone POS demo data from base tenant to virtual tenant
|
||||
|
||||
This endpoint is called by the demo_session service during session initialization.
|
||||
It clones POS configurations and recent transactions.
|
||||
"""
|
||||
|
||||
start_time = datetime.now(timezone.utc)
|
||||
|
||||
# Parse session_created_at or fallback to now
|
||||
if session_created_at:
|
||||
try:
|
||||
session_time = datetime.fromisoformat(session_created_at.replace('Z', '+00:00'))
|
||||
except (ValueError, AttributeError) as e:
|
||||
logger.warning(
|
||||
"Invalid session_created_at format, using current time",
|
||||
session_created_at=session_created_at,
|
||||
error=str(e)
|
||||
)
|
||||
session_time = datetime.now(timezone.utc)
|
||||
else:
|
||||
logger.warning("session_created_at not provided, using current time")
|
||||
session_time = datetime.now(timezone.utc)
|
||||
|
||||
logger.info(
|
||||
"Starting POS data cloning with date adjustment",
|
||||
base_tenant_id=base_tenant_id,
|
||||
virtual_tenant_id=virtual_tenant_id,
|
||||
demo_account_type=demo_account_type,
|
||||
session_id=session_id,
|
||||
session_time=session_time.isoformat()
|
||||
)
|
||||
|
||||
try:
|
||||
base_uuid = uuid.UUID(base_tenant_id)
|
||||
virtual_uuid = uuid.UUID(virtual_tenant_id)
|
||||
|
||||
# Fetch base tenant POS configurations
|
||||
result = await db.execute(
|
||||
select(POSConfiguration).where(POSConfiguration.tenant_id == base_uuid)
|
||||
)
|
||||
base_configs = list(result.scalars().all())
|
||||
|
||||
configs_cloned = 0
|
||||
transactions_cloned = 0
|
||||
|
||||
# Clone each configuration
|
||||
for base_config in base_configs:
|
||||
# Create new config for virtual tenant
|
||||
new_config = POSConfiguration(
|
||||
id=uuid.uuid4(),
|
||||
tenant_id=virtual_uuid,
|
||||
pos_system=base_config.pos_system,
|
||||
provider_name=f"{base_config.provider_name} (Demo Session)",
|
||||
is_active=base_config.is_active,
|
||||
is_connected=base_config.is_connected,
|
||||
encrypted_credentials=base_config.encrypted_credentials,
|
||||
webhook_url=base_config.webhook_url,
|
||||
webhook_secret=base_config.webhook_secret,
|
||||
environment=base_config.environment,
|
||||
location_id=base_config.location_id,
|
||||
merchant_id=base_config.merchant_id,
|
||||
sync_enabled=base_config.sync_enabled,
|
||||
sync_interval_minutes=base_config.sync_interval_minutes,
|
||||
auto_sync_products=base_config.auto_sync_products,
|
||||
auto_sync_transactions=base_config.auto_sync_transactions,
|
||||
last_sync_at=base_config.last_sync_at,
|
||||
last_successful_sync_at=base_config.last_successful_sync_at,
|
||||
last_sync_status=base_config.last_sync_status,
|
||||
last_sync_message=base_config.last_sync_message,
|
||||
provider_settings=base_config.provider_settings,
|
||||
last_health_check_at=base_config.last_health_check_at,
|
||||
health_status=base_config.health_status,
|
||||
health_message=base_config.health_message,
|
||||
created_at=session_time,
|
||||
updated_at=session_time,
|
||||
notes=f"Cloned from base config {base_config.id} for demo session {session_id}"
|
||||
)
|
||||
|
||||
db.add(new_config)
|
||||
await db.flush()
|
||||
configs_cloned += 1
|
||||
|
||||
# Clone recent transactions for this config
|
||||
tx_result = await db.execute(
|
||||
select(POSTransaction)
|
||||
.where(POSTransaction.pos_config_id == base_config.id)
|
||||
.order_by(POSTransaction.transaction_date.desc())
|
||||
.limit(10) # Clone last 10 transactions
|
||||
)
|
||||
base_transactions = list(tx_result.scalars().all())
|
||||
|
||||
# Clone each transaction
|
||||
for base_tx in base_transactions:
|
||||
new_tx = POSTransaction(
|
||||
id=uuid.uuid4(),
|
||||
tenant_id=virtual_uuid,
|
||||
pos_config_id=new_config.id,
|
||||
pos_system=base_tx.pos_system,
|
||||
external_transaction_id=base_tx.external_transaction_id,
|
||||
external_order_id=base_tx.external_order_id,
|
||||
transaction_type=base_tx.transaction_type,
|
||||
status=base_tx.status,
|
||||
subtotal=base_tx.subtotal,
|
||||
tax_amount=base_tx.tax_amount,
|
||||
tip_amount=base_tx.tip_amount,
|
||||
discount_amount=base_tx.discount_amount,
|
||||
total_amount=base_tx.total_amount,
|
||||
currency=base_tx.currency,
|
||||
payment_method=base_tx.payment_method,
|
||||
payment_status=base_tx.payment_status,
|
||||
transaction_date=base_tx.transaction_date,
|
||||
pos_created_at=base_tx.pos_created_at,
|
||||
pos_updated_at=base_tx.pos_updated_at,
|
||||
location_id=base_tx.location_id,
|
||||
location_name=base_tx.location_name,
|
||||
staff_id=base_tx.staff_id,
|
||||
staff_name=base_tx.staff_name,
|
||||
customer_id=base_tx.customer_id,
|
||||
customer_email=base_tx.customer_email,
|
||||
customer_phone=base_tx.customer_phone,
|
||||
order_type=base_tx.order_type,
|
||||
table_number=base_tx.table_number,
|
||||
receipt_number=base_tx.receipt_number,
|
||||
is_synced_to_sales=base_tx.is_synced_to_sales,
|
||||
sales_record_id=base_tx.sales_record_id,
|
||||
sync_attempted_at=base_tx.sync_attempted_at,
|
||||
sync_completed_at=base_tx.sync_completed_at,
|
||||
sync_error=base_tx.sync_error,
|
||||
sync_retry_count=base_tx.sync_retry_count,
|
||||
raw_data=base_tx.raw_data,
|
||||
is_processed=base_tx.is_processed,
|
||||
processing_error=base_tx.processing_error,
|
||||
is_duplicate=base_tx.is_duplicate,
|
||||
duplicate_of=base_tx.duplicate_of,
|
||||
created_at=session_time,
|
||||
updated_at=session_time
|
||||
)
|
||||
|
||||
db.add(new_tx)
|
||||
await db.flush()
|
||||
transactions_cloned += 1
|
||||
|
||||
# Clone transaction items
|
||||
item_result = await db.execute(
|
||||
select(POSTransactionItem).where(POSTransactionItem.transaction_id == base_tx.id)
|
||||
)
|
||||
base_items = list(item_result.scalars().all())
|
||||
|
||||
for base_item in base_items:
|
||||
new_item = POSTransactionItem(
|
||||
id=uuid.uuid4(),
|
||||
transaction_id=new_tx.id,
|
||||
tenant_id=virtual_uuid,
|
||||
external_item_id=base_item.external_item_id,
|
||||
sku=base_item.sku,
|
||||
product_name=base_item.product_name,
|
||||
product_category=base_item.product_category,
|
||||
product_subcategory=base_item.product_subcategory,
|
||||
quantity=base_item.quantity,
|
||||
unit_price=base_item.unit_price,
|
||||
total_price=base_item.total_price,
|
||||
discount_amount=base_item.discount_amount,
|
||||
tax_amount=base_item.tax_amount,
|
||||
modifiers=base_item.modifiers,
|
||||
inventory_product_id=base_item.inventory_product_id,
|
||||
is_mapped_to_inventory=base_item.is_mapped_to_inventory,
|
||||
is_synced_to_sales=base_item.is_synced_to_sales,
|
||||
sync_error=base_item.sync_error,
|
||||
raw_data=base_item.raw_data,
|
||||
created_at=session_time,
|
||||
updated_at=session_time
|
||||
)
|
||||
|
||||
db.add(new_item)
|
||||
|
||||
await db.commit()
|
||||
|
||||
logger.info(
|
||||
"POS demo data cloned successfully",
|
||||
virtual_tenant_id=str(virtual_tenant_id),
|
||||
configs_cloned=configs_cloned,
|
||||
transactions_cloned=transactions_cloned
|
||||
)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"records_cloned": configs_cloned + transactions_cloned,
|
||||
"configs_cloned": configs_cloned,
|
||||
"transactions_cloned": transactions_cloned,
|
||||
"service": "pos"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to clone POS demo data", error=str(e), exc_info=True)
|
||||
await db.rollback()
|
||||
raise HTTPException(status_code=500, detail=f"Failed to clone POS demo data: {str(e)}")
|
||||
|
||||
|
||||
@router.delete("/internal/demo/tenant/{virtual_tenant_id}")
|
||||
async def delete_demo_data(
|
||||
virtual_tenant_id: str,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_: bool = Depends(verify_internal_api_key)
|
||||
):
|
||||
"""Delete all POS data for a virtual demo tenant"""
|
||||
logger.info("Deleting POS data for virtual tenant", virtual_tenant_id=virtual_tenant_id)
|
||||
start_time = datetime.now(timezone.utc)
|
||||
|
||||
try:
|
||||
virtual_uuid = uuid.UUID(virtual_tenant_id)
|
||||
|
||||
# Count records
|
||||
config_count = await db.scalar(select(func.count(POSConfiguration.id)).where(POSConfiguration.tenant_id == virtual_uuid))
|
||||
transaction_count = await db.scalar(select(func.count(POSTransaction.id)).where(POSTransaction.tenant_id == virtual_uuid))
|
||||
item_count = await db.scalar(select(func.count(POSTransactionItem.id)).where(POSTransactionItem.tenant_id == virtual_uuid))
|
||||
|
||||
# Delete in order (items -> transactions -> configs)
|
||||
await db.execute(delete(POSTransactionItem).where(POSTransactionItem.tenant_id == virtual_uuid))
|
||||
await db.execute(delete(POSTransaction).where(POSTransaction.tenant_id == virtual_uuid))
|
||||
await db.execute(delete(POSConfiguration).where(POSConfiguration.tenant_id == virtual_uuid))
|
||||
await db.commit()
|
||||
|
||||
duration_ms = int((datetime.now(timezone.utc) - start_time).total_seconds() * 1000)
|
||||
logger.info("POS data deleted successfully", virtual_tenant_id=virtual_tenant_id, duration_ms=duration_ms)
|
||||
|
||||
return {
|
||||
"service": "pos",
|
||||
"status": "deleted",
|
||||
"virtual_tenant_id": virtual_tenant_id,
|
||||
"records_deleted": {
|
||||
"configurations": config_count,
|
||||
"transactions": transaction_count,
|
||||
"items": item_count,
|
||||
"total": config_count + transaction_count + item_count
|
||||
},
|
||||
"duration_ms": duration_ms
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error("Failed to delete POS data", error=str(e), exc_info=True)
|
||||
await db.rollback()
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
Reference in New Issue
Block a user