""" 年度收视目标 API — 增删改查 业务规则(.clinerules 5.2 只增不改): - yearly_targets.year 有 UNIQUE 约束 - INSERT 时 year 冲突 → 直接报错,阻止静默覆盖 - 禁止 ON CONFLICT UPDATE """ from fastapi import APIRouter, Depends, HTTPException, status from sqlmodel import Session, select from app.core.deps import get_current_user, require_role from app.db.session import get_session from app.models.user import User, UserRole from app.models.yearly_target import YearlyTarget from app.schemas.yearly_target import YearlyTargetCreate, YearlyTargetResponse, YearlyTargetUpdate router = APIRouter(prefix="/api/yearly_targets", tags=["年度目标"]) @router.get("", response_model=list[YearlyTargetResponse]) def list_targets( session: Session = Depends(get_session), current_user: User = Depends(require_role(UserRole.zhipianren, UserRole.zebian, UserRole.biandao)), ): """获取所有年度目标(三角色均可读)。""" statement = select(YearlyTarget).order_by(YearlyTarget.year.desc()) targets = session.exec(statement).all() return targets @router.get("/{target_id}", response_model=YearlyTargetResponse) def get_target( target_id: int, session: Session = Depends(get_session), current_user: User = Depends(require_role(UserRole.zhipianren, UserRole.zebian, UserRole.biandao)), ): """根据 ID 获取单个年度目标(三角色均可读)。""" target = session.get(YearlyTarget, target_id) if target is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="目标不存在") return target @router.post("", response_model=YearlyTargetResponse, status_code=status.HTTP_201_CREATED) def create_target( body: YearlyTargetCreate, session: Session = Depends(get_session), current_user: User = Depends(require_role(UserRole.zhipianren, UserRole.zebian)), ): """新增年度目标(仅管理员/责编)。 注意:year 有 UNIQUE 约束,重复年份会直接报错。 这是预期行为——历史年份目标不可覆盖。 """ # 先查是否已存在(让报错更友好,而不是数据库层直接崩溃) existing = session.exec(select(YearlyTarget).where(YearlyTarget.year == body.year)).first() if existing: raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail=f"{body.year} 年目标已存在,历史年份不可覆盖。请新增其他年份。", ) target = YearlyTarget.model_validate(body.model_dump()) session.add(target) session.commit() session.refresh(target) return target @router.patch("/{target_id}", response_model=YearlyTargetResponse) def update_target( target_id: int, body: YearlyTargetUpdate, session: Session = Depends(get_session), current_user: User = Depends(require_role(UserRole.zhipianren, UserRole.zebian)), ): """更新年度目标(仅管理员/责编)。 注意:此接口仅用于修正错字等小修,year 字段不可修改。 """ target = session.get(YearlyTarget, target_id) if target is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="目标不存在") update_data = body.model_dump(exclude_unset=True) # 禁止修改 year 字段 update_data.pop("year", None) for field, value in update_data.items(): setattr(target, field, value) session.add(target) session.commit() session.refresh(target) return target @router.delete("/{target_id}", status_code=status.HTTP_204_NO_CONTENT) def delete_target( target_id: int, session: Session = Depends(get_session), current_user: User = Depends(require_role(UserRole.zhipianren)), ): """删除年度目标(仅制片人)。""" target = session.get(YearlyTarget, target_id) if target is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="目标不存在") session.delete(target) session.commit() return None