feat(loyalty): Phase 6 — admin GDPR, bulk ops, point restore, cascade
Some checks failed
CI / ruff (push) Successful in 15s
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has started running

Admin operations for production management:

- GDPR anonymization: DELETE /admin/loyalty/cards/customer/{id}
  Nulls customer_id, deactivates cards, scrubs PII from transaction
  notes. Keeps aggregate data for reporting.
- Bulk deactivate: POST /admin/loyalty/merchants/{id}/cards/bulk/deactivate
  and POST /store/loyalty/cards/bulk/deactivate (merchant_owner only).
  Deactivates multiple cards with audit trail.
- Point restore: POST /admin/loyalty/cards/{id}/restore-points
  Creates ADMIN_ADJUSTMENT transaction with positive delta. Reuses
  existing adjust_points service method.
- Cascade restore: POST /admin/loyalty/merchants/{id}/restore-deleted
  Restores all soft-deleted programs and cards for a merchant.

Service methods: anonymize_cards_for_customer, bulk_deactivate_cards,
restore_deleted_cards, restore_deleted_programs.

342 tests pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-11 22:40:34 +02:00
parent 4c1608f78a
commit 8cd09f3f89
4 changed files with 354 additions and 0 deletions

View File

@@ -810,3 +810,39 @@ def adjust_points(
)
return PointsAdjustResponse(**result)
# =============================================================================
# Bulk Operations (Merchant Owner only)
# =============================================================================
@router.post("/cards/bulk/deactivate")
@rate_limit(max_requests=10, window_seconds=60)
def bulk_deactivate_cards(
request: Request,
data: dict,
current_user: User = Depends(get_current_store_api),
db: Session = Depends(get_db),
):
"""Bulk deactivate multiple loyalty cards (merchant_owner only)."""
if current_user.role != "merchant_owner":
raise AuthorizationException("Only merchant owners can bulk deactivate cards")
from app.modules.tenancy.services.store_service import store_service
store = store_service.get_store_by_id_optional(db, current_user.token_store_id)
if not store:
raise AuthorizationException("Store not found")
card_ids = data.get("card_ids", [])
reason = data.get("reason", "Merchant bulk deactivation")
count = card_service.bulk_deactivate_cards(
db,
card_ids=card_ids,
merchant_id=store.merchant_id,
reason=reason,
)
return {"cards_deactivated": count, "message": f"Deactivated {count} card(s)"}