feat: add periodic practice reports

This commit is contained in:
2026-07-31 16:37:25 +08:00
parent 88ed92d78c
commit 99e23b33ec
16 changed files with 602 additions and 3 deletions

View File

@@ -7,6 +7,7 @@ from io import BytesIO
from fastapi import APIRouter, Depends, File, HTTPException, Query, UploadFile, status
from fastapi.responses import StreamingResponse
from openpyxl import Workbook, load_workbook
from pydantic import BaseModel
from pydantic import ValidationError
from sqlalchemy import extract, func, select
from sqlalchemy.orm import Session
@@ -26,6 +27,7 @@ from app.services.admin_service import OperationLogService
from app.services.entitlement_service import EntitlementService, entitlement_dict, view_from_plan
from app.services.growth_profile_service import GrowthProfileService, growth_profile_dict, topic_dict, topic_summary_dict
from app.services.help_card_service import help_card_dict
from app.services.periodic_report_service import PeriodicReportService, periodic_report_dict
from app.services.share_draft_service import share_draft_dict
from app.api.pagination import page_result
@@ -34,6 +36,12 @@ router = APIRouter()
STUDENT_TEMPLATE_HEADERS = ["手机号", "姓名", "昵称", "每日聊天额度", "状态", "有效期"]
class AdminGenerateReportRequest(BaseModel):
reportType: str
periodStart: datetime | None = None
periodEnd: datetime | None = None
@router.get("/user/list")
def list_users(
keyword: str = Query(default=""),
@@ -259,10 +267,49 @@ def user_operation_detail(
"recentTopics": _recent_topics(db, user.id),
"recentHelpCards": [help_card_dict(item) for item in _recent_help_cards(db, user.id)],
"recentShareDrafts": [share_draft_dict(item) for item in _recent_share_drafts(db, user.id)],
"recentReports": [periodic_report_dict(item) for item in PeriodicReportService.list_user_reports(db, user_id=user.id, limit=10)],
}
)
@router.get("/user/{user_id}/reports")
def user_reports(
user_id: int,
limit: int = Query(default=20, ge=1, le=50),
db: Session = Depends(get_db),
current_admin: Admin = Depends(get_current_admin),
) -> dict:
user = _get_user(db, user_id)
reports = PeriodicReportService.list_user_reports(db, user_id=user.id, limit=limit)
return api_success([periodic_report_dict(item) for item in reports])
@router.post("/user/{user_id}/reports/generate")
def generate_user_report(
user_id: int,
payload: AdminGenerateReportRequest,
db: Session = Depends(get_db),
current_admin: Admin = Depends(get_current_admin),
) -> dict:
user = _get_user(db, user_id)
if payload.reportType not in ("weekly", "monthly", "stage"):
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="不支持的报告类型")
try:
report = PeriodicReportService.generate_for_user(
db,
user=user,
report_type=payload.reportType, # type: ignore[arg-type]
period_start=payload.periodStart,
period_end=payload.periodEnd,
generated_by=f"admin:{current_admin.id}",
)
except ValueError as exc:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc
OperationLogService.write(db, admin_id=current_admin.id, module="user_report", action="generate", target_id=report.id)
db.commit()
return api_success(periodic_report_dict(report))
@router.put("/user/{user_id}")
def update_user(
user_id: int,

View File

@@ -10,6 +10,7 @@ from app.models.user import User
from app.schemas.user import UserProfile
from app.services.entitlement_service import EntitlementService, entitlement_dict
from app.services.growth_profile_service import GrowthProfileService, growth_profile_dict
from app.services.periodic_report_service import PeriodicReportService, periodic_report_dict
from app.services.topic_session_service import TopicSessionService
router = APIRouter()
@@ -53,3 +54,13 @@ def growth_profile(
],
}
)
@router.get("/periodic-report/list")
def periodic_reports(
limit: int = 20,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
) -> dict:
reports = PeriodicReportService.list_user_reports(db, user_id=current_user.id, limit=max(1, min(limit, 50)))
return api_success([periodic_report_dict(item) for item in reports])