from fastapi import APIRouter, Depends, HTTPException, Query, status from sqlalchemy import select from sqlalchemy.orm import Session, selectinload from app.database import get_db from app.deps import current_user, require_roles from app.models import Notice, NoticePoint, Personnel, Role from app.schemas import NoticeCreate, NoticeOut, NoticeUpdate, PageResponse from app.services.common import paginate from app.services.attendance_points import accessible_point_names router = APIRouter(prefix="/api/notices", tags=["notices"]) def _notice_out(notice: Notice) -> NoticeOut: point_names = sorted([item.attendance_point_name for item in notice.attendance_points]) return NoticeOut( id=notice.id, title=notice.title, content=notice.content, attendance_point_names=point_names, sort_order=notice.sort_order, is_active=notice.is_active, created_by=notice.created_by, creator_name=notice.creator.name if notice.creator else "", created_at=notice.created_at, updated_at=notice.updated_at, ) def _clean_text(value: str, field_name: str) -> str: text = value.strip() if not text: raise HTTPException(status_code=400, detail=f"{field_name}不能为空") return text @router.get("", response_model=PageResponse) def list_notices( include_inactive: bool = False, page: int = Query(1, ge=1), page_size: int = Query(10, ge=1, le=100), user: Personnel = Depends(current_user), db: Session = Depends(get_db), ) -> PageResponse: if include_inactive and user.role not in (Role.admin, Role.manager): raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="无权限") query = ( select(Notice) .options(selectinload(Notice.creator), selectinload(Notice.attendance_points)) .order_by(Notice.sort_order.asc(), Notice.updated_at.desc()) ) if not include_inactive: query = query.where(Notice.is_active.is_(True)) point_names = accessible_point_names(db, user) query = query.where(Notice.attendance_points.any(NoticePoint.attendance_point_name.in_(point_names))) result = paginate(db, query, page, page_size) return PageResponse(**{**result, "rows": [_notice_out(row) for row in result["rows"]]}) @router.post("", response_model=NoticeOut) def create_notice( payload: NoticeCreate, user: Personnel = Depends(require_roles(Role.admin, Role.manager)), db: Session = Depends(get_db), ) -> NoticeOut: allowed = set(accessible_point_names(db, user)) point_names = payload.attendance_point_names or list(allowed) denied = [name for name in point_names if name not in allowed] if denied: raise HTTPException(status_code=403, detail=f"无该考勤点通知配置权限:{'、'.join(denied)}") notice = Notice( title=_clean_text(payload.title, "标题"), content=_clean_text(payload.content, "内容"), sort_order=payload.sort_order, is_active=payload.is_active, created_by=user.phone, ) for name in dict.fromkeys(point_names): notice.attendance_points.append(NoticePoint(attendance_point_name=name)) db.add(notice) db.commit() db.refresh(notice) return _notice_out(notice) @router.put("/{notice_id}", response_model=NoticeOut) def update_notice( notice_id: int, payload: NoticeUpdate, user: Personnel = Depends(require_roles(Role.admin, Role.manager)), db: Session = Depends(get_db), ) -> NoticeOut: notice = db.scalar( select(Notice) .options(selectinload(Notice.attendance_points)) .where(Notice.id == notice_id) ) if notice is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="通知不存在") allowed = set(accessible_point_names(db, user)) if user.role == Role.admin and not any(item.attendance_point_name in allowed for item in notice.attendance_points): raise HTTPException(status_code=403, detail="无该考勤点通知配置权限") if payload.title is not None: notice.title = _clean_text(payload.title, "标题") if payload.content is not None: notice.content = _clean_text(payload.content, "内容") if payload.sort_order is not None: notice.sort_order = payload.sort_order if payload.is_active is not None: notice.is_active = payload.is_active if payload.attendance_point_names is not None: denied = [name for name in payload.attendance_point_names if name not in allowed] if denied: raise HTTPException(status_code=403, detail=f"无该考勤点通知配置权限:{'、'.join(denied)}") notice.attendance_points.clear() db.flush() for name in dict.fromkeys(payload.attendance_point_names or list(allowed)): notice.attendance_points.append(NoticePoint(attendance_point_name=name)) db.commit() db.refresh(notice) return _notice_out(notice) @router.delete("/{notice_id}", status_code=204) def delete_notice( notice_id: int, user: Personnel = Depends(require_roles(Role.admin, Role.manager)), db: Session = Depends(get_db), ) -> None: notice = db.scalar( select(Notice) .options(selectinload(Notice.attendance_points)) .where(Notice.id == notice_id) ) if notice is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="通知不存在") allowed = set(accessible_point_names(db, user)) if user.role == Role.admin and not any(item.attendance_point_name in allowed for item in notice.attendance_points): raise HTTPException(status_code=403, detail="无该考勤点通知配置权限") db.delete(notice) db.commit()