85 lines
2.6 KiB
Python
85 lines
2.6 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import datetime, timezone
|
|
from typing import Optional
|
|
from uuid import uuid4
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
from packages.adapters.sqlalchemy_impl.models import PointsTransactionModel
|
|
|
|
|
|
class SQLAlchemyPointsTransactionRepository:
|
|
"""积分流水仓储 — 只追加,不修改/删除."""
|
|
|
|
def __init__(self, session: Session):
|
|
self.session = session
|
|
|
|
def create(
|
|
self,
|
|
*,
|
|
user_id: str,
|
|
account_id: str,
|
|
type_: str,
|
|
source: str,
|
|
amount: int,
|
|
balance_after: int,
|
|
description: str = "",
|
|
ref_id: str = "",
|
|
) -> PointsTransactionModel:
|
|
model = PointsTransactionModel(
|
|
id=str(uuid4()),
|
|
user_id=user_id,
|
|
account_id=account_id,
|
|
type=type_,
|
|
source=source,
|
|
amount=amount,
|
|
balance_after=balance_after,
|
|
description=description,
|
|
ref_id=ref_id,
|
|
created_at=datetime.now(timezone.utc),
|
|
)
|
|
self.session.add(model)
|
|
self.session.flush()
|
|
return model
|
|
|
|
def get_by_id(self, tx_id: str) -> PointsTransactionModel | None:
|
|
return self.session.get(PointsTransactionModel, tx_id)
|
|
|
|
def exists_refund_for(self, original_tx_id: str) -> bool:
|
|
"""判断给定原 spend 流水是否已有 refund 流水(幂等检查)."""
|
|
from sqlalchemy import func
|
|
|
|
return bool(
|
|
self.session.query(func.count(PointsTransactionModel.id))
|
|
.filter(
|
|
PointsTransactionModel.type == "refund",
|
|
PointsTransactionModel.ref_id == original_tx_id,
|
|
)
|
|
.scalar()
|
|
)
|
|
|
|
def list_by_user(
|
|
self,
|
|
user_id: str,
|
|
*,
|
|
offset: int = 0,
|
|
limit: int = 20,
|
|
type_: Optional[str] = None,
|
|
source: Optional[str] = None,
|
|
start_date: Optional[datetime] = None,
|
|
end_date: Optional[datetime] = None,
|
|
) -> tuple[list[PointsTransactionModel], int]:
|
|
q = self.session.query(PointsTransactionModel).filter(PointsTransactionModel.user_id == user_id)
|
|
if type_:
|
|
q = q.filter(PointsTransactionModel.type == type_)
|
|
if source:
|
|
q = q.filter(PointsTransactionModel.source == source)
|
|
if start_date:
|
|
q = q.filter(PointsTransactionModel.created_at >= start_date)
|
|
if end_date:
|
|
q = q.filter(PointsTransactionModel.created_at <= end_date)
|
|
total = q.count()
|
|
items = q.order_by(PointsTransactionModel.created_at.desc()).offset(offset).limit(limit).all()
|
|
return items, total
|