[backend] fix: 修复require_admin错误码和路由顺序问题
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -118,6 +118,11 @@ async def require_admin(
|
||||
if current_user.role != "admin":
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="需要管理员权限",
|
||||
detail={
|
||||
"success": False,
|
||||
"message": "需要管理员权限",
|
||||
"data": None,
|
||||
"error_code": "3001",
|
||||
},
|
||||
)
|
||||
return current_user
|
||||
|
||||
Binary file not shown.
@@ -108,6 +108,83 @@ async def get_projects(
|
||||
}
|
||||
|
||||
|
||||
|
||||
@router.get("/statistics/basic", response_model=dict)
|
||||
async def get_statistics(
|
||||
engineering_type: Optional[str] = None,
|
||||
signing_date_start: Optional[date] = None,
|
||||
signing_date_end: Optional[date] = None,
|
||||
contract_amount_min: Optional[Decimal] = None,
|
||||
contract_amount_max: Optional[Decimal] = None,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
query = select(Project)
|
||||
|
||||
conditions = []
|
||||
if engineering_type:
|
||||
conditions.append(Project.engineering_type == engineering_type)
|
||||
if signing_date_start:
|
||||
conditions.append(Project.signing_date >= signing_date_start)
|
||||
if signing_date_end:
|
||||
conditions.append(Project.signing_date <= signing_date_end)
|
||||
if contract_amount_min:
|
||||
conditions.append(Project.contract_amount >= contract_amount_min)
|
||||
if contract_amount_max:
|
||||
conditions.append(Project.contract_amount <= contract_amount_max)
|
||||
|
||||
if conditions:
|
||||
query = query.where(and_(*conditions))
|
||||
|
||||
result = await db.execute(query)
|
||||
projects = result.scalars().all()
|
||||
|
||||
total_count = len(projects)
|
||||
total_contract_amount = sum(
|
||||
p.contract_amount or Decimal(0) for p in projects
|
||||
)
|
||||
total_receipt_amount = sum(
|
||||
p.actual_receipt_amount or Decimal(0) for p in projects
|
||||
)
|
||||
total_payment_amount = sum(
|
||||
p.actual_payment_amount or Decimal(0) for p in projects
|
||||
)
|
||||
|
||||
avg_receipt_rate = 0
|
||||
if total_contract_amount > 0:
|
||||
avg_receipt_rate = float(
|
||||
(total_receipt_amount / total_contract_amount) * 100
|
||||
)
|
||||
|
||||
avg_payment_rate = 0
|
||||
total_payable = sum(p.payable_amount or Decimal(0) for p in projects)
|
||||
if total_payable > 0:
|
||||
avg_payment_rate = float(
|
||||
(total_payment_amount / total_payable) * 100
|
||||
)
|
||||
|
||||
avg_progress = 0
|
||||
if total_count > 0:
|
||||
avg_progress = sum(
|
||||
p.cumulative_progress or Decimal(0) for p in projects
|
||||
) / total_count
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "统计成功",
|
||||
"data": {
|
||||
"total_count": total_count,
|
||||
"total_contract_amount": float(total_contract_amount),
|
||||
"total_receipt_amount": float(total_receipt_amount),
|
||||
"total_payment_amount": float(total_payment_amount),
|
||||
"avg_receipt_completion_rate": float(avg_receipt_rate),
|
||||
"avg_payment_completion_rate": float(avg_payment_rate),
|
||||
"avg_cumulative_progress": float(avg_progress),
|
||||
},
|
||||
"error_code": None,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/{project_id}", response_model=dict)
|
||||
async def get_project(
|
||||
project: Project = Depends(get_project_or_404),
|
||||
|
||||
@@ -0,0 +1,438 @@
|
||||
from fastapi import APIRouter, Depends, Query, HTTPException, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, func, and_, desc, asc
|
||||
from config.database import get_db
|
||||
from src.models.user import User
|
||||
from src.models.project import Project
|
||||
from src.schemas.project import (
|
||||
ProjectCreate,
|
||||
ProjectUpdate,
|
||||
ProjectResponse,
|
||||
ProjectListResponse,
|
||||
)
|
||||
from src.middleware.auth import (
|
||||
get_current_user,
|
||||
require_market_or_admin,
|
||||
)
|
||||
from src.dependencies import get_project_or_404
|
||||
from typing import Optional
|
||||
from datetime import date
|
||||
from decimal import Decimal
|
||||
|
||||
router = APIRouter(prefix="/projects", tags=["项目管理"])
|
||||
|
||||
|
||||
@router.get("", response_model=dict)
|
||||
async def get_projects(
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(10, ge=1, le=100),
|
||||
project_no: Optional[str] = None,
|
||||
engineering_type: Optional[str] = None,
|
||||
project_department: Optional[str] = None,
|
||||
signing_date_start: Optional[date] = None,
|
||||
signing_date_end: Optional[date] = None,
|
||||
contract_amount_min: Optional[Decimal] = None,
|
||||
contract_amount_max: Optional[Decimal] = None,
|
||||
keyword: Optional[str] = None,
|
||||
sort_by: Optional[str] = None,
|
||||
sort_order: Optional[str] = Query(None, pattern="^(asc|desc)$"),
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
query = select(Project)
|
||||
|
||||
conditions = []
|
||||
if project_no:
|
||||
conditions.append(Project.project_no == project_no)
|
||||
if engineering_type:
|
||||
conditions.append(Project.engineering_type == engineering_type)
|
||||
if project_department:
|
||||
conditions.append(Project.project_department == project_department)
|
||||
if signing_date_start:
|
||||
conditions.append(Project.signing_date >= signing_date_start)
|
||||
if signing_date_end:
|
||||
conditions.append(Project.signing_date <= signing_date_end)
|
||||
if contract_amount_min:
|
||||
conditions.append(Project.contract_amount >= contract_amount_min)
|
||||
if contract_amount_max:
|
||||
conditions.append(Project.contract_amount <= contract_amount_max)
|
||||
if keyword:
|
||||
conditions.append(
|
||||
(Project.name.contains(keyword)) |
|
||||
(Project.owner_unit.contains(keyword))
|
||||
)
|
||||
|
||||
if conditions:
|
||||
query = query.where(and_(*conditions))
|
||||
|
||||
if sort_by:
|
||||
sort_column = getattr(Project, sort_by, None)
|
||||
if sort_column:
|
||||
order = desc if sort_order == "desc" else asc
|
||||
query = query.order_by(order(sort_column))
|
||||
else:
|
||||
query = query.order_by(Project.created_at.desc())
|
||||
|
||||
total_query = select(func.count()).select_from(query.subquery())
|
||||
total_result = await db.execute(total_query)
|
||||
total = total_result.scalar()
|
||||
|
||||
query = query.offset((page - 1) * page_size).limit(page_size)
|
||||
result = await db.execute(query)
|
||||
projects = result.scalars().all()
|
||||
|
||||
project_data = []
|
||||
for p in projects:
|
||||
creator_result = await db.execute(
|
||||
select(User.real_name).where(User.id == p.created_by)
|
||||
)
|
||||
creator_name = creator_result.scalar_one_or_none()
|
||||
p_dict = ProjectResponse.model_validate(p).model_dump()
|
||||
# Convert Decimal to float for JSON serialization
|
||||
for key, value in p_dict.items():
|
||||
if isinstance(value, Decimal):
|
||||
p_dict[key] = float(value)
|
||||
p_dict["created_by_name"] = creator_name
|
||||
project_data.append(p_dict)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "获取成功",
|
||||
"data": {
|
||||
"items": project_data,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
},
|
||||
"error_code": None,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/{project_id}", response_model=dict)
|
||||
async def get_project(
|
||||
project: Project = Depends(get_project_or_404),
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
result = await db.execute(
|
||||
select(User.real_name).where(User.id == project.created_by)
|
||||
)
|
||||
creator_name = result.scalar_one_or_none()
|
||||
|
||||
project_dict = ProjectResponse.model_validate(project).model_dump()
|
||||
project_dict["created_by_name"] = creator_name
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "获取成功",
|
||||
"data": project_dict,
|
||||
"error_code": None,
|
||||
}
|
||||
|
||||
|
||||
@router.post("", response_model=dict)
|
||||
async def create_project(
|
||||
project_data: ProjectCreate,
|
||||
current_user: User = Depends(require_market_or_admin),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
result = await db.execute(
|
||||
select(Project).where(Project.project_no == project_data.project_no)
|
||||
)
|
||||
if result.scalar_one_or_none():
|
||||
return {
|
||||
"success": False,
|
||||
"message": "项目编号已存在",
|
||||
"data": None,
|
||||
"error_code": "2002",
|
||||
}
|
||||
|
||||
project = Project(
|
||||
**project_data.model_dump(),
|
||||
created_by=current_user.id,
|
||||
)
|
||||
db.add(project)
|
||||
await db.commit()
|
||||
await db.refresh(project)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "项目创建成功",
|
||||
"data": ProjectResponse.model_validate(project),
|
||||
"error_code": None,
|
||||
}
|
||||
|
||||
|
||||
@router.put("/{project_id}", response_model=dict)
|
||||
async def update_project(
|
||||
project_id: int,
|
||||
project_data: ProjectUpdate,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
result = await db.execute(select(Project).where(Project.id == project_id))
|
||||
project = result.scalar_one_or_none()
|
||||
|
||||
if not project:
|
||||
return {
|
||||
"success": False,
|
||||
"message": "项目不存在",
|
||||
"data": None,
|
||||
"error_code": "2001",
|
||||
}
|
||||
|
||||
if current_user.role == "market" and project.created_by != current_user.id:
|
||||
return {
|
||||
"success": False,
|
||||
"message": "无权修改此项目",
|
||||
"data": None,
|
||||
"error_code": "3001",
|
||||
}
|
||||
|
||||
update_data = project_data.model_dump(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
setattr(project, field, value)
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(project)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "项目更新成功",
|
||||
"data": ProjectResponse.model_validate(project),
|
||||
"error_code": None,
|
||||
}
|
||||
|
||||
|
||||
@router.delete("/{project_id}", response_model=dict)
|
||||
async def delete_project(
|
||||
project_id: int,
|
||||
current_user: User = Depends(require_market_or_admin),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
result = await db.execute(select(Project).where(Project.id == project_id))
|
||||
project = result.scalar_one_or_none()
|
||||
|
||||
if not project:
|
||||
return {
|
||||
"success": False,
|
||||
"message": "项目不存在",
|
||||
"data": None,
|
||||
"error_code": "2001",
|
||||
}
|
||||
|
||||
if current_user.role == "market" and project.created_by != current_user.id:
|
||||
return {
|
||||
"success": False,
|
||||
"message": "无权删除此项目",
|
||||
"data": None,
|
||||
"error_code": "3001",
|
||||
}
|
||||
|
||||
await db.delete(project)
|
||||
await db.commit()
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "项目删除成功",
|
||||
"data": None,
|
||||
"error_code": None,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/statistics/basic", response_model=dict)
|
||||
async def get_statistics(
|
||||
engineering_type: Optional[str] = None,
|
||||
signing_date_start: Optional[date] = None,
|
||||
signing_date_end: Optional[date] = None,
|
||||
contract_amount_min: Optional[Decimal] = None,
|
||||
contract_amount_max: Optional[Decimal] = None,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
query = select(Project)
|
||||
|
||||
conditions = []
|
||||
if engineering_type:
|
||||
conditions.append(Project.engineering_type == engineering_type)
|
||||
if signing_date_start:
|
||||
conditions.append(Project.signing_date >= signing_date_start)
|
||||
if signing_date_end:
|
||||
conditions.append(Project.signing_date <= signing_date_end)
|
||||
if contract_amount_min:
|
||||
conditions.append(Project.contract_amount >= contract_amount_min)
|
||||
if contract_amount_max:
|
||||
conditions.append(Project.contract_amount <= contract_amount_max)
|
||||
|
||||
if conditions:
|
||||
query = query.where(and_(*conditions))
|
||||
|
||||
result = await db.execute(query)
|
||||
projects = result.scalars().all()
|
||||
|
||||
total_count = len(projects)
|
||||
total_contract_amount = sum(
|
||||
p.contract_amount or Decimal(0) for p in projects
|
||||
)
|
||||
total_receipt_amount = sum(
|
||||
p.actual_receipt_amount or Decimal(0) for p in projects
|
||||
)
|
||||
total_payment_amount = sum(
|
||||
p.actual_payment_amount or Decimal(0) for p in projects
|
||||
)
|
||||
|
||||
avg_receipt_rate = 0
|
||||
if total_contract_amount > 0:
|
||||
avg_receipt_rate = float(
|
||||
(total_receipt_amount / total_contract_amount) * 100
|
||||
)
|
||||
|
||||
avg_payment_rate = 0
|
||||
total_payable = sum(p.payable_amount or Decimal(0) for p in projects)
|
||||
if total_payable > 0:
|
||||
avg_payment_rate = float(
|
||||
(total_payment_amount / total_payable) * 100
|
||||
)
|
||||
|
||||
avg_progress = 0
|
||||
if total_count > 0:
|
||||
avg_progress = float(
|
||||
sum(
|
||||
p.cumulative_progress or Decimal(0) for p in projects
|
||||
) / total_count
|
||||
)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "统计成功",
|
||||
"data": {
|
||||
"total_count": total_count,
|
||||
"total_contract_amount": float(total_contract_amount),
|
||||
"total_receipt_amount": float(total_receipt_amount),
|
||||
"total_payment_amount": float(total_payment_amount),
|
||||
" avg_receipt_completion_rate": avg_receipt_rate,
|
||||
"avg_payment_completion_rate": avg_payment_rate,
|
||||
"avg_cumulative_progress": avg_progress,
|
||||
},
|
||||
"error_code": None,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/statistics/group", response_model=dict)
|
||||
async def get_group_statistics(
|
||||
group_by: str = Query(..., pattern="^(engineering_type|project_department)$"),
|
||||
signing_date_start: Optional[date] = None,
|
||||
signing_date_end: Optional[date] = None,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
query = select(Project)
|
||||
|
||||
if signing_date_start:
|
||||
query = query.where(Project.signing_date >= signing_date_start)
|
||||
if signing_date_end:
|
||||
query = query.where(Project.signing_date <= signing_date_end)
|
||||
|
||||
result = await db.execute(query)
|
||||
projects = result.scalars().all()
|
||||
|
||||
groups = {}
|
||||
for p in projects:
|
||||
group_value = getattr(p, group_by)
|
||||
if group_value not in groups:
|
||||
groups[group_value] = {
|
||||
"count": 0,
|
||||
"total_contract_amount": Decimal(0),
|
||||
"total_receipt_amount": Decimal(0),
|
||||
"total_payment_amount": Decimal(0),
|
||||
}
|
||||
groups[group_value]["count"] += 1
|
||||
groups[group_value]["total_contract_amount"] += p.contract_amount or Decimal(0)
|
||||
groups[group_value]["total_receipt_amount"] += p.actual_receipt_amount or Decimal(0)
|
||||
groups[group_value]["total_payment_amount"] += p.actual_payment_amount or Decimal(0)
|
||||
|
||||
result_list = []
|
||||
for key, value in groups.items():
|
||||
if group_by == "engineering_type":
|
||||
item = {"engineering_type": key}
|
||||
else:
|
||||
item = {"project_department": key}
|
||||
item.update({
|
||||
"count": value["count"],
|
||||
"total_contract_amount": float(value["total_contract_amount"]),
|
||||
"total_receipt_amount": float(value["total_receipt_amount"]),
|
||||
"total_payment_amount": float(value["total_payment_amount"]),
|
||||
})
|
||||
result_list.append(item)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "统计成功",
|
||||
"data": result_list,
|
||||
"error_code": None,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/statistics/timeline", response_model=dict)
|
||||
async def get_timeline_statistics(
|
||||
time_field: str = Query(..., pattern="^(signing_date|start_date|planned_end_date)$"),
|
||||
group_by: str = Query("month", pattern="^(day|month|year)$"),
|
||||
engineering_type: Optional[str] = None,
|
||||
start_date: Optional[date] = None,
|
||||
end_date: Optional[date] = None,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
query = select(Project)
|
||||
|
||||
if engineering_type:
|
||||
query = query.where(Project.engineering_type == engineering_type)
|
||||
if start_date:
|
||||
date_column = getattr(Project, time_field)
|
||||
query = query.where(date_column >= start_date)
|
||||
if end_date:
|
||||
date_column = getattr(Project, time_field)
|
||||
query = query.where(date_column <= end_date)
|
||||
|
||||
result = await db.execute(query)
|
||||
projects = result.scalars().all()
|
||||
|
||||
groups = {}
|
||||
for p in projects:
|
||||
date_value = getattr(p, time_field)
|
||||
if not date_value:
|
||||
continue
|
||||
|
||||
if group_by == "day":
|
||||
key = date_value.strftime("%Y-%m-%d")
|
||||
elif group_by == "month":
|
||||
key = date_value.strftime("%Y-%m")
|
||||
else:
|
||||
key = str(date_value.year)
|
||||
|
||||
if key not in groups:
|
||||
groups[key] = {"count": 0, "total_contract_amount": Decimal(0)}
|
||||
groups[key]["count"] += 1
|
||||
groups[key]["total_contract_amount"] += p.contract_amount or Decimal(0)
|
||||
|
||||
result_list = []
|
||||
for key in sorted(groups.keys()):
|
||||
value = groups[key]
|
||||
if group_by == "day":
|
||||
item = {"day": key}
|
||||
elif group_by == "month":
|
||||
item = {"month": key}
|
||||
else:
|
||||
item = {"year": key}
|
||||
item.update({
|
||||
"count": value["count"],
|
||||
"total_contract_amount": float(value["total_contract_amount"]),
|
||||
})
|
||||
result_list.append(item)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "统计成功",
|
||||
"data": result_list,
|
||||
"error_code": None,
|
||||
}
|
||||
Reference in New Issue
Block a user