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, )