feat: 增强员工管理与报销单全流程功能

- 新增员工Excel导入服务(employee_spreadsheet)及导入/导出API端点
- 员工服务增加批量创建、邮箱唯一校验、组织架构关联等能力
- 报销单提交补充身份回填、部门信息透传及预审结果展示优化
- 认证流程增加部门信息(departmentName)并在schema中同步扩展
- 用户Agent服务增加部门关联与报销单回填逻辑
- 前端员工管理页面全面重构,新增导入导出、搜索过滤、分页等功能
- 前端审批中心、审计、差旅报销等视图交互与样式优化
- 新增TableLoadingState共享组件及员工导入测试用例
This commit is contained in:
caoxiaozhu
2026-05-20 14:21:56 +08:00
parent 57957d11a0
commit d7e98a58b9
46 changed files with 4022 additions and 305 deletions

View File

@@ -54,7 +54,7 @@ EXPENSE_TYPE_LABELS = {
"welfare": "福利",
}
PRIVILEGED_CLAIM_ROLE_CODES = {"finance"}
PRIVILEGED_CLAIM_ROLE_CODES = {"finance", "executive"}
APPROVAL_VISIBLE_CLAIM_ROLE_CODES = {"manager", "approver"}
MAX_DRAFT_CLAIMS_PER_USER = 3
LOCATION_REQUIRED_EXPENSE_TYPES = {
@@ -814,17 +814,19 @@ class ExpenseClaimService:
"invoice_count": int(claim.invoice_count or 0),
}
def delete_claim(self, claim_id: str, current_user: CurrentUserContext) -> ExpenseClaim | None:
claim = self.get_claim(claim_id, current_user)
if claim is None:
return None
self._ensure_draft_claim(claim)
before_json = self._serialize_claim(claim)
resource_id = claim.id
self._delete_claim_attachment_root(claim.id)
self.db.delete(claim)
def delete_claim(self, claim_id: str, current_user: CurrentUserContext) -> ExpenseClaim | None:
claim = self.get_claim(claim_id, current_user)
if claim is None:
return None
if not self._has_privileged_claim_access(current_user):
self._ensure_draft_claim(claim)
before_json = self._serialize_claim(claim)
resource_id = claim.id
self._delete_claim_attachment_root(claim.id)
self.db.delete(claim)
self.db.commit()
self.audit_service.log_action(
@@ -835,10 +837,60 @@ class ExpenseClaimService:
before_json=before_json,
after_json=None,
)
return claim
def upsert_draft_from_ontology(
return claim
def return_claim(
self,
claim_id: str,
current_user: CurrentUserContext,
*,
reason: str | None = None,
) -> ExpenseClaim | None:
claim = self.get_claim(claim_id, current_user)
if claim is None:
return None
if not self._has_privileged_claim_access(current_user):
raise ValueError("只有财务人员或高级管理人员可以退回报销单。")
normalized_status = str(claim.status or "").strip().lower()
if normalized_status == "draft":
raise ValueError("草稿状态无需退回。")
if normalized_status in {"approved", "completed", "paid"}:
raise ValueError("已完成单据不允许退回。")
before_json = self._serialize_claim(claim)
operator = current_user.name or current_user.username
return_reason = str(reason or "").strip()
return_flag = {
"source": "manual_return",
"severity": "medium",
"label": "人工退回",
"message": return_reason or f"{operator} 已退回该报销单,请申请人补充后重新提交。",
"operator": operator,
"created_at": datetime.now(UTC).isoformat(),
}
claim.status = "returned"
claim.approval_stage = "待补充"
claim.risk_flags_json = [*list(claim.risk_flags_json or []), return_flag]
self.db.commit()
self.db.refresh(claim)
self.audit_service.log_action(
actor=operator,
action="expense_claim.return",
resource_type="expense_claim",
resource_id=claim.id,
before_json=before_json,
after_json=self._serialize_claim(claim),
)
return claim
def upsert_draft_from_ontology(
self,
*,
run_id: str,