feat: 优化差旅报销预审流程与个人工作台 UI 体系

- 完善 user_agent_application 申请差旅报销预审槽位与消息组装
- 增强预算助理报告与风险建议卡片交互
- 重构登录页视觉样式与移动端响应式适配
- 优化个人工作台、文档中心、政策中心、员工管理等页面布局
- 拆分 travelRequestDetailPreReviewModel 为 advice/submit 模型
- 补充报销草稿、风险复核、Item Sync 与模板执行器测试覆盖
This commit is contained in:
caoxiaozhu
2026-06-02 14:01:51 +08:00
parent 92444e7eae
commit ca691f3ee0
107 changed files with 5663 additions and 1542 deletions

View File

@@ -14,6 +14,7 @@ CITY_CONSISTENCY_SEMANTIC_TYPES = {
"travel_city_consistency",
"travel_route_city_consistency",
}
ROUTE_CITY_SPLIT_PATTERN = re.compile(r"\s*(?:至|到|→|->||-|—|~||/|、||,|;|)\s*")
class RiskRuleTemplateExecutor:
@@ -612,19 +613,32 @@ class RiskRuleTemplateExecutor:
) -> list[str]:
if len(route_values) < 2:
return []
allowed = {value.lower() for value in [*reference_values, *home_values] if value}
if not allowed:
allowed_values = [value for value in [*reference_values, *home_values] if value]
if not allowed_values:
return []
candidates = route_values if home_values else route_values[1:-1]
unexpected: list[str] = []
for city in candidates:
normalized = city.lower()
if normalized in allowed:
if RiskRuleTemplateExecutor._values_overlap([city], allowed_values):
continue
if city not in unexpected:
unexpected.append(city)
return unexpected
@staticmethod
def _expand_route_city_values(values: list[Any]) -> list[Any]:
expanded: list[Any] = []
for value in values:
if isinstance(value, (list, tuple, set)):
expanded.extend(RiskRuleTemplateExecutor._expand_route_city_values(list(value)))
continue
text = str(value or "").strip()
if not text:
continue
parts = [part.strip() for part in ROUTE_CITY_SPLIT_PATTERN.split(text) if part.strip()]
expanded.extend(parts if len(parts) >= 2 else [text])
return expanded
def _resolve_attachment_values(
self, field_key: str, contexts: list[dict[str, Any]]
) -> list[str]:
@@ -643,7 +657,7 @@ class RiskRuleTemplateExecutor:
else self._scan_document_values(document_info, "city")
)
elif field_key == "route_cities":
values.extend(self._scan_document_values(document_info, field_key))
values.extend(self._expand_route_city_values(self._scan_document_values(document_info, field_key)))
else:
values.extend(self._scan_document_values(document_info, field_key))
return self._normalize_values(values)
@@ -878,9 +892,9 @@ class RiskRuleTemplateExecutor:
left_set = {value.lower() for value in left_values}
right_set = {value.lower() for value in right_values}
if operator in {"equals", "in", "overlap"}:
return bool(left_set & right_set)
return RiskRuleTemplateExecutor._values_overlap(left_values, right_values)
if operator in {"not_equals", "not_in", "not_overlap"}:
return not bool(left_set & right_set)
return not RiskRuleTemplateExecutor._values_overlap(left_values, right_values)
if operator == "contains_any":
return any(any(right in left for right in right_set) for left in left_set)
return bool(left_set & right_set)