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, }