deep-risk/backend/app/api/v1/endpoints/expense.py
2025-12-14 20:08:27 +08:00

308 lines
11 KiB
Python

"""
成本费用凭证API路由
"""
from typing import Any, List, Optional
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy import select, func, and_, or_
from sqlalchemy.ext.asyncio import AsyncSession
from loguru import logger
from app.database import get_async_session
from app.models.expense import Expense
from app.schemas.expense import (
ExpenseCreate,
ExpenseUpdate,
ExpenseResponse,
ExpenseListResponse,
)
router = APIRouter()
@router.get("", response_model=ExpenseListResponse)
async def list_expenses(
page: int = Query(1, ge=1, description="页码"),
size: int = Query(10, ge=1, le=100, description="每页数量"),
expense_id: str = Query(None, description="费用ID"),
expense_type: str = Query(None, description="费用类型"),
payee_name: str = Query(None, description="收款方名称"),
payment_status: str = Query(None, description="支付状态"),
db: AsyncSession = Depends(get_async_session),
):
"""
获取成本费用凭证列表(分页查询)
"""
logger.info(f"获取成本费用凭证列表: page={page}, size={size}")
# 构建查询
query = select(Expense)
# 添加过滤条件
conditions = []
if expense_id:
conditions.append(Expense.expense_id.ilike(f"%{expense_id}%"))
if expense_type:
conditions.append(Expense.expense_type == expense_type)
if payee_name:
conditions.append(Expense.payee_name.ilike(f"%{payee_name}%"))
if payment_status:
conditions.append(Expense.payment_status == payment_status)
if conditions:
query = query.where(and_(*conditions))
# 获取总数
count_query = select(func.count()).select_from(Expense)
if conditions:
count_query = count_query.where(and_(*conditions))
total_result = await db.execute(count_query)
total = total_result.scalar()
# 分页
query = query.offset((page - 1) * size).limit(size)
# 执行查询
result = await db.execute(query)
records = result.scalars().all()
# 转换为响应格式
response_records = []
for expense in records:
response_records.append({
"id": expense.id,
"expense_id": expense.expense_id,
"voucher_no": expense.voucher_no,
"expense_type": expense.expense_type,
"expense_category": expense.expense_category,
"payer_name": expense.payer_name,
"payer_account_no": expense.payer_account_no,
"payee_name": expense.payee_name,
"payee_account_no": expense.payee_account_no,
"payee_bank_name": expense.payee_bank_name,
"expense_date": expense.expense_date.isoformat() if expense.expense_date else None,
"expense_amount": expense.expense_amount,
"tax_amount": expense.tax_amount,
"tax_rate": expense.tax_rate,
"payment_method": expense.payment_method,
"payment_status": expense.payment_status,
"accounting_status": expense.accounting_status,
"fiscal_year": expense.fiscal_year,
"fiscal_period": expense.fiscal_period,
"is_large_amount": expense.is_large_amount,
"is_cross_border": expense.is_cross_border,
"expense_description": expense.expense_description,
"related_contract_id": expense.related_contract_id,
})
return ExpenseListResponse(
records=response_records,
total=total,
page=page,
size=size,
)
@router.get("/{expense_id}", response_model=ExpenseResponse)
async def get_expense(expense_id: str, db: AsyncSession = Depends(get_async_session)):
"""
根据ID获取成本费用凭证详细信息
"""
logger.info(f"获取成本费用凭证详情: {expense_id}")
query = select(Expense).where(Expense.expense_id == expense_id)
result = await db.execute(query)
expense = result.scalar_one_or_none()
if not expense:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"成本费用凭证不存在: {expense_id}",
)
# 转换为响应格式
response = {
"id": expense.id,
"expense_id": expense.expense_id,
"voucher_no": expense.voucher_no,
"expense_type": expense.expense_type,
"expense_category": expense.expense_category,
"payer_name": expense.payer_name,
"payer_account_no": expense.payer_account_no,
"payee_name": expense.payee_name,
"payee_account_no": expense.payee_account_no,
"payee_bank_name": expense.payee_bank_name,
"expense_date": expense.expense_date.isoformat() if expense.expense_date else None,
"expense_amount": expense.expense_amount,
"tax_amount": expense.tax_amount,
"tax_rate": expense.tax_rate,
"payment_method": expense.payment_method,
"payment_status": expense.payment_status,
"accounting_status": expense.accounting_status,
"fiscal_year": expense.fiscal_year,
"fiscal_period": expense.fiscal_period,
"is_large_amount": expense.is_large_amount,
"is_cross_border": expense.is_cross_border,
"expense_description": expense.expense_description,
"related_contract_id": expense.related_contract_id,
}
return response
@router.post("", response_model=ExpenseResponse, status_code=status.HTTP_201_CREATED)
async def create_expense(expense: ExpenseCreate, db: AsyncSession = Depends(get_async_session)):
"""
创建新的成本费用凭证
"""
logger.info(f"创建成本费用凭证: {expense.voucher_no}")
# 生成费用ID
query = select(func.count()).select_from(Expense)
result = await db.execute(query)
count = result.scalar()
expense_id = f"EXP{2024}{(count + 1):06d}"
# 创建新费用
new_expense = Expense(
expense_id=expense_id,
voucher_no=expense.voucher_no,
expense_type=expense.expense_type,
expense_category=expense.expense_category,
payer_name=expense.payer_name,
payer_account_no=expense.payer_account_no,
payee_name=expense.payee_name,
payee_account_no=expense.payee_account_no,
payee_bank_name=expense.payee_bank_name,
expense_date=expense.expense_date,
expense_amount=expense.expense_amount,
tax_amount=expense.tax_amount,
tax_rate=expense.tax_rate,
payment_method=expense.payment_method,
payment_status=expense.payment_status,
accounting_status=expense.accounting_status,
fiscal_year=expense.fiscal_year,
fiscal_period=expense.fiscal_period,
is_large_amount=expense.is_large_amount,
is_cross_border=expense.is_cross_border,
expense_description=expense.expense_description,
related_contract_id=expense.related_contract_id,
)
db.add(new_expense)
await db.commit()
await db.refresh(new_expense)
# 转换为响应格式
response = {
"id": new_expense.id,
"expense_id": new_expense.expense_id,
"voucher_no": new_expense.voucher_no,
"expense_type": new_expense.expense_type,
"expense_category": new_expense.expense_category,
"payer_name": new_expense.payer_name,
"payer_account_no": new_expense.payer_account_no,
"payee_name": new_expense.payee_name,
"payee_account_no": new_expense.payee_account_no,
"payee_bank_name": new_expense.payee_bank_name,
"expense_date": new_expense.expense_date.isoformat() if new_expense.expense_date else None,
"expense_amount": new_expense.expense_amount,
"tax_amount": new_expense.tax_amount,
"tax_rate": new_expense.tax_rate,
"payment_method": new_expense.payment_method,
"payment_status": new_expense.payment_status,
"accounting_status": new_expense.accounting_status,
"fiscal_year": new_expense.fiscal_year,
"fiscal_period": new_expense.fiscal_period,
"is_large_amount": new_expense.is_large_amount,
"is_cross_border": new_expense.is_cross_border,
"expense_description": new_expense.expense_description,
"related_contract_id": new_expense.related_contract_id,
}
return response
@router.put("/{expense_id}", response_model=ExpenseResponse)
async def update_expense(
expense_id: str,
expense_update: ExpenseUpdate,
db: AsyncSession = Depends(get_async_session),
):
"""
更新成本费用凭证
"""
logger.info(f"更新成本费用凭证: {expense_id}")
query = select(Expense).where(Expense.expense_id == expense_id)
result = await db.execute(query)
expense = result.scalar_one_or_none()
if not expense:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"成本费用凭证不存在: {expense_id}",
)
# 更新字段
update_data = expense_update.model_dump(exclude_unset=True)
for key, value in update_data.items():
if hasattr(expense, key):
setattr(expense, key, value)
await db.commit()
await db.refresh(expense)
# 转换为响应格式
response = {
"id": expense.id,
"expense_id": expense.expense_id,
"voucher_no": expense.voucher_no,
"expense_type": expense.expense_type,
"expense_category": expense.expense_category,
"payer_name": expense.payer_name,
"payer_account_no": expense.payer_account_no,
"payee_name": expense.payee_name,
"payee_account_no": expense.payee_account_no,
"payee_bank_name": expense.payee_bank_name,
"expense_date": expense.expense_date.isoformat() if expense.expense_date else None,
"expense_amount": expense.expense_amount,
"tax_amount": expense.tax_amount,
"tax_rate": expense.tax_rate,
"payment_method": expense.payment_method,
"payment_status": expense.payment_status,
"accounting_status": expense.accounting_status,
"fiscal_year": expense.fiscal_year,
"fiscal_period": expense.fiscal_period,
"is_large_amount": expense.is_large_amount,
"is_cross_border": expense.is_cross_border,
"expense_description": expense.expense_description,
"related_contract_id": expense.related_contract_id,
}
return response
@router.delete("/{expense_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_expense(expense_id: str, db: AsyncSession = Depends(get_async_session)):
"""
删除成本费用凭证(软删除)
"""
logger.info(f"删除成本费用凭证: {expense_id}")
query = select(Expense).where(Expense.expense_id == expense_id)
result = await db.execute(query)
expense = result.scalar_one_or_none()
if not expense:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"成本费用凭证不存在: {expense_id}",
)
# 软删除 - 设置为已冲销状态
expense.accounting_status = "reversed"
await db.commit()
return None