# Quick Start: Implementing Remaining Service Deletions

## Overview

**Time to complete per service:** 30-45 minutes
**Remaining services:** 3 (POS, External, Alert Processor)
**Pattern:** Copy → Customize → Test

---

## Step-by-Step Template

### 1. Create Deletion Service File

**Location:** `services/{service}/app/services/tenant_deletion_service.py`

**Template:**

```python
"""
{Service} Service - Tenant Data Deletion
Handles deletion of all {service}-related data for a tenant
"""
from typing import Dict
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, delete, func
import structlog

from shared.services.tenant_deletion import BaseTenantDataDeletionService, TenantDataDeletionResult

logger = structlog.get_logger()


class {Service}TenantDeletionService(BaseTenantDataDeletionService):
    """Service for deleting all {service}-related data for a tenant"""

    def __init__(self, db_session: AsyncSession):
        super().__init__("{service}-service")
        self.db = db_session

    async def get_tenant_data_preview(self, tenant_id: str) -> Dict[str, int]:
        """Get counts of what would be deleted"""

        try:
            preview = {}

            # Import models here to avoid circular imports
            from app.models.{model_file} import Model1, Model2

            # Count each model type
            count1 = await self.db.scalar(
                select(func.count(Model1.id)).where(Model1.tenant_id == tenant_id)
            )
            preview["model1_plural"] = count1 or 0

            # Repeat for each model...

            return preview

        except Exception as e:
            logger.error("Error getting deletion preview",
                        tenant_id=tenant_id,
                        error=str(e))
            return {}

    async def delete_tenant_data(self, tenant_id: str) -> TenantDataDeletionResult:
        """Delete all data for a tenant"""

        result = TenantDataDeletionResult(tenant_id, self.service_name)

        try:
            # Import models here
            from app.models.{model_file} import Model1, Model2

            # Delete in reverse dependency order (children first, then parents)

            # Child models first
            try:
                child_delete = await self.db.execute(
                    delete(ChildModel).where(ChildModel.tenant_id == tenant_id)
                )
                result.add_deleted_items("child_models", child_delete.rowcount)
            except Exception as e:
                logger.error("Error deleting child models",
                           tenant_id=tenant_id,
                           error=str(e))
                result.add_error(f"Child model deletion: {str(e)}")

            # Parent models last
            try:
                parent_delete = await self.db.execute(
                    delete(ParentModel).where(ParentModel.tenant_id == tenant_id)
                )
                result.add_deleted_items("parent_models", parent_delete.rowcount)

                logger.info("Deleted parent models for tenant",
                           tenant_id=tenant_id,
                           count=parent_delete.rowcount)
            except Exception as e:
                logger.error("Error deleting parent models",
                           tenant_id=tenant_id,
                           error=str(e))
                result.add_error(f"Parent model deletion: {str(e)}")

            # Commit all deletions
            await self.db.commit()

            logger.info("Tenant data deletion completed",
                       tenant_id=tenant_id,
                       deleted_counts=result.deleted_counts)

        except Exception as e:
            logger.error("Fatal error during tenant data deletion",
                        tenant_id=tenant_id,
                        error=str(e))
            await self.db.rollback()
            result.add_error(f"Fatal error: {str(e)}")

        return result
```

### 2. Add API Endpoints

**Location:** `services/{service}/app/api/{main_router}.py`

**Add at end of file:**

