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

@@ -23,6 +23,7 @@ from app.services.expense_claim_constants import (
AI_REVIEW_REPEAT_RISK_WARNING_COUNT,
DOCUMENT_FACT_ITEM_TYPES,
LOCATION_REQUIRED_EXPENSE_TYPES,
OPTIONAL_ATTACHMENT_ITEM_TYPES,
SYSTEM_GENERATED_ITEM_TYPES,
TRAVEL_ALLOWANCE_TRIGGER_ITEM_TYPES,
TRAVEL_POLICY_HOTEL_NIGHT_PATTERN,
@@ -399,6 +400,20 @@ class ExpenseClaimItemSyncMixin:
return 1
return max(0, int(policy.min_attachment_count or 0))
@staticmethod
def _is_attachment_required_item_type(item_type: str | None) -> bool:
normalized = str(item_type or "").strip().lower()
return normalized not in SYSTEM_GENERATED_ITEM_TYPES and normalized not in OPTIONAL_ATTACHMENT_ITEM_TYPES
def _resolve_claim_required_attachment_count(self, claim: ExpenseClaim) -> int:
required_items = [
item for item in list(claim.items or [])
if self._is_attachment_required_item_type(item.item_type)
]
if not required_items:
return 0
return min(self._resolve_min_attachment_count(claim.expense_type), len(required_items))
def _build_scene_reason_corpus(self, claim: ExpenseClaim) -> str:
parts = [str(claim.reason or "").strip(), str(claim.location or "").strip()]
for item in claim.items:
@@ -454,16 +469,16 @@ class ExpenseClaimItemSyncMixin:
def _format_submission_blocked_message(issues: list[str]) -> str:
normalized_issues = [str(issue or "").strip() for issue in issues if str(issue or "").strip()]
if not normalized_issues:
return "AI预审未通过,但没有返回明确原因,请刷新草稿后重试。"
return "自动检测未通过,但没有返回明确原因,请刷新草稿后重试。"
return "AI预审暂未通过,原因如下:\n" + "\n".join(
return "自动检测暂未通过,原因如下:\n" + "\n".join(
f"{index}. {issue}" for index, issue in enumerate(normalized_issues, start=1)
)
def _validate_claim_for_submission(self, claim: ExpenseClaim) -> list[str]:
issues: list[str] = []
claim_location_required = self._is_location_required_expense_type(claim.expense_type)
claim_min_attachment_count = self._resolve_min_attachment_count(claim.expense_type)
claim_min_attachment_count = self._resolve_claim_required_attachment_count(claim)
if self._is_missing_value(claim.employee_name):
issues.append("申请人未完善")
@@ -498,7 +513,7 @@ class ExpenseClaimItemSyncMixin:
issues.append(f"{prefix}缺少地点")
if item.item_amount is None or item.item_amount <= Decimal("0.00"):
issues.append(f"{prefix}缺少金额")
if not is_system_generated and self._is_missing_value(item.invoice_id):
if self._is_attachment_required_item_type(item.item_type) and self._is_missing_value(item.invoice_id):
issues.append(f"{prefix}缺少票据标识")
return issues