Include Loyalty/Communication/Sports Center backends and registry updates alongside production nginx and compose wiring. Co-authored-by: Cursor <cursoragent@cursor.com>
214 lines
6.9 KiB
Python
214 lines
6.9 KiB
Python
"""Loyalty foundation repositories — Phase 7.0."""
|
|
from __future__ import annotations
|
|
|
|
from uuid import UUID
|
|
|
|
from sqlalchemy import select
|
|
|
|
from app.models.foundation import (
|
|
Campaign,
|
|
LoyaltyAuditLog,
|
|
LoyaltyProgram,
|
|
Member,
|
|
MembershipLifecycleEvent,
|
|
MembershipTier,
|
|
PointAccount,
|
|
Reward,
|
|
)
|
|
from app.repositories.base import TenantBaseRepository
|
|
|
|
|
|
class LoyaltyProgramRepository(TenantBaseRepository[LoyaltyProgram]):
|
|
model = LoyaltyProgram
|
|
|
|
async def get_by_code(self, tenant_id: UUID, code: str) -> LoyaltyProgram | None:
|
|
stmt = select(LoyaltyProgram).where(
|
|
LoyaltyProgram.tenant_id == tenant_id,
|
|
LoyaltyProgram.code == code,
|
|
LoyaltyProgram.is_deleted.is_(False),
|
|
)
|
|
result = await self.session.execute(stmt)
|
|
return result.scalar_one_or_none()
|
|
|
|
|
|
class MembershipTierRepository(TenantBaseRepository[MembershipTier]):
|
|
model = MembershipTier
|
|
|
|
async def get_by_code(
|
|
self, tenant_id: UUID, program_id: UUID, code: str
|
|
) -> MembershipTier | None:
|
|
stmt = select(MembershipTier).where(
|
|
MembershipTier.tenant_id == tenant_id,
|
|
MembershipTier.program_id == program_id,
|
|
MembershipTier.code == code,
|
|
MembershipTier.is_deleted.is_(False),
|
|
)
|
|
result = await self.session.execute(stmt)
|
|
return result.scalar_one_or_none()
|
|
|
|
async def list_by_program(
|
|
self, tenant_id: UUID, program_id: UUID, *, offset: int = 0, limit: int = 50
|
|
):
|
|
stmt = (
|
|
select(MembershipTier)
|
|
.where(
|
|
MembershipTier.tenant_id == tenant_id,
|
|
MembershipTier.program_id == program_id,
|
|
MembershipTier.is_deleted.is_(False),
|
|
)
|
|
.order_by(MembershipTier.rank.asc(), MembershipTier.created_at.desc())
|
|
.offset(offset)
|
|
.limit(limit)
|
|
)
|
|
result = await self.session.execute(stmt)
|
|
return result.scalars().all()
|
|
|
|
|
|
class MemberRepository(TenantBaseRepository[Member]):
|
|
model = Member
|
|
|
|
async def get_by_membership_number(
|
|
self, tenant_id: UUID, membership_number: str
|
|
) -> Member | None:
|
|
stmt = select(Member).where(
|
|
Member.tenant_id == tenant_id,
|
|
Member.membership_number == membership_number,
|
|
Member.is_deleted.is_(False),
|
|
)
|
|
result = await self.session.execute(stmt)
|
|
return result.scalar_one_or_none()
|
|
|
|
async def get_by_external_ref(
|
|
self, tenant_id: UUID, program_id: UUID, external_customer_ref: str
|
|
) -> Member | None:
|
|
stmt = select(Member).where(
|
|
Member.tenant_id == tenant_id,
|
|
Member.program_id == program_id,
|
|
Member.external_customer_ref == external_customer_ref,
|
|
Member.is_deleted.is_(False),
|
|
)
|
|
result = await self.session.execute(stmt)
|
|
return result.scalar_one_or_none()
|
|
|
|
async def count_blocking_for_program(
|
|
self, tenant_id: UUID, program_id: UUID, statuses: list
|
|
) -> int:
|
|
from sqlalchemy import func
|
|
|
|
stmt = (
|
|
select(func.count())
|
|
.select_from(Member)
|
|
.where(
|
|
Member.tenant_id == tenant_id,
|
|
Member.program_id == program_id,
|
|
Member.is_deleted.is_(False),
|
|
Member.status.in_(statuses),
|
|
)
|
|
)
|
|
result = await self.session.execute(stmt)
|
|
return int(result.scalar_one())
|
|
|
|
|
|
class PointAccountRepository(TenantBaseRepository[PointAccount]):
|
|
model = PointAccount
|
|
|
|
async def get_by_member(
|
|
self, tenant_id: UUID, member_id: UUID
|
|
) -> PointAccount | None:
|
|
stmt = select(PointAccount).where(
|
|
PointAccount.tenant_id == tenant_id,
|
|
PointAccount.member_id == member_id,
|
|
PointAccount.is_deleted.is_(False),
|
|
)
|
|
result = await self.session.execute(stmt)
|
|
return result.scalar_one_or_none()
|
|
|
|
async def get_by_account_number(
|
|
self, tenant_id: UUID, account_number: str
|
|
) -> PointAccount | None:
|
|
stmt = select(PointAccount).where(
|
|
PointAccount.tenant_id == tenant_id,
|
|
PointAccount.account_number == account_number,
|
|
PointAccount.is_deleted.is_(False),
|
|
)
|
|
result = await self.session.execute(stmt)
|
|
return result.scalar_one_or_none()
|
|
|
|
|
|
class RewardRepository(TenantBaseRepository[Reward]):
|
|
model = Reward
|
|
|
|
async def get_by_code(
|
|
self, tenant_id: UUID, program_id: UUID, code: str
|
|
) -> Reward | None:
|
|
stmt = select(Reward).where(
|
|
Reward.tenant_id == tenant_id,
|
|
Reward.program_id == program_id,
|
|
Reward.code == code,
|
|
Reward.is_deleted.is_(False),
|
|
)
|
|
result = await self.session.execute(stmt)
|
|
return result.scalar_one_or_none()
|
|
|
|
|
|
class CampaignRepository(TenantBaseRepository[Campaign]):
|
|
model = Campaign
|
|
|
|
async def get_by_code(
|
|
self, tenant_id: UUID, program_id: UUID, code: str
|
|
) -> Campaign | None:
|
|
stmt = select(Campaign).where(
|
|
Campaign.tenant_id == tenant_id,
|
|
Campaign.program_id == program_id,
|
|
Campaign.code == code,
|
|
Campaign.is_deleted.is_(False),
|
|
)
|
|
result = await self.session.execute(stmt)
|
|
return result.scalar_one_or_none()
|
|
|
|
|
|
class LoyaltyAuditLogRepository(TenantBaseRepository[LoyaltyAuditLog]):
|
|
model = LoyaltyAuditLog
|
|
|
|
async def list_for_entity(
|
|
self,
|
|
tenant_id: UUID,
|
|
entity_type: str,
|
|
entity_id: UUID,
|
|
*,
|
|
limit: int = 100,
|
|
):
|
|
stmt = (
|
|
select(LoyaltyAuditLog)
|
|
.where(
|
|
LoyaltyAuditLog.tenant_id == tenant_id,
|
|
LoyaltyAuditLog.entity_type == entity_type,
|
|
LoyaltyAuditLog.entity_id == entity_id,
|
|
)
|
|
.order_by(LoyaltyAuditLog.created_at.desc())
|
|
.limit(limit)
|
|
)
|
|
result = await self.session.execute(stmt)
|
|
return result.scalars().all()
|
|
|
|
|
|
class MembershipLifecycleEventRepository(
|
|
TenantBaseRepository[MembershipLifecycleEvent]
|
|
):
|
|
model = MembershipLifecycleEvent
|
|
|
|
async def list_for_member(
|
|
self, tenant_id: UUID, member_id: UUID, *, limit: int = 100
|
|
):
|
|
stmt = (
|
|
select(MembershipLifecycleEvent)
|
|
.where(
|
|
MembershipLifecycleEvent.tenant_id == tenant_id,
|
|
MembershipLifecycleEvent.member_id == member_id,
|
|
)
|
|
.order_by(MembershipLifecycleEvent.created_at.desc())
|
|
.limit(limit)
|
|
)
|
|
result = await self.session.execute(stmt)
|
|
return result.scalars().all()
|