Support filtered and selected leave rows via backend export and ListExportDropdown. Co-authored-by: Cursor <cursoragent@cursor.com>
289 lines
9.6 KiB
Python
289 lines
9.6 KiB
Python
"""请假申请 API — 工作流测试"""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from datetime import datetime
|
|
from typing import List, Optional
|
|
|
|
from pydantic import BaseModel
|
|
|
|
from fastapi import APIRouter, Depends, Query
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.core.menu_guard import require_admin_or_super_admin
|
|
from app.core.database import get_db
|
|
from app.core.response import success_response, error_response
|
|
from app.crud import leave as leave_crud
|
|
from app.crud.leave import total_pages
|
|
from app.schemas.leave import LeaveCreate, LeaveUpdate
|
|
from app.utils.excel_export import (
|
|
EXPORT_MAX_ROWS,
|
|
ExportColumn,
|
|
ExportValidationError,
|
|
export_rows_to_xlsx_response,
|
|
filter_export_columns,
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter(prefix="/web/admin/leave", tags=["请假管理"])
|
|
|
|
_LEAVE_TYPE_MAP = {"personal": "事假", "sick": "病假", "annual": "年假", "other": "其他"}
|
|
|
|
|
|
def _leave_local_callback(db: Session, business_id: str, event: str) -> None:
|
|
if not business_id:
|
|
return
|
|
if event == "completed":
|
|
leave_crud.update_leave_status(db, business_id, "approved")
|
|
elif event == "rejected":
|
|
leave_crud.update_leave_status(db, business_id, "rejected")
|
|
db.commit()
|
|
|
|
|
|
from app.services.workflow_callback import register_local_callback
|
|
register_local_callback("/api/admin/leave/callback", _leave_local_callback)
|
|
|
|
|
|
@router.get("")
|
|
def list_leaves(
|
|
page: int = Query(1, ge=1),
|
|
pageSize: int = Query(10, ge=1, le=500),
|
|
keyword: Optional[str] = Query(None),
|
|
status: Optional[str] = Query(None),
|
|
_user: dict = Depends(require_admin_or_super_admin),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
tenant_no = _user.get("tenant_no", "")
|
|
user_id = _user.get("user_id") or _user.get("id", "")
|
|
items, total = leave_crud.list_leaves(
|
|
db, page=page, page_size=pageSize, keyword=keyword, status=status,
|
|
tenant_no=tenant_no, create_by=user_id, user_info=_user,
|
|
)
|
|
for item in items:
|
|
item["leaveTypeLabel"] = _LEAVE_TYPE_MAP.get(item.get("leaveType", ""), item.get("leaveType", ""))
|
|
return success_response({
|
|
"list": items, "total": total, "page": page, "pageSize": pageSize,
|
|
"totalPages": total_pages(total, pageSize),
|
|
})
|
|
|
|
|
|
|
|
_LEAVE_STATUS_MAP = {"draft": "草稿", "pending": "审批中", "approved": "已通过", "rejected": "已驳回"}
|
|
|
|
LEAVE_EXPORT_COLUMNS = [
|
|
ExportColumn(title="请假类型", field="leaveTypeLabel"),
|
|
ExportColumn(title="开始日期", field="startDate"),
|
|
ExportColumn(title="结束日期", field="endDate"),
|
|
ExportColumn(title="天数", field="days"),
|
|
ExportColumn(title="请假原因", field="reason"),
|
|
ExportColumn(title="状态", field="statusLabel"),
|
|
ExportColumn(title="创建时间", field="createTime"),
|
|
]
|
|
|
|
|
|
class LeaveExportBody(BaseModel):
|
|
keyword: Optional[str] = None
|
|
status: Optional[str] = None
|
|
ids: Optional[List[str]] = None
|
|
columns: Optional[List[str]] = None
|
|
|
|
|
|
@router.post("/export")
|
|
def export_leaves(
|
|
body: LeaveExportBody,
|
|
_user: dict = Depends(require_admin_or_super_admin),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""导出请假列表(当前筛选全量,或 ids 已选)。"""
|
|
try:
|
|
tenant_no = _user.get("tenant_no", "")
|
|
user_id = _user.get("user_id") or _user.get("id", "")
|
|
items, _ = leave_crud.list_leaves(
|
|
db,
|
|
page=1,
|
|
page_size=EXPORT_MAX_ROWS,
|
|
keyword=body.keyword,
|
|
status=body.status,
|
|
tenant_no=tenant_no,
|
|
create_by=user_id,
|
|
ids=body.ids,
|
|
max_page_size=EXPORT_MAX_ROWS,
|
|
user_info=_user,
|
|
)
|
|
for item in items:
|
|
item["leaveTypeLabel"] = _LEAVE_TYPE_MAP.get(item.get("leaveType", ""), item.get("leaveType", ""))
|
|
item["statusLabel"] = _LEAVE_STATUS_MAP.get(item.get("status", ""), item.get("status", ""))
|
|
# map frontend props status/leaveType to label fields when columns requested
|
|
requested = None
|
|
if body.columns:
|
|
requested = []
|
|
for key in body.columns:
|
|
if key == "status":
|
|
requested.append("statusLabel")
|
|
elif key == "leaveType":
|
|
requested.append("leaveTypeLabel")
|
|
else:
|
|
requested.append(key)
|
|
cols = filter_export_columns(LEAVE_EXPORT_COLUMNS, requested)
|
|
date_str = datetime.now().strftime("%Y%m%d")
|
|
return export_rows_to_xlsx_response(
|
|
db,
|
|
tenant_no=tenant_no or None,
|
|
sheet_title="请假列表",
|
|
columns=cols,
|
|
rows=items,
|
|
filename_stem=f"请假列表_{date_str}",
|
|
)
|
|
except ExportValidationError as e:
|
|
return error_response(400, str(e))
|
|
except Exception as e:
|
|
return error_response(500, str(e))
|
|
|
|
|
|
@router.post("")
|
|
def create_leave(
|
|
body: LeaveCreate,
|
|
_user: dict = Depends(require_admin_or_super_admin),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
tenant_no = _user.get("tenant_no", "")
|
|
user_id = _user.get("user_id") or _user.get("id", "")
|
|
dept_id = _user.get("region_id", "")
|
|
row = leave_crud.create_leave(
|
|
db,
|
|
leave_type=body.leaveType,
|
|
start_date=body.startDate,
|
|
end_date=body.endDate,
|
|
days=body.days,
|
|
reason=body.reason or "",
|
|
tenant_no=tenant_no,
|
|
user_id=user_id,
|
|
dept_id=dept_id,
|
|
remark=body.remark or "",
|
|
)
|
|
db.commit()
|
|
return success_response(leave_crud._row_to_dict(row))
|
|
|
|
|
|
@router.post("/{leave_id}/submit")
|
|
def submit_leave(
|
|
leave_id: str,
|
|
_user: dict = Depends(require_admin_or_super_admin),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
from app.models.models import WfProcessDef
|
|
from app.services.workflow_engine import WorkflowEngine
|
|
|
|
tenant_no = _user.get("tenant_no", "")
|
|
user_id = _user.get("user_id") or _user.get("id", "")
|
|
|
|
leave = leave_crud.get_leave(db, leave_id)
|
|
if not leave:
|
|
return error_response(404, "请假记录不存在")
|
|
|
|
if leave["status"] not in ("draft", "rejected"):
|
|
return error_response(400, "只有草稿或已驳回的请假单可以提交")
|
|
|
|
defn = db.query(WfProcessDef).filter(
|
|
WfProcessDef.process_code == "leave_request",
|
|
WfProcessDef.del_flag == 0,
|
|
WfProcessDef.status == "1",
|
|
).first()
|
|
if not defn:
|
|
return error_response(400, "未找到请假审批流程(leave_request),请先在流程配置中创建并发布")
|
|
|
|
variables = {
|
|
"days": leave["days"],
|
|
"leave_days": leave["days"],
|
|
"leaveType": leave["leaveType"],
|
|
"applicant": user_id,
|
|
}
|
|
|
|
try:
|
|
engine = WorkflowEngine(db)
|
|
instance = engine.start_process(
|
|
process_def_id=defn.id,
|
|
business_type="leave",
|
|
business_id=leave_id,
|
|
starter_user_id=user_id,
|
|
variables=variables,
|
|
callback_url="/api/admin/leave/callback",
|
|
tenant_no=tenant_no,
|
|
user_id=user_id,
|
|
)
|
|
leave_crud.update_leave_status(db, leave_id, "pending", wf_instance_id=instance.id, user_id=user_id)
|
|
db.commit()
|
|
return success_response({"instanceId": instance.id, "status": "pending"})
|
|
except Exception as e:
|
|
db.rollback()
|
|
logger.exception("发起请假审批失败: %s", e)
|
|
return error_response(500, f"发起审批失败: {e}")
|
|
|
|
|
|
@router.post("/callback")
|
|
def leave_workflow_callback(
|
|
body: dict,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
logger.info("请假工作流回调: %s", body)
|
|
business_id = body.get("businessId", "")
|
|
event = body.get("event", "")
|
|
if not business_id:
|
|
return success_response({"received": True})
|
|
|
|
if event == "completed":
|
|
leave_crud.update_leave_status(db, business_id, "approved")
|
|
elif event == "rejected":
|
|
leave_crud.update_leave_status(db, business_id, "rejected")
|
|
db.commit()
|
|
return success_response({"received": True})
|
|
|
|
|
|
@router.get("/{leave_id}")
|
|
def get_leave(
|
|
leave_id: str,
|
|
_user: dict = Depends(require_admin_or_super_admin),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
item = leave_crud.get_leave(db, leave_id)
|
|
if not item:
|
|
return error_response(404, "请假记录不存在")
|
|
item["leaveTypeLabel"] = _LEAVE_TYPE_MAP.get(item.get("leaveType", ""), item.get("leaveType", ""))
|
|
return success_response(item)
|
|
|
|
|
|
@router.delete("/{leave_id}")
|
|
def delete_leave(
|
|
leave_id: str,
|
|
_user: dict = Depends(require_admin_or_super_admin),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
user_id = _user.get("user_id") or _user.get("id", "")
|
|
leave = leave_crud.get_leave(db, leave_id)
|
|
if not leave:
|
|
return error_response(404, "请假记录不存在")
|
|
if leave["status"] == "pending":
|
|
return error_response(400, "审批中的请假单不能删除")
|
|
leave_crud.soft_delete_leave(db, leave_id, user_id=user_id, user_info=_user)
|
|
db.commit()
|
|
return success_response(None)
|
|
|
|
|
|
@router.put("/{leave_id}")
|
|
def update_leave(
|
|
leave_id: str,
|
|
body: LeaveUpdate,
|
|
_user: dict = Depends(require_admin_or_super_admin),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
user_id = _user.get("user_id") or _user.get("id", "")
|
|
leave = leave_crud.get_leave(db, leave_id)
|
|
if not leave:
|
|
return error_response(404, "请假记录不存在")
|
|
if leave["status"] not in ("draft", "rejected"):
|
|
return error_response(400, "只有草稿或已驳回状态可以编辑")
|
|
leave_crud.update_leave(db, leave_id, user_id=user_id, **body.model_dump(exclude_none=True))
|
|
db.commit()
|
|
return success_response(leave_crud.get_leave(db, leave_id))
|