feat: 完善报销单审批流程及退回原因追踪
新增直属领导审批通过接口和审批待办列表查询,报销单退回 支持原因码分类和审批环节标记,优化票据附件去重和路径 回退查找,前端新增退回原因对话框、审批收件箱和工作台 图标组件,补充工具函数和单元测试覆盖。
This commit is contained in:
@@ -104,7 +104,7 @@ DOCUMENT_RULES: tuple[DocumentRule, ...] = (
|
||||
scene_code="transport",
|
||||
scene_label="交通票据",
|
||||
expense_type="transport",
|
||||
keywords=("滴滴出行", "滴滴", "网约车", "出租车", "打车", "快车", "专车", "订单号", "上车", "下车", "起点", "终点", "里程", "司机"),
|
||||
keywords=("滴滴出行", "滴滴", "网约车", "出租车", "打车", "乘车", "用车", "叫车", "车费", "车资", "的士", "快车", "专车", "订单号", "上车", "下车", "起点", "终点", "里程", "司机"),
|
||||
score_bias=0.38,
|
||||
),
|
||||
DocumentRule(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -167,11 +167,16 @@ EXPENSE_TYPE_KEYWORDS = {
|
||||
"出差": "travel",
|
||||
"住宿": "hotel",
|
||||
"酒店": "hotel",
|
||||
"交通": "transport",
|
||||
"打车": "transport",
|
||||
"网约车": "transport",
|
||||
"出租车": "transport",
|
||||
"停车费": "transport",
|
||||
"交通": "transport",
|
||||
"打车": "transport",
|
||||
"网约车": "transport",
|
||||
"出租车": "transport",
|
||||
"乘车": "transport",
|
||||
"乘车费": "transport",
|
||||
"用车": "transport",
|
||||
"叫车": "transport",
|
||||
"车资": "transport",
|
||||
"停车费": "transport",
|
||||
"餐费": "meal",
|
||||
"用餐": "meal",
|
||||
"会务": "meeting",
|
||||
@@ -202,9 +207,14 @@ EXPENSE_NARRATIVE_KEYWORDS = (
|
||||
"花了",
|
||||
"支出",
|
||||
"垫付",
|
||||
"打车",
|
||||
"车费",
|
||||
"餐费",
|
||||
"打车",
|
||||
"车费",
|
||||
"乘车",
|
||||
"乘车费",
|
||||
"用车",
|
||||
"叫车",
|
||||
"车资",
|
||||
"餐费",
|
||||
"吃饭",
|
||||
"用餐",
|
||||
"宴请",
|
||||
@@ -1190,8 +1200,11 @@ class SemanticOntologyService:
|
||||
)
|
||||
)
|
||||
|
||||
if any(keyword in query for keyword in ("打车", "网约车", "出租车", "车费", "停车费", "过路费")):
|
||||
upsert(self._make_entity("expense_type", "交通", "transport", role="filter", confidence=0.9))
|
||||
if any(
|
||||
keyword in query
|
||||
for keyword in ("打车", "网约车", "出租车", "车费", "乘车", "用车", "叫车", "车资", "停车费", "过路费")
|
||||
):
|
||||
upsert(self._make_entity("expense_type", "交通", "transport", role="filter", confidence=0.9))
|
||||
|
||||
if any(keyword in query for keyword in ("出差", "机票", "火车", "高铁", "行程单")):
|
||||
upsert(self._make_entity("expense_type", "差旅", "travel", role="filter", confidence=0.88))
|
||||
|
||||
@@ -226,6 +226,16 @@ SYSTEM_GENERATED_REASON_PREFIXES = (
|
||||
"查看报销草稿",
|
||||
"请解释一下当前这笔报销的合规风险和待补充项",
|
||||
)
|
||||
LEADING_REASON_TIME_PATTERNS = (
|
||||
re.compile(
|
||||
r"^\s*(?:识别事项(?:有)?[::]\s*)?"
|
||||
r"(?:业务发生(?:时间|日期)|费用发生(?:时间|日期)|发生(?:时间|日期)|报销(?:时间|日期)|时间)[::]?\s*"
|
||||
r"(?:19|20)\d{2}[-/年.]\d{1,2}[-/月.]\d{1,2}日?\s*[,,。;;、]?\s*"
|
||||
),
|
||||
re.compile(
|
||||
r"^\s*(?:19|20)\d{2}[-/年.]\d{1,2}[-/月.]\d{1,2}日?\s*[,,。;;、]\s*"
|
||||
),
|
||||
)
|
||||
AMOUNT_UNIT_ALIASES = {
|
||||
"员": "元",
|
||||
"圆": "元",
|
||||
@@ -2298,8 +2308,11 @@ class UserAgentService:
|
||||
@staticmethod
|
||||
def _resolve_submission_blocked_reasons(payload: UserAgentRequest) -> list[str]:
|
||||
raw_reasons = payload.tool_payload.get("submission_blocked_reasons")
|
||||
if raw_reasons is None:
|
||||
submission_blocked = bool(payload.tool_payload.get("submission_blocked"))
|
||||
if raw_reasons is None and submission_blocked:
|
||||
raw_reasons = payload.tool_payload.get("missing_fields")
|
||||
if raw_reasons is None and not submission_blocked:
|
||||
return []
|
||||
|
||||
reasons: list[str] = []
|
||||
if isinstance(raw_reasons, list):
|
||||
@@ -2311,11 +2324,18 @@ class UserAgentService:
|
||||
if item.strip()
|
||||
)
|
||||
|
||||
if not reasons:
|
||||
if not reasons and submission_blocked:
|
||||
message = str(payload.tool_payload.get("message") or "").strip()
|
||||
prefix = "提交前请先补全信息:"
|
||||
if message.startswith(prefix):
|
||||
message = message[len(prefix):].strip()
|
||||
for prefix in (
|
||||
"提交前请先补全信息:",
|
||||
"AI预审暂未通过,原因如下:",
|
||||
"AI预审未通过,原因如下:",
|
||||
"AI预审暂未通过:",
|
||||
"AI预审未通过:",
|
||||
):
|
||||
if message.startswith(prefix):
|
||||
message = message[len(prefix):].strip()
|
||||
break
|
||||
if message:
|
||||
reasons.extend(
|
||||
item.strip()
|
||||
@@ -2769,7 +2789,7 @@ class UserAgentService:
|
||||
|
||||
@classmethod
|
||||
def _resolve_reason_text(cls, message: str) -> str:
|
||||
reason = cls._extract_message_reason(message)
|
||||
reason = cls._strip_leading_time_from_reason(cls._extract_message_reason(message))
|
||||
if not reason:
|
||||
return ""
|
||||
|
||||
@@ -2799,6 +2819,15 @@ class UserAgentService:
|
||||
|
||||
return reason
|
||||
|
||||
@staticmethod
|
||||
def _strip_leading_time_from_reason(value: str) -> str:
|
||||
reason = str(value or "").strip()
|
||||
for pattern in LEADING_REASON_TIME_PATTERNS:
|
||||
next_reason = pattern.sub("", reason).strip()
|
||||
if next_reason != reason:
|
||||
return next_reason
|
||||
return reason
|
||||
|
||||
@staticmethod
|
||||
def _should_skip_model_answer(
|
||||
payload: UserAgentRequest,
|
||||
@@ -3490,7 +3519,7 @@ class UserAgentService:
|
||||
return "travel", "差旅费"
|
||||
if any(keyword in compact for keyword in ("住宿", "酒店", "宾馆")):
|
||||
return "hotel", "住宿费"
|
||||
if any(keyword in compact for keyword in ("交通", "打车", "网约车", "出租车", "车费", "停车")):
|
||||
if any(keyword in compact for keyword in ("交通", "打车", "网约车", "出租车", "乘车", "用车", "叫车", "车费", "车资", "的士", "停车")):
|
||||
return "transport", "交通费"
|
||||
if any(keyword in compact for keyword in ("餐费", "用餐", "午餐", "晚餐", "早餐", "伙食")):
|
||||
return "meal", "餐费"
|
||||
@@ -3698,7 +3727,7 @@ class UserAgentService:
|
||||
"group_code": "travel",
|
||||
"scene_label": "住宿票据",
|
||||
}
|
||||
if any(keyword in compact for keyword in ("打车", "出租车", "滴滴", "网约车", "过路费", "停车")):
|
||||
if any(keyword in compact for keyword in ("打车", "出租车", "滴滴", "网约车", "乘车", "用车", "叫车", "车费", "车资", "的士", "过路费", "停车")):
|
||||
return {
|
||||
"document_type": "transport_receipt",
|
||||
"expense_type": "transport",
|
||||
|
||||
Reference in New Issue
Block a user