```python
# ===== Tenant Data Deletion Endpoints =====

@router.delete("/tenant/{tenant_id}")
async def delete_tenant_data(
    tenant_id: str,
    current_user: dict = Depends(get_current_user_dep),
    db: AsyncSession = Depends(get_db)
):
    """
    Delete all {service}-related data for a tenant
    Only accessible by internal services (called during tenant deletion)
    """

    logger.info(f"Tenant data deletion request received for tenant: {tenant_id}")

    # Only allow internal service calls
    if current_user.get("type") != "service":
        raise HTTPException(
            status_code=403,
            detail="This endpoint is only accessible to internal services"
        )

    try:
        from app.services.tenant_deletion_service import {Service}TenantDeletionService

        deletion_service = {Service}TenantDeletionService(db)
        result = await deletion_service.safe_delete_tenant_data(tenant_id)

        return {
            "message": "Tenant data deletion completed in {service}-service",
            "summary": result.to_dict()
        }

    except Exception as e:
        logger.error(f"Tenant data deletion failed for {tenant_id}: {e}")
        raise HTTPException(
            status_code=500,
            detail=f"Failed to delete tenant data: {str(e)}"
        )


@router.get("/tenant/{tenant_id}/deletion-preview")
async def preview_tenant_data_deletion(
    tenant_id: str,
    current_user: dict = Depends(get_current_user_dep),
    db: AsyncSession = Depends(get_db)
):
    """
    Preview what data would be deleted for a tenant (dry-run)
    Accessible by internal services and tenant admins
    """

    # Allow internal services and admins
    is_service = current_user.get("type") == "service"
    is_admin = current_user.get("role") in ["owner", "admin"]

    if not (is_service or is_admin):
        raise HTTPException(
            status_code=403,
            detail="Insufficient permissions"
        )

    try:
        from app.services.tenant_deletion_service import {Service}TenantDeletionService

        deletion_service = {Service}TenantDeletionService(db)
        preview = await deletion_service.get_tenant_data_preview(tenant_id)

        return {
            "tenant_id": tenant_id,
            "service": "{service}-service",
            "data_counts": preview,
            "total_items": sum(preview.values())
        }

    except Exception as e:
        logger.error(f"Deletion preview failed for {tenant_id}: {e}")
        raise HTTPException(
            status_code=500,
            detail=f"Failed to get deletion preview: {str(e)}"
        )
```

---

## Remaining Services

### 1. POS Service

**Models to delete:**
- POSConfiguration
- POSTransaction
- POSSession
- POSDevice (if exists)

**Deletion order:**
1. POSTransaction (child)
2. POSSession (child)
3. POSDevice (if exists)
4. POSConfiguration (parent)

**Estimated time:** 30 minutes

### 2. External Service

**Models to delete:**
- ExternalDataCache
- APIKeyUsage
- ExternalAPILog (if exists)

**Deletion order:**
1. ExternalAPILog (if exists)
2. APIKeyUsage
3. ExternalDataCache

**Estimated time:** 30 minutes

### 3. Alert Processor Service

**Models to delete:**
- Alert
- AlertRule
- AlertHistory
- AlertNotification (if exists)

**Deletion order:**
1. AlertNotification (if exists, child)
2. AlertHistory (child)
3. Alert (child of AlertRule)
4. AlertRule (parent)

**Estimated time:** 30 minutes

---

## Testing Checklist

### Manual Testing (for each service):

```bash
# 1. Start the service
docker-compose up {service}-service

# 2. Test deletion preview (should return counts)
curl -X GET "http://localhost:8000/api/v1/{service}/tenant/{tenant_id}/deletion-preview" \
  -H "Authorization: Bearer {token}" \
  -H "X-Internal-Service: auth-service"

# 3. Test actual deletion
curl -X DELETE "http://localhost:8000/api/v1/{service}/tenant/{tenant_id}" \
  -H "Authorization: Bearer {token}" \
  -H "X-Internal-Service: auth-service"

# 4. Verify data is deleted
# Check database: SELECT COUNT(*) FROM {table} WHERE tenant_id = '{tenant_id}';
# Should return 0 for all tables
```

### Integration Testing:

```python
# Test via orchestrator
from services.auth.app.services.deletion_orchestrator import DeletionOrchestrator

orchestrator = DeletionOrchestrator()
job = await orchestrator.orchestrate_tenant_deletion(
    tenant_id="test-tenant-123",
    tenant_name="Test Bakery"
)

# Check results
print(job.to_dict())
# Should show:
# - services_completed: 12/12
# - services_failed: 0
# - total_items_deleted: > 0
```

---

## Common Patterns

### Pattern 1: Simple Service (1-2 models)

**Example:** Sales, External

```python
# Just delete the main model(s)
sales_delete = await self.db.execute(
    delete(SalesData).where(SalesData.tenant_id == tenant_id)
)
result.add_deleted_items("sales_records", sales_delete.rowcount)
```

### Pattern 2: Parent-Child (CASCADE)

**Example:** Orders, Recipes

```python
# Delete parent, CASCADE handles children
order_delete = await self.db.execute(
    delete(Order).where(Order.tenant_id == tenant_id)
)
# order_items, order_status_history deleted via CASCADE
result.add_deleted_items("orders", order_delete.rowcount)
result.add_deleted_items("order_items", preview["order_items"])  # From preview
```

### Pattern 3: Multiple Independent Models

**Example:** Inventory, Production

```python
# Delete each independently
for Model in [InventoryItem, InventoryTransaction, StockAlert]:
    try:
        deleted = await self.db.execute(
            delete(Model).where(Model.tenant_id == tenant_id)
        )
        result.add_deleted_items(model_name, deleted.rowcount)
    except Exception as e:
        result.add_error(f"{model_name}: {str(e)}")
```

