refactor: enforce 800 line source limits
This commit is contained in:
@@ -114,294 +114,7 @@ APPROVED_APPLICATION_LINK_STATUSES = {"approved", "completed"}
|
||||
INACTIVE_APPLICATION_LINK_REIMBURSEMENT_STATUSES = {"cancelled", "canceled", "deleted"}
|
||||
|
||||
|
||||
class ExpenseClaimDraftFlowMixin:
|
||||
def upsert_draft_from_ontology(
|
||||
self,
|
||||
*,
|
||||
run_id: str,
|
||||
user_id: str | None,
|
||||
message: str,
|
||||
ontology: OntologyParseResult,
|
||||
context_json: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
self._ensure_ready()
|
||||
context_json = dict(context_json or {})
|
||||
retry_count = self._resolve_claim_no_retry_count(context_json)
|
||||
|
||||
review_action = str(context_json.get("review_action") or "").strip()
|
||||
attachment_names = self._resolve_attachment_names(context_json)
|
||||
context_documents = self._resolve_context_documents(context_json)
|
||||
|
||||
employee = self._resolve_employee(
|
||||
ontology=ontology,
|
||||
context_json=context_json,
|
||||
user_id=user_id,
|
||||
)
|
||||
draft_owner_name = (
|
||||
employee.name
|
||||
if employee is not None
|
||||
else self._resolve_employee_name(
|
||||
ontology=ontology,
|
||||
context_json=context_json,
|
||||
user_id=user_id,
|
||||
)
|
||||
)
|
||||
|
||||
association_candidate = self._find_association_candidate(
|
||||
ontology=ontology,
|
||||
context_json=context_json,
|
||||
user_id=user_id,
|
||||
employee=employee,
|
||||
)
|
||||
if self._should_defer_multi_document_association(
|
||||
context_json=context_json,
|
||||
review_action=review_action,
|
||||
association_candidate=association_candidate,
|
||||
context_documents=context_documents,
|
||||
):
|
||||
document_count = max(len(context_documents), len(attachment_names), self._resolve_attachment_count(context_json))
|
||||
return {
|
||||
"message": (
|
||||
f"检测到你已有草稿 {association_candidate.claim_no},"
|
||||
f"当前新上传了 {document_count} 张票据,请先选择关联到现有草稿,或单独建立新的报销单。"
|
||||
),
|
||||
"draft_only": False,
|
||||
"status": "pending_association_decision",
|
||||
"pending_association_decision": True,
|
||||
"association_candidate_claim_id": association_candidate.id,
|
||||
"association_candidate_claim_no": association_candidate.claim_no,
|
||||
}
|
||||
|
||||
claim = self._find_target_claim(
|
||||
ontology=ontology,
|
||||
context_json=context_json,
|
||||
review_action=review_action,
|
||||
association_candidate=association_candidate,
|
||||
)
|
||||
is_new_claim = claim is None
|
||||
before_json = self._serialize_claim(claim) if claim is not None else None
|
||||
application_link_block_result = self._build_application_link_block_result(
|
||||
context_json=context_json,
|
||||
target_claim=claim,
|
||||
)
|
||||
if application_link_block_result is not None:
|
||||
return application_link_block_result
|
||||
if is_new_claim:
|
||||
existing_draft_count = self._count_draft_claims_for_owner(
|
||||
employee=employee,
|
||||
user_id=user_id,
|
||||
)
|
||||
if existing_draft_count >= MAX_DRAFT_CLAIMS_PER_USER:
|
||||
return {
|
||||
"message": (
|
||||
f"你当前已保存 {MAX_DRAFT_CLAIMS_PER_USER} 个草稿,请先完成已保存的草稿,"
|
||||
"才能再次新建草稿。"
|
||||
),
|
||||
"draft_limit_reached": True,
|
||||
"draft_only": False,
|
||||
"status": "blocked",
|
||||
"draft_count": existing_draft_count,
|
||||
"max_draft_count": MAX_DRAFT_CLAIMS_PER_USER,
|
||||
}
|
||||
|
||||
amount = self._resolve_amount(ontology.entities, context_json=context_json)
|
||||
occurred_at = self._resolve_occurred_at(ontology, context_json=context_json)
|
||||
explicit_expense_type = self._resolve_explicit_review_expense_type(context_json)
|
||||
inferred_expense_type = self._resolve_expense_type(ontology.entities, context_json=context_json)
|
||||
locked_expense_type = explicit_expense_type
|
||||
if not locked_expense_type and claim is not None and review_action in DOCUMENT_ASSOCIATION_REVIEW_ACTIONS:
|
||||
locked_expense_type = str(claim.expense_type or "").strip()
|
||||
expense_type = locked_expense_type or inferred_expense_type
|
||||
location = self._resolve_location(message=message, context_json=context_json)
|
||||
reason = self._resolve_reason(
|
||||
message=message,
|
||||
context_json=context_json,
|
||||
allow_message_fallback=is_new_claim,
|
||||
)
|
||||
attachment_count = len(attachment_names) or self._resolve_attachment_count(context_json)
|
||||
|
||||
final_amount = amount if amount is not None else (claim.amount if claim is not None else Decimal("0.00"))
|
||||
final_occurred_at = (
|
||||
occurred_at if occurred_at is not None else (claim.occurred_at if claim is not None else datetime.now(UTC))
|
||||
)
|
||||
final_expense_type = expense_type or (claim.expense_type if claim is not None else "other")
|
||||
final_location = location or (claim.location if claim is not None else "待补充")
|
||||
final_reason = reason or (claim.reason if claim is not None else "待补充")
|
||||
final_attachment_count = (
|
||||
attachment_count if attachment_count > 0 else int(claim.invoice_count or 0) if claim is not None else 0
|
||||
)
|
||||
final_risk_flags = self._merge_persistent_claim_risk_flags(
|
||||
existing_flags=list(claim.risk_flags_json or []) if claim is not None else [],
|
||||
next_flags=list(ontology.risk_flags),
|
||||
)
|
||||
final_risk_flags = self._merge_application_link_flag(
|
||||
final_risk_flags,
|
||||
context_json=context_json,
|
||||
)
|
||||
if context_documents or attachment_names:
|
||||
document_specs = self._build_context_item_specs(
|
||||
context_documents=context_documents,
|
||||
attachment_names=attachment_names,
|
||||
occurred_at=final_occurred_at,
|
||||
expense_type=final_expense_type,
|
||||
amount=final_amount,
|
||||
reason=final_reason,
|
||||
location=final_location,
|
||||
context_json=context_json,
|
||||
employee_grade=str(employee.grade or "").strip() if employee is not None else "",
|
||||
user_id=user_id,
|
||||
)
|
||||
else:
|
||||
document_specs = []
|
||||
|
||||
if claim is not None and review_action == "link_to_existing_draft" and document_specs:
|
||||
duplicate_result = self._build_duplicate_attachment_block_result(
|
||||
claim=claim,
|
||||
document_specs=document_specs,
|
||||
context_documents=context_documents,
|
||||
)
|
||||
if duplicate_result is not None:
|
||||
return duplicate_result
|
||||
|
||||
try:
|
||||
if claim is None:
|
||||
claim = ExpenseClaim(
|
||||
claim_no=self._generate_claim_no(final_occurred_at),
|
||||
employee_id=employee.id if employee is not None else None,
|
||||
employee_name=draft_owner_name,
|
||||
department_id=employee.organization_unit_id if employee is not None else None,
|
||||
department_name=self._resolve_department_name(
|
||||
employee=employee,
|
||||
context_json=context_json,
|
||||
),
|
||||
project_code=self._resolve_project_code(ontology.entities),
|
||||
expense_type=final_expense_type,
|
||||
reason=final_reason,
|
||||
location=final_location,
|
||||
amount=final_amount,
|
||||
currency="CNY",
|
||||
invoice_count=final_attachment_count,
|
||||
occurred_at=final_occurred_at,
|
||||
status="draft",
|
||||
approval_stage="待提交",
|
||||
risk_flags_json=final_risk_flags,
|
||||
)
|
||||
self.db.add(claim)
|
||||
else:
|
||||
claim.employee_id = employee.id if employee is not None else claim.employee_id
|
||||
claim.employee_name = (
|
||||
employee.name
|
||||
if employee is not None
|
||||
else self._resolve_employee_name(
|
||||
ontology=ontology,
|
||||
context_json=context_json,
|
||||
user_id=user_id,
|
||||
fallback=claim.employee_name,
|
||||
)
|
||||
)
|
||||
claim.department_id = employee.organization_unit_id if employee is not None else claim.department_id
|
||||
claim.department_name = self._resolve_department_name(
|
||||
employee=employee,
|
||||
context_json=context_json,
|
||||
fallback=claim.department_name,
|
||||
)
|
||||
claim.project_code = self._resolve_project_code(ontology.entities) or claim.project_code
|
||||
claim.expense_type = final_expense_type
|
||||
claim.reason = final_reason
|
||||
claim.location = final_location
|
||||
claim.amount = final_amount
|
||||
claim.invoice_count = final_attachment_count
|
||||
claim.occurred_at = final_occurred_at
|
||||
claim.status = "draft"
|
||||
claim.approval_stage = "待提交"
|
||||
claim.risk_flags_json = final_risk_flags
|
||||
|
||||
self.db.flush()
|
||||
skip_primary_item = self._should_skip_application_link_placeholder_item(
|
||||
claim=claim,
|
||||
context_json=context_json,
|
||||
document_specs=document_specs,
|
||||
attachment_count=attachment_count,
|
||||
amount=amount,
|
||||
)
|
||||
if document_specs and (is_new_claim or review_action in DOCUMENT_ASSOCIATION_REVIEW_ACTIONS):
|
||||
if review_action == "link_to_existing_draft" and claim.items:
|
||||
self._append_document_items(
|
||||
claim=claim,
|
||||
item_specs=document_specs,
|
||||
)
|
||||
else:
|
||||
self._replace_claim_items(
|
||||
claim=claim,
|
||||
item_specs=document_specs,
|
||||
)
|
||||
self._sync_claim_from_items(claim)
|
||||
elif skip_primary_item:
|
||||
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,
|
||||
occurred_at=final_occurred_at,
|
||||
expense_type=final_expense_type,
|
||||
amount=final_amount,
|
||||
reason=final_reason,
|
||||
location=final_location,
|
||||
attachment_names=attachment_names,
|
||||
)
|
||||
self._sync_claim_from_items(claim)
|
||||
if locked_expense_type:
|
||||
claim.expense_type = locked_expense_type
|
||||
self.db.commit()
|
||||
self.db.refresh(claim)
|
||||
except IntegrityError as exc:
|
||||
self.db.rollback()
|
||||
if (
|
||||
is_new_claim
|
||||
and retry_count < MAX_CLAIM_NO_RETRY_ATTEMPTS
|
||||
and self._is_claim_no_conflict_error(exc)
|
||||
):
|
||||
retry_context = dict(context_json)
|
||||
retry_context["_claim_no_retry_count"] = retry_count + 1
|
||||
return self.upsert_draft_from_ontology(
|
||||
run_id=run_id,
|
||||
user_id=user_id,
|
||||
message=message,
|
||||
ontology=ontology,
|
||||
context_json=retry_context,
|
||||
)
|
||||
raise
|
||||
|
||||
except Exception:
|
||||
self.db.rollback()
|
||||
raise
|
||||
|
||||
self.audit_service.log_action(
|
||||
actor=user_id or claim.employee_name or "anonymous",
|
||||
action="expense_claim.draft_upsert",
|
||||
resource_type="expense_claim",
|
||||
resource_id=claim.id,
|
||||
before_json=before_json,
|
||||
after_json=self._serialize_claim(claim),
|
||||
request_id=run_id,
|
||||
)
|
||||
|
||||
return {
|
||||
"message": (
|
||||
f"已{'创建' if is_new_claim else '更新'}报销草稿 {claim.claim_no},当前状态为 draft。"
|
||||
"请核对识别结果,确认无误后继续提交。"
|
||||
),
|
||||
"draft_only": True,
|
||||
"claim_id": claim.id,
|
||||
"claim_no": claim.claim_no,
|
||||
"status": claim.status,
|
||||
"amount": float(claim.amount),
|
||||
"invoice_count": int(claim.invoice_count or 0),
|
||||
}
|
||||
|
||||
class ExpenseClaimApplicationLinkMixin:
|
||||
def _sync_application_link_draft_without_items(self, claim: ExpenseClaim) -> None:
|
||||
claim.amount = Decimal("0.00")
|
||||
claim.invoice_count = 0
|
||||
@@ -826,6 +539,8 @@ class ExpenseClaimDraftFlowMixin:
|
||||
def _normalize_context_object(value: Any) -> dict[str, Any]:
|
||||
return dict(value) if isinstance(value, dict) else {}
|
||||
|
||||
|
||||
class ExpenseClaimDraftAttachmentAssociationMixin:
|
||||
def _find_target_claim(
|
||||
self,
|
||||
*,
|
||||
@@ -1062,3 +777,293 @@ class ExpenseClaimDraftFlowMixin:
|
||||
"amount": float(claim.amount or Decimal("0.00")),
|
||||
"invoice_count": int(claim.invoice_count or 0),
|
||||
}
|
||||
|
||||
|
||||
class ExpenseClaimDraftFlowMixin(ExpenseClaimApplicationLinkMixin, ExpenseClaimDraftAttachmentAssociationMixin):
|
||||
def upsert_draft_from_ontology(
|
||||
self,
|
||||
*,
|
||||
run_id: str,
|
||||
user_id: str | None,
|
||||
message: str,
|
||||
ontology: OntologyParseResult,
|
||||
context_json: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
self._ensure_ready()
|
||||
context_json = dict(context_json or {})
|
||||
retry_count = self._resolve_claim_no_retry_count(context_json)
|
||||
|
||||
review_action = str(context_json.get("review_action") or "").strip()
|
||||
attachment_names = self._resolve_attachment_names(context_json)
|
||||
context_documents = self._resolve_context_documents(context_json)
|
||||
|
||||
employee = self._resolve_employee(
|
||||
ontology=ontology,
|
||||
context_json=context_json,
|
||||
user_id=user_id,
|
||||
)
|
||||
draft_owner_name = (
|
||||
employee.name
|
||||
if employee is not None
|
||||
else self._resolve_employee_name(
|
||||
ontology=ontology,
|
||||
context_json=context_json,
|
||||
user_id=user_id,
|
||||
)
|
||||
)
|
||||
|
||||
association_candidate = self._find_association_candidate(
|
||||
ontology=ontology,
|
||||
context_json=context_json,
|
||||
user_id=user_id,
|
||||
employee=employee,
|
||||
)
|
||||
if self._should_defer_multi_document_association(
|
||||
context_json=context_json,
|
||||
review_action=review_action,
|
||||
association_candidate=association_candidate,
|
||||
context_documents=context_documents,
|
||||
):
|
||||
document_count = max(len(context_documents), len(attachment_names), self._resolve_attachment_count(context_json))
|
||||
return {
|
||||
"message": (
|
||||
f"检测到你已有草稿 {association_candidate.claim_no},"
|
||||
f"当前新上传了 {document_count} 张票据,请先选择关联到现有草稿,或单独建立新的报销单。"
|
||||
),
|
||||
"draft_only": False,
|
||||
"status": "pending_association_decision",
|
||||
"pending_association_decision": True,
|
||||
"association_candidate_claim_id": association_candidate.id,
|
||||
"association_candidate_claim_no": association_candidate.claim_no,
|
||||
}
|
||||
|
||||
claim = self._find_target_claim(
|
||||
ontology=ontology,
|
||||
context_json=context_json,
|
||||
review_action=review_action,
|
||||
association_candidate=association_candidate,
|
||||
)
|
||||
is_new_claim = claim is None
|
||||
before_json = self._serialize_claim(claim) if claim is not None else None
|
||||
application_link_block_result = self._build_application_link_block_result(
|
||||
context_json=context_json,
|
||||
target_claim=claim,
|
||||
)
|
||||
if application_link_block_result is not None:
|
||||
return application_link_block_result
|
||||
if is_new_claim:
|
||||
existing_draft_count = self._count_draft_claims_for_owner(
|
||||
employee=employee,
|
||||
user_id=user_id,
|
||||
)
|
||||
if existing_draft_count >= MAX_DRAFT_CLAIMS_PER_USER:
|
||||
return {
|
||||
"message": (
|
||||
f"你当前已保存 {MAX_DRAFT_CLAIMS_PER_USER} 个草稿,请先完成已保存的草稿,"
|
||||
"才能再次新建草稿。"
|
||||
),
|
||||
"draft_limit_reached": True,
|
||||
"draft_only": False,
|
||||
"status": "blocked",
|
||||
"draft_count": existing_draft_count,
|
||||
"max_draft_count": MAX_DRAFT_CLAIMS_PER_USER,
|
||||
}
|
||||
|
||||
amount = self._resolve_amount(ontology.entities, context_json=context_json)
|
||||
occurred_at = self._resolve_occurred_at(ontology, context_json=context_json)
|
||||
explicit_expense_type = self._resolve_explicit_review_expense_type(context_json)
|
||||
inferred_expense_type = self._resolve_expense_type(ontology.entities, context_json=context_json)
|
||||
locked_expense_type = explicit_expense_type
|
||||
if not locked_expense_type and claim is not None and review_action in DOCUMENT_ASSOCIATION_REVIEW_ACTIONS:
|
||||
locked_expense_type = str(claim.expense_type or "").strip()
|
||||
expense_type = locked_expense_type or inferred_expense_type
|
||||
location = self._resolve_location(message=message, context_json=context_json)
|
||||
reason = self._resolve_reason(
|
||||
message=message,
|
||||
context_json=context_json,
|
||||
allow_message_fallback=is_new_claim,
|
||||
)
|
||||
attachment_count = len(attachment_names) or self._resolve_attachment_count(context_json)
|
||||
|
||||
final_amount = amount if amount is not None else (claim.amount if claim is not None else Decimal("0.00"))
|
||||
final_occurred_at = (
|
||||
occurred_at if occurred_at is not None else (claim.occurred_at if claim is not None else datetime.now(UTC))
|
||||
)
|
||||
final_expense_type = expense_type or (claim.expense_type if claim is not None else "other")
|
||||
final_location = location or (claim.location if claim is not None else "待补充")
|
||||
final_reason = reason or (claim.reason if claim is not None else "待补充")
|
||||
final_attachment_count = (
|
||||
attachment_count if attachment_count > 0 else int(claim.invoice_count or 0) if claim is not None else 0
|
||||
)
|
||||
final_risk_flags = self._merge_persistent_claim_risk_flags(
|
||||
existing_flags=list(claim.risk_flags_json or []) if claim is not None else [],
|
||||
next_flags=list(ontology.risk_flags),
|
||||
)
|
||||
final_risk_flags = self._merge_application_link_flag(
|
||||
final_risk_flags,
|
||||
context_json=context_json,
|
||||
)
|
||||
if context_documents or attachment_names:
|
||||
document_specs = self._build_context_item_specs(
|
||||
context_documents=context_documents,
|
||||
attachment_names=attachment_names,
|
||||
occurred_at=final_occurred_at,
|
||||
expense_type=final_expense_type,
|
||||
amount=final_amount,
|
||||
reason=final_reason,
|
||||
location=final_location,
|
||||
context_json=context_json,
|
||||
employee_grade=str(employee.grade or "").strip() if employee is not None else "",
|
||||
user_id=user_id,
|
||||
)
|
||||
else:
|
||||
document_specs = []
|
||||
|
||||
if claim is not None and review_action == "link_to_existing_draft" and document_specs:
|
||||
duplicate_result = self._build_duplicate_attachment_block_result(
|
||||
claim=claim,
|
||||
document_specs=document_specs,
|
||||
context_documents=context_documents,
|
||||
)
|
||||
if duplicate_result is not None:
|
||||
return duplicate_result
|
||||
|
||||
try:
|
||||
if claim is None:
|
||||
claim = ExpenseClaim(
|
||||
claim_no=self._generate_claim_no(final_occurred_at),
|
||||
employee_id=employee.id if employee is not None else None,
|
||||
employee_name=draft_owner_name,
|
||||
department_id=employee.organization_unit_id if employee is not None else None,
|
||||
department_name=self._resolve_department_name(
|
||||
employee=employee,
|
||||
context_json=context_json,
|
||||
),
|
||||
project_code=self._resolve_project_code(ontology.entities),
|
||||
expense_type=final_expense_type,
|
||||
reason=final_reason,
|
||||
location=final_location,
|
||||
amount=final_amount,
|
||||
currency="CNY",
|
||||
invoice_count=final_attachment_count,
|
||||
occurred_at=final_occurred_at,
|
||||
status="draft",
|
||||
approval_stage="待提交",
|
||||
risk_flags_json=final_risk_flags,
|
||||
)
|
||||
self.db.add(claim)
|
||||
else:
|
||||
claim.employee_id = employee.id if employee is not None else claim.employee_id
|
||||
claim.employee_name = (
|
||||
employee.name
|
||||
if employee is not None
|
||||
else self._resolve_employee_name(
|
||||
ontology=ontology,
|
||||
context_json=context_json,
|
||||
user_id=user_id,
|
||||
fallback=claim.employee_name,
|
||||
)
|
||||
)
|
||||
claim.department_id = employee.organization_unit_id if employee is not None else claim.department_id
|
||||
claim.department_name = self._resolve_department_name(
|
||||
employee=employee,
|
||||
context_json=context_json,
|
||||
fallback=claim.department_name,
|
||||
)
|
||||
claim.project_code = self._resolve_project_code(ontology.entities) or claim.project_code
|
||||
claim.expense_type = final_expense_type
|
||||
claim.reason = final_reason
|
||||
claim.location = final_location
|
||||
claim.amount = final_amount
|
||||
claim.invoice_count = final_attachment_count
|
||||
claim.occurred_at = final_occurred_at
|
||||
claim.status = "draft"
|
||||
claim.approval_stage = "待提交"
|
||||
claim.risk_flags_json = final_risk_flags
|
||||
|
||||
self.db.flush()
|
||||
skip_primary_item = self._should_skip_application_link_placeholder_item(
|
||||
claim=claim,
|
||||
context_json=context_json,
|
||||
document_specs=document_specs,
|
||||
attachment_count=attachment_count,
|
||||
amount=amount,
|
||||
)
|
||||
if document_specs and (is_new_claim or review_action in DOCUMENT_ASSOCIATION_REVIEW_ACTIONS):
|
||||
if review_action == "link_to_existing_draft" and claim.items:
|
||||
self._append_document_items(
|
||||
claim=claim,
|
||||
item_specs=document_specs,
|
||||
)
|
||||
else:
|
||||
self._replace_claim_items(
|
||||
claim=claim,
|
||||
item_specs=document_specs,
|
||||
)
|
||||
self._sync_claim_from_items(claim)
|
||||
elif skip_primary_item:
|
||||
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,
|
||||
occurred_at=final_occurred_at,
|
||||
expense_type=final_expense_type,
|
||||
amount=final_amount,
|
||||
reason=final_reason,
|
||||
location=final_location,
|
||||
attachment_names=attachment_names,
|
||||
)
|
||||
self._sync_claim_from_items(claim)
|
||||
if locked_expense_type:
|
||||
claim.expense_type = locked_expense_type
|
||||
self.db.commit()
|
||||
self.db.refresh(claim)
|
||||
except IntegrityError as exc:
|
||||
self.db.rollback()
|
||||
if (
|
||||
is_new_claim
|
||||
and retry_count < MAX_CLAIM_NO_RETRY_ATTEMPTS
|
||||
and self._is_claim_no_conflict_error(exc)
|
||||
):
|
||||
retry_context = dict(context_json)
|
||||
retry_context["_claim_no_retry_count"] = retry_count + 1
|
||||
return self.upsert_draft_from_ontology(
|
||||
run_id=run_id,
|
||||
user_id=user_id,
|
||||
message=message,
|
||||
ontology=ontology,
|
||||
context_json=retry_context,
|
||||
)
|
||||
raise
|
||||
|
||||
except Exception:
|
||||
self.db.rollback()
|
||||
raise
|
||||
|
||||
self.audit_service.log_action(
|
||||
actor=user_id or claim.employee_name or "anonymous",
|
||||
action="expense_claim.draft_upsert",
|
||||
resource_type="expense_claim",
|
||||
resource_id=claim.id,
|
||||
before_json=before_json,
|
||||
after_json=self._serialize_claim(claim),
|
||||
request_id=run_id,
|
||||
)
|
||||
|
||||
return {
|
||||
"message": (
|
||||
f"已{'创建' if is_new_claim else '更新'}报销草稿 {claim.claim_no},当前状态为 draft。"
|
||||
"请核对识别结果,确认无误后继续提交。"
|
||||
),
|
||||
"draft_only": True,
|
||||
"claim_id": claim.id,
|
||||
"claim_no": claim.claim_no,
|
||||
"status": claim.status,
|
||||
"amount": float(claim.amount),
|
||||
"invoice_count": int(claim.invoice_count or 0),
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user