Files
qiji/backend/app/services/leaves.py
T
2026-07-12 13:01:55 +08:00

205 lines
6.5 KiB
Python

from datetime import date
from uuid import UUID
from sqlalchemy import select, func, and_
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.leave import Leave
from app.models.user import User
from app.utils.timezone import today_cst
from app.services.dashboard import get_week_range
async def get_leaves(
db: AsyncSession,
user_id: str,
role: str,
manager_id: str | None = None,
status: str | None = None,
page: int = 1,
page_size: int = 25,
) -> dict:
"""List leaves. Managers see only their own."""
today = today_cst()
query = select(Leave)
count_q = select(func.count(Leave.id))
# Role filter
if role == "manager":
query = query.where(Leave.manager_id == user_id)
count_q = count_q.where(Leave.manager_id == user_id)
elif manager_id:
query = query.where(Leave.manager_id == manager_id)
count_q = count_q.where(Leave.manager_id == manager_id)
# Status filter
if status == "active":
query = query.where(and_(Leave.start_date <= today, Leave.end_date >= today))
count_q = count_q.where(and_(Leave.start_date <= today, Leave.end_date >= today))
elif status == "upcoming":
query = query.where(Leave.start_date > today)
count_q = count_q.where(Leave.start_date > today)
elif status == "past":
query = query.where(Leave.end_date < today)
count_q = count_q.where(Leave.end_date < today)
total = (await db.execute(count_q)).scalar() or 0
query = query.order_by(Leave.start_date.desc())
query = query.offset((page - 1) * page_size).limit(page_size)
result = await db.execute(query)
rows = result.scalars().all()
# Resolve names
user_ids = set()
for r in rows:
user_ids.add(r.manager_id)
user_ids.add(r.submitted_by)
users_result = await db.execute(select(User).where(User.id.in_(user_ids)))
user_map = {u.id: u.name for u in users_result.scalars().all()}
items = []
for r in rows:
days = ((r.end_date - r.start_date).days + 1) if r.start_date and r.end_date else 0
items.append({
"id": r.id,
"manager_id": r.manager_id,
"manager_name": user_map.get(r.manager_id, ""),
"leave_type": r.leave_type,
"start_date": str(r.start_date),
"end_date": str(r.end_date),
"days": days,
"reason": r.reason or "",
"submitted_by": r.submitted_by,
"submitted_by_name": user_map.get(r.submitted_by, ""),
"created_at": str(r.created_at) if r.created_at else None,
"updated_at": str(r.updated_at) if r.updated_at else None,
})
return {"items": items, "total": total, "page": page, "page_size": page_size}
async def get_leave_by_id(db: AsyncSession, leave_id: UUID) -> Leave | None:
result = await db.execute(select(Leave).where(Leave.id == leave_id))
return result.scalar()
async def create_leave(
db: AsyncSession,
data: dict,
submitted_by: UUID,
role: str,
) -> Leave:
"""Create leave. Manager can only create for self; director can create for anyone."""
if role == "manager":
if str(data.get("manager_id")) != str(submitted_by):
raise PermissionError("客户经理只能为自己提交请假")
if data["start_date"] > data["end_date"]:
raise ValueError("结束日期不能早于开始日期")
leave = Leave(
manager_id=data["manager_id"],
leave_type=data.get("leave_type", "事假"),
start_date=data["start_date"],
end_date=data["end_date"],
reason=data.get("reason", ""),
submitted_by=submitted_by,
)
db.add(leave)
await db.commit()
await db.refresh(leave)
return leave
async def update_leave(
db: AsyncSession,
leave_id: UUID,
data: dict,
user_id: UUID,
role: str,
) -> Leave:
"""Update leave. Director can edit any; manager can edit own submitted leaves."""
leave = await get_leave_by_id(db, leave_id)
if not leave:
raise ValueError("请假记录不存在")
if role == "manager":
if str(leave.submitted_by) != str(user_id):
raise PermissionError("客户经理只能编辑自己提交的请假")
for field in ("manager_id", "leave_type", "start_date", "end_date", "reason"):
if field in data and data[field] is not None:
setattr(leave, field, data[field])
if leave.start_date > leave.end_date:
raise ValueError("结束日期不能早于开始日期")
await db.commit()
await db.refresh(leave)
return leave
async def delete_leave(
db: AsyncSession,
leave_id: UUID,
user_id: UUID,
role: str,
) -> bool:
"""Delete leave. Director can delete any; manager can delete own submitted leaves."""
leave = await get_leave_by_id(db, leave_id)
if not leave:
return False
if role == "manager":
if str(leave.submitted_by) != str(user_id):
raise PermissionError("客户经理只能删除自己提交的请假")
await db.delete(leave)
await db.commit()
return True
async def get_leave_overview(
db: AsyncSession,
user_id: str,
role: str,
reference_date: date | None = None,
) -> dict:
"""Get leave overview for the dashboard card. Returns leaves overlapping with current week."""
monday, sunday = get_week_range(reference_date)
query = select(Leave).where(
and_(Leave.start_date <= sunday, Leave.end_date >= monday)
)
if role == "manager":
query = query.where(Leave.manager_id == user_id)
result = await db.execute(query.order_by(Leave.start_date))
rows = result.scalars().all()
# Deduplicate by manager_id (one manager may have multiple leave records)
user_ids = set(r.manager_id for r in rows)
users_result = await db.execute(select(User).where(User.id.in_(user_ids)))
user_map = {u.id: u.name for u in users_result.scalars().all()}
manager_set = set()
leave_list = []
for r in rows:
days = ((r.end_date - r.start_date).days + 1) if r.start_date and r.end_date else 0
leave_list.append({
"manager_id": str(r.manager_id),
"manager_name": user_map.get(r.manager_id, ""),
"leave_type": r.leave_type,
"start_date": str(r.start_date),
"end_date": str(r.end_date),
"days": days,
})
manager_set.add(str(r.manager_id))
return {
"week_start": str(monday),
"week_end": str(sunday),
"total_on_leave": len(manager_set),
"leave_list": leave_list,
}