feat: 增强员工管理与报销单全流程功能
- 新增员工Excel导入服务(employee_spreadsheet)及导入/导出API端点 - 员工服务增加批量创建、邮箱唯一校验、组织架构关联等能力 - 报销单提交补充身份回填、部门信息透传及预审结果展示优化 - 认证流程增加部门信息(departmentName)并在schema中同步扩展 - 用户Agent服务增加部门关联与报销单回填逻辑 - 前端员工管理页面全面重构,新增导入导出、搜索过滤、分页等功能 - 前端审批中心、审计、差旅报销等视图交互与样式优化 - 新增TableLoadingState共享组件及员工导入测试用例
This commit is contained in:
@@ -530,8 +530,16 @@ class UserAgentService:
|
||||
"entry_source": payload.context_json.get("entry_source"),
|
||||
"user_name": payload.context_json.get("name"),
|
||||
"user_role": payload.context_json.get("role"),
|
||||
"user_department": payload.context_json.get("department_name")
|
||||
or payload.context_json.get("department"),
|
||||
"user_position": payload.context_json.get("position"),
|
||||
"user_grade": payload.context_json.get("grade"),
|
||||
"employee_no": payload.context_json.get("employee_no"),
|
||||
"manager_name": payload.context_json.get("manager_name"),
|
||||
"employee_location": payload.context_json.get("employee_location"),
|
||||
"cost_center": payload.context_json.get("cost_center"),
|
||||
"finance_owner_name": payload.context_json.get("finance_owner_name"),
|
||||
"employee_risk_profile": payload.context_json.get("employee_risk_profile", {}),
|
||||
"user_role_codes": payload.context_json.get("role_codes", []),
|
||||
"is_admin": bool(payload.context_json.get("is_admin")),
|
||||
"request_context": payload.context_json.get("request_context"),
|
||||
@@ -2178,18 +2186,36 @@ class UserAgentService:
|
||||
title="AI预审未通过",
|
||||
level="high",
|
||||
content=reason,
|
||||
detail=(
|
||||
"该项属于提交审批前的阻断条件。系统会先要求补齐基础字段、附件或业务说明,"
|
||||
"否则审批人无法判断成本归属、业务真实性或票据有效性。"
|
||||
),
|
||||
suggestion="按提示补齐对应信息;如果业务场景本身合理,请补充说明或佐证附件后再提交。",
|
||||
)
|
||||
)
|
||||
|
||||
employee_name = self._collect_entity_values(payload).get("employee_name") or str(
|
||||
payload.context_json.get("name") or ""
|
||||
).strip()
|
||||
employee = self._resolve_employee_profile(payload)
|
||||
employee_name = (
|
||||
str(employee.name).strip()
|
||||
if employee is not None and employee.name
|
||||
else self._collect_entity_values(payload).get("employee_name")
|
||||
or str(payload.context_json.get("name") or "").strip()
|
||||
)
|
||||
if employee_name:
|
||||
since = datetime.now(UTC) - timedelta(days=90)
|
||||
stmt = select(ExpenseClaim).where(
|
||||
ExpenseClaim.employee_name == employee_name,
|
||||
ExpenseClaim.occurred_at >= since,
|
||||
)
|
||||
claim_identity_conditions = [ExpenseClaim.employee_name == employee_name]
|
||||
if employee is not None:
|
||||
employee_identifiers = {
|
||||
str(employee.name or "").strip(),
|
||||
str(employee.email or "").strip(),
|
||||
str(employee.employee_no or "").strip(),
|
||||
}
|
||||
employee_identifiers.discard("")
|
||||
claim_identity_conditions = [
|
||||
ExpenseClaim.employee_id == employee.id,
|
||||
ExpenseClaim.employee_name.in_(list(employee_identifiers)),
|
||||
]
|
||||
stmt = select(ExpenseClaim).where(or_(*claim_identity_conditions), ExpenseClaim.occurred_at >= since)
|
||||
recent_claims = list(self.db.scalars(stmt).all())
|
||||
if recent_claims:
|
||||
risky_count = sum(1 for item in recent_claims if item.risk_flags_json)
|
||||
@@ -2202,6 +2228,11 @@ class UserAgentService:
|
||||
f"{employee_name} 最近 90 天共有 {len(recent_claims)} 笔报销,"
|
||||
f"其中 {risky_count} 笔带风险标记,{draft_count} 笔仍处于草稿态。"
|
||||
),
|
||||
detail=(
|
||||
"该画像来自员工近 90 天报销记录,用于辅助判断是否存在频繁草稿、"
|
||||
"历史风险或异常重复报销倾向,不会单独阻断审批。"
|
||||
),
|
||||
suggestion="如历史记录中存在风险标记,本次提交时建议主动补充业务背景和票据说明。",
|
||||
)
|
||||
)
|
||||
current_amount = self._resolve_amount_value(payload)
|
||||
@@ -2220,6 +2251,11 @@ class UserAgentService:
|
||||
f"近 90 天发现 {duplicate_count} 笔金额相同的报销记录,"
|
||||
"提交前建议核对是否为重复报销或拆分不当。"
|
||||
),
|
||||
detail=(
|
||||
"系统将当前金额与近 90 天历史报销金额进行比对。金额完全一致不一定违规,"
|
||||
"但在交通、餐饮、办公采购等场景中可能提示重复票据或拆分报销。"
|
||||
),
|
||||
suggestion="核对历史单据与当前票据是否对应同一业务;如不是重复,请在事由中说明差异。",
|
||||
)
|
||||
)
|
||||
|
||||
@@ -2229,6 +2265,8 @@ class UserAgentService:
|
||||
title="制度注意事项",
|
||||
level="info",
|
||||
content=citations[0].excerpt or f"请先核对 {citations[0].title} 的制度要求。",
|
||||
detail=f"本条来自规则或知识库引用:{citations[0].title}。提交前应确认当前单据符合该条口径。",
|
||||
suggestion="如当前场景与制度口径存在差异,请补充审批说明或选择更准确的报销分类。",
|
||||
)
|
||||
)
|
||||
|
||||
@@ -2239,6 +2277,8 @@ class UserAgentService:
|
||||
title="票据识别提醒",
|
||||
level="warning",
|
||||
content=f"当前共有 {warning_count} 条票据识别提示,建议逐张确认 OCR 识别字段。",
|
||||
detail="票据 OCR 识别存在字段缺失、置信度偏低或类型判断不稳定时,会生成该提醒。",
|
||||
suggestion="打开票据明细逐张核对日期、金额、商户和票据类型,必要时更正后再提交。",
|
||||
)
|
||||
)
|
||||
|
||||
@@ -2248,6 +2288,8 @@ class UserAgentService:
|
||||
title="建议拆单",
|
||||
level="high",
|
||||
content=f"系统检测到 {len(claim_groups)} 类费用场景,建议拆成多张报销单后再提交。",
|
||||
detail="同一批附件中包含多类费用场景时,混在一张报销单里会影响规则匹配、附件核验和审批归口。",
|
||||
suggestion="按费用场景拆成多张报销单,分别确认金额、事由和附件归属。",
|
||||
)
|
||||
)
|
||||
|
||||
@@ -2676,6 +2718,7 @@ class UserAgentService:
|
||||
|
||||
stmt = (
|
||||
select(Employee)
|
||||
.options(selectinload(Employee.organization_unit), selectinload(Employee.manager))
|
||||
.where(
|
||||
or_(
|
||||
Employee.name.in_(normalized),
|
||||
|
||||
Reference in New Issue
Block a user