feat: 财务看板口径重构与半年模拟数据及报销状态注册表
- 重构 finance_dashboard 口径计算,新增模拟公司画像数据生成与筛选 - 引入 expense_claim_status_registry 统一报销状态流转 - 完善报销草稿流程、Item Sync 与本体解析器 - 优化总览页趋势图、分页组件与请求进度步骤 - 增强报销申请快速预览、本体工具与详情展示 - 新增半年报销模拟数据种子脚本与状态审计工具 - 补充财务看板、报销状态注册与模拟数据测试覆盖
This commit is contained in:
@@ -327,7 +327,11 @@ class ExpenseClaimDraftFlowMixin:
|
||||
)
|
||||
self._sync_claim_from_items(claim)
|
||||
elif skip_primary_item:
|
||||
self._sync_application_link_draft_without_items(claim)
|
||||
self._clear_application_link_placeholder_items(claim, context_json=context_json)
|
||||
if claim.items:
|
||||
self._sync_claim_from_items(claim)
|
||||
else:
|
||||
self._sync_application_link_draft_without_items(claim)
|
||||
else:
|
||||
self._upsert_primary_item(
|
||||
claim=claim,
|
||||
@@ -394,6 +398,61 @@ class ExpenseClaimDraftFlowMixin:
|
||||
claim.risk_flags_json = self._merge_claim_attachment_risk_flags(claim, [])
|
||||
claim.risk_flags_json = self._merge_claim_platform_risk_preview_flags(claim, [])
|
||||
|
||||
def _clear_application_link_placeholder_items(
|
||||
self,
|
||||
claim: ExpenseClaim,
|
||||
*,
|
||||
context_json: dict[str, Any],
|
||||
) -> None:
|
||||
application_amounts = self._resolve_application_amount_candidates(context_json)
|
||||
for item in list(claim.items or []):
|
||||
if not self._is_application_link_placeholder_item(
|
||||
item,
|
||||
claim=claim,
|
||||
context_json=context_json,
|
||||
application_amounts=application_amounts,
|
||||
):
|
||||
continue
|
||||
claim.items.remove(item)
|
||||
self.db.delete(item)
|
||||
|
||||
def _is_application_link_placeholder_item(
|
||||
self,
|
||||
item: ExpenseClaimItem,
|
||||
*,
|
||||
claim: ExpenseClaim,
|
||||
context_json: dict[str, Any],
|
||||
application_amounts: set[Decimal],
|
||||
) -> bool:
|
||||
if str(item.invoice_id or "").strip():
|
||||
return False
|
||||
|
||||
item_type = str(item.item_type or "").strip().lower()
|
||||
if item_type in DOCUMENT_FACT_ITEM_TYPES:
|
||||
return False
|
||||
if item_type in SYSTEM_GENERATED_ITEM_TYPES:
|
||||
return True
|
||||
|
||||
claim_type = str(claim.expense_type or "").strip().lower()
|
||||
if item_type and claim_type and item_type != claim_type:
|
||||
return False
|
||||
|
||||
amount = self._parse_context_money_amount(item.item_amount)
|
||||
if application_amounts and amount is not None and amount > Decimal("0.00") and amount not in application_amounts:
|
||||
return False
|
||||
|
||||
reason = str(item.item_reason or "").strip()
|
||||
if not reason or reason == "待补充":
|
||||
return True
|
||||
|
||||
review_values = self._normalize_context_object(context_json.get("review_form_values"))
|
||||
linked_reasons = {
|
||||
str(review_values.get(key) or "").strip()
|
||||
for key in ("application_reason", "reason", "business_reason")
|
||||
}
|
||||
linked_reasons.add(str(claim.reason or "").strip())
|
||||
return reason in {value for value in linked_reasons if value}
|
||||
|
||||
def _should_skip_application_link_placeholder_item(
|
||||
self,
|
||||
*,
|
||||
@@ -405,23 +464,10 @@ class ExpenseClaimDraftFlowMixin:
|
||||
) -> bool:
|
||||
if document_specs or attachment_count > 0:
|
||||
return False
|
||||
if claim is not None and list(claim.items or []):
|
||||
return False
|
||||
if self._build_application_link_flag(context_json) is None:
|
||||
return False
|
||||
|
||||
application_amounts = self._resolve_application_amount_candidates(context_json)
|
||||
review_values = self._normalize_context_object(context_json.get("review_form_values"))
|
||||
raw_amount = str(review_values.get("amount") or "").strip()
|
||||
if raw_amount:
|
||||
parsed_amount = self._parse_context_money_amount(raw_amount)
|
||||
if parsed_amount is None:
|
||||
return True
|
||||
return bool(application_amounts and parsed_amount in application_amounts)
|
||||
|
||||
if amount is None or amount <= Decimal("0.00"):
|
||||
return True
|
||||
return bool(application_amounts and amount in application_amounts)
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _resolve_application_amount_candidates(cls, context_json: dict[str, Any]) -> set[Decimal]:
|
||||
@@ -497,7 +543,26 @@ class ExpenseClaimDraftFlowMixin:
|
||||
application_amount_label = pick("application_amount_label", "applicationAmountLabel")
|
||||
application_reason = pick("application_reason", "applicationReason", "reason")
|
||||
application_location = pick("application_location", "applicationLocation", "location")
|
||||
application_date = pick("application_date", "applicationDate", "business_time", "time_range")
|
||||
application_time = pick(
|
||||
"application_business_time",
|
||||
"applicationBusinessTime",
|
||||
"application_time",
|
||||
"applicationTime",
|
||||
"business_time",
|
||||
"businessTime",
|
||||
"time_range",
|
||||
"timeRange",
|
||||
"time",
|
||||
)
|
||||
application_date = pick("application_date", "applicationDate")
|
||||
application_days = pick("application_days", "applicationDays", "days")
|
||||
application_transport_mode = pick("application_transport_mode", "applicationTransportMode", "transport_mode", "transportMode")
|
||||
application_lodging_daily_cap = pick("application_lodging_daily_cap", "applicationLodgingDailyCap", "lodging_daily_cap", "lodgingDailyCap")
|
||||
application_subsidy_daily_cap = pick("application_subsidy_daily_cap", "applicationSubsidyDailyCap", "subsidy_daily_cap", "subsidyDailyCap")
|
||||
application_transport_policy = pick("application_transport_policy", "applicationTransportPolicy", "transport_policy", "transportPolicy")
|
||||
application_policy_estimate = pick("application_policy_estimate", "applicationPolicyEstimate", "policy_estimate", "policyEstimate")
|
||||
application_rule_name = pick("application_rule_name", "applicationRuleName", "rule_name", "ruleName")
|
||||
application_rule_version = pick("application_rule_version", "applicationRuleVersion", "rule_version", "ruleVersion")
|
||||
application_status = pick("application_status", "applicationStatus")
|
||||
application_status_label = pick("application_status_label", "applicationStatusLabel")
|
||||
|
||||
@@ -517,7 +582,17 @@ class ExpenseClaimDraftFlowMixin:
|
||||
"application_location": application_location,
|
||||
"application_amount": application_amount,
|
||||
"application_amount_label": application_amount_label,
|
||||
"application_time": application_date,
|
||||
"application_time": application_time or application_date,
|
||||
"application_business_time": application_time,
|
||||
"application_date": application_date,
|
||||
"application_days": application_days,
|
||||
"application_transport_mode": application_transport_mode,
|
||||
"application_lodging_daily_cap": application_lodging_daily_cap,
|
||||
"application_subsidy_daily_cap": application_subsidy_daily_cap,
|
||||
"application_transport_policy": application_transport_policy,
|
||||
"application_policy_estimate": application_policy_estimate,
|
||||
"application_rule_name": application_rule_name,
|
||||
"application_rule_version": application_rule_version,
|
||||
},
|
||||
"review_form_values": review_values,
|
||||
"expense_scene_selection": scene_selection,
|
||||
|
||||
Reference in New Issue
Block a user