135 lines
4.3 KiB
Python
135 lines
4.3 KiB
Python
from sqlalchemy import select, func
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy.orm import selectinload
|
|
|
|
from app.db.models import FundRecord, User
|
|
from app.schemas.fund import FundRecordCreate, FundRecordUpdate, FundStatistics
|
|
|
|
|
|
async def create_fund_record(
|
|
db: AsyncSession, class_id: int, recorder_id: int, data: FundRecordCreate
|
|
) -> FundRecord:
|
|
record = FundRecord(
|
|
class_id=class_id,
|
|
recorder_id=recorder_id,
|
|
type=data.type,
|
|
amount=data.amount,
|
|
category=data.category,
|
|
description=data.description,
|
|
image_urls=None,
|
|
record_date=data.record_date,
|
|
)
|
|
record.set_image_urls_list(data.image_urls or [])
|
|
db.add(record)
|
|
await db.commit()
|
|
await db.refresh(record)
|
|
return record
|
|
|
|
|
|
async def update_fund_record(
|
|
db: AsyncSession, record: FundRecord, data: FundRecordUpdate
|
|
) -> FundRecord:
|
|
values = data.model_dump(exclude_unset=True)
|
|
has_image_urls = "image_urls" in values
|
|
image_urls = values.pop("image_urls", None)
|
|
for field, value in values.items():
|
|
setattr(record, field, value)
|
|
if has_image_urls:
|
|
record.set_image_urls_list(image_urls or [])
|
|
await db.commit()
|
|
await db.refresh(record)
|
|
return record
|
|
|
|
|
|
async def delete_fund_record(db: AsyncSession, record: FundRecord):
|
|
await db.delete(record)
|
|
await db.commit()
|
|
|
|
|
|
async def get_fund_record_by_id(db: AsyncSession, record_id: int) -> FundRecord | None:
|
|
result = await db.execute(
|
|
select(FundRecord)
|
|
.options(selectinload(FundRecord.recorder))
|
|
.where(FundRecord.id == record_id)
|
|
)
|
|
return result.scalar_one_or_none()
|
|
|
|
|
|
async def list_fund_records(
|
|
db: AsyncSession,
|
|
class_id: int,
|
|
page: int = 1,
|
|
page_size: int = 20,
|
|
type: str | None = None,
|
|
category: str | None = None,
|
|
) -> tuple[list[FundRecord], int]:
|
|
query = select(FundRecord).where(FundRecord.class_id == class_id)
|
|
count_query = select(func.count(FundRecord.id)).where(FundRecord.class_id == class_id)
|
|
|
|
if type:
|
|
query = query.where(FundRecord.type == type)
|
|
count_query = count_query.where(FundRecord.type == type)
|
|
if category:
|
|
query = query.where(FundRecord.category == category)
|
|
count_query = count_query.where(FundRecord.category == category)
|
|
|
|
total_result = await db.execute(count_query)
|
|
total = total_result.scalar() or 0
|
|
|
|
result = await db.execute(
|
|
query.options(selectinload(FundRecord.recorder))
|
|
.order_by(FundRecord.record_date.desc(), FundRecord.created_at.desc())
|
|
.offset((page - 1) * page_size)
|
|
.limit(page_size)
|
|
)
|
|
records = list(result.scalars().all())
|
|
return records, total
|
|
|
|
|
|
async def get_fund_statistics(db: AsyncSession, class_id: int) -> FundStatistics:
|
|
# Total income
|
|
income_result = await db.execute(
|
|
select(func.coalesce(func.sum(FundRecord.amount), 0))
|
|
.where(FundRecord.class_id == class_id, FundRecord.type == "income")
|
|
)
|
|
total_income = float(income_result.scalar() or 0)
|
|
|
|
# Total expense
|
|
expense_result = await db.execute(
|
|
select(func.coalesce(func.sum(FundRecord.amount), 0))
|
|
.where(FundRecord.class_id == class_id, FundRecord.type == "expense")
|
|
)
|
|
total_expense = float(expense_result.scalar() or 0)
|
|
|
|
balance = total_income - total_expense
|
|
|
|
# Income by category
|
|
income_cat_result = await db.execute(
|
|
select(FundRecord.category, func.sum(FundRecord.amount))
|
|
.where(FundRecord.class_id == class_id, FundRecord.type == "income")
|
|
.group_by(FundRecord.category)
|
|
)
|
|
income_by_category = [
|
|
{"category": row[0], "amount": float(row[1] or 0)}
|
|
for row in income_cat_result.fetchall()
|
|
]
|
|
|
|
# Expense by category
|
|
expense_cat_result = await db.execute(
|
|
select(FundRecord.category, func.sum(FundRecord.amount))
|
|
.where(FundRecord.class_id == class_id, FundRecord.type == "expense")
|
|
.group_by(FundRecord.category)
|
|
)
|
|
expense_by_category = [
|
|
{"category": row[0], "amount": float(row[1] or 0)}
|
|
for row in expense_cat_result.fetchall()
|
|
]
|
|
|
|
return FundStatistics(
|
|
total_income=total_income,
|
|
total_expense=total_expense,
|
|
balance=balance,
|
|
income_by_category=income_by_category,
|
|
expense_by_category=expense_by_category,
|
|
)
|