### Pattern 4: Complex Dependencies

**Example:** Suppliers

```python
# Delete in specific order
# 1. Children first
poi_delete = await self.db.execute(
    delete(PurchaseOrderItem)
    .where(PurchaseOrderItem.purchase_order_id.in_(
        select(PurchaseOrder.id).where(PurchaseOrder.tenant_id == tenant_id)
    ))
)

# 2. Then intermediate
po_delete = await self.db.execute(
    delete(PurchaseOrder).where(PurchaseOrder.tenant_id == tenant_id)
)

# 3. Finally parent
supplier_delete = await self.db.execute(
    delete(Supplier).where(Supplier.tenant_id == tenant_id)
)
```

---

## Troubleshooting

### Issue: "ModuleNotFoundError: No module named 'shared.services.tenant_deletion'"

**Solution:** Ensure shared module is in PYTHONPATH:
```python
# Add to service's __init__.py or main.py
import sys
sys.path.insert(0, "/path/to/services/shared")
```

### Issue: "Table doesn't exist"

**Solution:** Wrap in try-except:
```python
try:
    count = await self.db.scalar(select(func.count(Model.id))...)
    preview["models"] = count or 0
except Exception:
    preview["models"] = 0  # Table doesn't exist, ignore
```

### Issue: "Foreign key constraint violation"

**Solution:** Delete in correct order (children before parents):
```python
# Wrong order:
await delete(Parent).where(...)  # Fails!
await delete(Child).where(...)

# Correct order:
await delete(Child).where(...)
await delete(Parent).where(...)  # Success!
```

### Issue: "Service timeout"

**Solution:** Increase timeout in orchestrator or implement chunked deletion:
```python
# In deletion_orchestrator.py, change:
async with httpx.AsyncClient(timeout=60.0) as client:
# To:
async with httpx.AsyncClient(timeout=300.0) as client:  # 5 minutes
```

---

## Performance Tips

### 1. Batch Deletes for Large Datasets

```python
# Instead of:
for item in items:
    await self.db.delete(item)

# Use:
await self.db.execute(
    delete(Model).where(Model.tenant_id == tenant_id)
)
```

### 2. Use Indexes

Ensure `tenant_id` has an index on all tables:
```sql
CREATE INDEX idx_{table}_tenant_id ON {table}(tenant_id);
```

### 3. Disable Triggers Temporarily (for very large deletes)

```python
await self.db.execute(text("SET session_replication_role = replica"))
# ... do deletions ...
await self.db.execute(text("SET session_replication_role = DEFAULT"))
```

---

## Completion Checklist

- [ ] POS Service deletion service created
- [ ] POS Service API endpoints added
- [ ] POS Service manually tested
- [ ] External Service deletion service created
- [ ] External Service API endpoints added
- [ ] External Service manually tested
- [ ] Alert Processor deletion service created
- [ ] Alert Processor API endpoints added
- [ ] Alert Processor manually tested
- [ ] All services tested via orchestrator
- [ ] Load testing completed
- [ ] Documentation updated

---

## Next Steps After Completion

1. **Update DeletionOrchestrator** - Verify all endpoint URLs are correct
2. **Integration Testing** - Test complete tenant deletion end-to-end
3. **Performance Testing** - Test with large datasets
4. **Monitoring Setup** - Add Prometheus metrics
5. **Production Deployment** - Deploy with feature flag

**Total estimated time for all 3 services:** 1.5-2 hours

---

## Quick Reference: Completed Services

| Service | Status | Files | Lines |
|---------|--------|-------|-------|
| Tenant | ✅ | 2 API files + 1 service | 641 |
| Orders | ✅ | tenant_deletion_service.py + endpoints | 225 |
| Inventory | ✅ | tenant_deletion_service.py | 110 |
| Recipes | ✅ | tenant_deletion_service.py + endpoints | 217 |
| Sales | ✅ | tenant_deletion_service.py | 85 |
| Production | ✅ | tenant_deletion_service.py | 171 |
| Suppliers | ✅ | tenant_deletion_service.py | 195 |
| **POS** | ⏳ | - | - |
| **External** | ⏳ | - | - |
| **Alert Processor** | ⏳ | - | - |
| Forecasting | 🔄 | Needs refactor | - |
| Training | 🔄 | Needs refactor | - |
| Notification | 🔄 | Needs refactor | - |

**Legend:**
- ✅ Complete
- ⏳ Pending
- 🔄 Needs refactoring to standard pattern
