feat: add Leave service layer
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
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,
|
||||
}
|
||||
Reference in New Issue
Block a user