"""月报路由:CRUD + 提交 + AI 解析占位。""" from datetime import datetime, timezone from fastapi import APIRouter, Depends, HTTPException, Query, status from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession from app.core.database import get_db from app.core.dependencies import get_current_user from app.models.company import Company from app.models.report import MonthlyReport from app.models.user import User from app.schemas.common import ApiResponse, success from app.schemas.report import ( MonthlyReportCreate, MonthlyReportListResponse, MonthlyReportResponse, MonthlyReportUpdate, ) router = APIRouter(prefix="/reports", tags=["reports"]) async def _get_company_or_404(db: AsyncSession, company_id: str, tenant_id: str) -> Company: """验证企业属于当前租户。""" result = await db.execute( select(Company).where(Company.id == company_id, Company.tenant_id == tenant_id) ) company = result.scalar_one_or_none() if not company: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="企业不存在") return company @router.get("", response_model=ApiResponse[MonthlyReportListResponse]) async def list_reports( company_id: str | None = Query(default=None, description="按企业筛选"), page: int = Query(default=1, ge=1), page_size: int = Query(default=20, ge=1, le=100), db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user), ): """获取月报列表。""" # 获取当前租户的企业 ID 集合 company_query = select(Company.id).where(Company.tenant_id == user.tenant_id) if company_id: company_query = company_query.where(Company.id == company_id) query = select(MonthlyReport).where( MonthlyReport.company_id.in_(company_query) ) count_query = select(func.count()).select_from(query.subquery()) total_result = await db.execute(count_query) total = total_result.scalar_one() offset = (page - 1) * page_size query = query.order_by(MonthlyReport.created_at.desc()).offset(offset).limit(page_size) result = await db.execute(query) reports = result.scalars().all() return success( data=MonthlyReportListResponse( items=[MonthlyReportResponse.model_validate(r, from_attributes=True) for r in reports], total=total, page=page, page_size=page_size, ) ) @router.get("/{report_id}", response_model=ApiResponse[MonthlyReportResponse]) async def get_report( report_id: str, db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user), ): """获取月报详情。""" result = await db.execute( select(MonthlyReport) .join(Company, MonthlyReport.company_id == Company.id) .where(MonthlyReport.id == report_id, Company.tenant_id == user.tenant_id) ) report = result.scalar_one_or_none() if not report: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="月报不存在") return success(data=MonthlyReportResponse.model_validate(report, from_attributes=True)) @router.post("", response_model=ApiResponse[MonthlyReportResponse], status_code=status.HTTP_201_CREATED) async def create_report( req: MonthlyReportCreate, db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user), ): """创建月报。""" await _get_company_or_404(db, req.company_id, user.tenant_id) # 检查同企业同年月是否已有月报 existing = await db.execute( select(MonthlyReport).where( MonthlyReport.company_id == req.company_id, MonthlyReport.period_year == req.period_year, MonthlyReport.period_month == req.period_month, ) ) if existing.scalar_one_or_none(): raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail=f"{req.period_year}年{req.period_month}月月报已存在", ) report = MonthlyReport( company_id=req.company_id, period_year=req.period_year, period_month=req.period_month, raw_content=req.raw_content, status="draft", ) db.add(report) await db.flush() return success( data=MonthlyReportResponse.model_validate(report, from_attributes=True), message="创建成功", ) @router.put("/{report_id}", response_model=ApiResponse[MonthlyReportResponse]) async def update_report( report_id: str, req: MonthlyReportUpdate, db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user), ): """更新月报。""" result = await db.execute( select(MonthlyReport) .join(Company, MonthlyReport.company_id == Company.id) .where(MonthlyReport.id == report_id, Company.tenant_id == user.tenant_id) ) report = result.scalar_one_or_none() if not report: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="月报不存在") update_data = req.model_dump(exclude_unset=True) for key, value in update_data.items(): setattr(report, key, value) await db.flush() return success( data=MonthlyReportResponse.model_validate(report, from_attributes=True), message="更新成功", ) @router.post("/{report_id}/submit", response_model=ApiResponse[MonthlyReportResponse]) async def submit_report( report_id: str, db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user), ): """提交月报(状态从 draft → submitted)。""" result = await db.execute( select(MonthlyReport) .join(Company, MonthlyReport.company_id == Company.id) .where(MonthlyReport.id == report_id, Company.tenant_id == user.tenant_id) ) report = result.scalar_one_or_none() if not report: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="月报不存在") if report.status != "draft": raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"当前状态为 {report.status},无法提交", ) report.status = "submitted" report.submitted_by = user.id report.submitted_at = datetime.now(timezone.utc) await db.flush() return success( data=MonthlyReportResponse.model_validate(report, from_attributes=True), message="提交成功", ) @router.delete("/{report_id}", response_model=ApiResponse[None]) async def delete_report( report_id: str, db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user), ): """删除月报。""" result = await db.execute( select(MonthlyReport) .join(Company, MonthlyReport.company_id == Company.id) .where(MonthlyReport.id == report_id, Company.tenant_id == user.tenant_id) ) report = result.scalar_one_or_none() if not report: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="月报不存在") await db.delete(report) return success(message="删除成功")