feat: 重构报销单服务并完善前端提交与审核交互
重构 expense_claims 服务模块结构并优化差旅票据审核逻辑, 增强用户代理服务的票据类型识别,前端报销创建页面拆分为 附件模型和会话模型模块,重构提交编排器和草稿关联确认流 程,更新知识库索引,补充单元测试。
This commit is contained in:
@@ -136,7 +136,9 @@ def test_save_or_submit_preview_does_not_create_claim_without_explicit_action()
|
||||
|
||||
assert result["preview_only"] is True
|
||||
assert result["status"] == "preview"
|
||||
assert "差旅费按“交通票据金额 + 住宿标准 × 出差天数 + 出差补贴 × 出差天数”估算" in result["message"]
|
||||
assert "报销测算参考:" in result["message"]
|
||||
assert "| 项目 | 当前信息 | 复核口径 |" in result["message"]
|
||||
assert "交通票据金额 + 住宿标准" not in result["message"]
|
||||
assert _count_claims(db) == before_count
|
||||
|
||||
|
||||
@@ -598,6 +600,91 @@ def test_upsert_draft_from_ontology_supports_link_or_create_for_multi_documents(
|
||||
assert float(new_claim.amount) == 50.5
|
||||
|
||||
|
||||
def test_link_existing_draft_blocks_duplicate_uploaded_invoice() -> None:
|
||||
user_id = "duplicate@example.com"
|
||||
|
||||
with build_session() as db:
|
||||
employee = Employee(
|
||||
employee_no="E5010",
|
||||
name="重复票据员工",
|
||||
email=user_id,
|
||||
)
|
||||
db.add(employee)
|
||||
db.flush()
|
||||
existing_claim = ExpenseClaim(
|
||||
claim_no="EXP-202605-021",
|
||||
employee_id=employee.id,
|
||||
employee_name="重复票据员工",
|
||||
department_name="销售部",
|
||||
project_code=None,
|
||||
expense_type="transport",
|
||||
reason="原有交通报销",
|
||||
location="上海",
|
||||
amount=Decimal("32.50"),
|
||||
currency="CNY",
|
||||
invoice_count=1,
|
||||
occurred_at=datetime(2026, 5, 13, tzinfo=UTC),
|
||||
status="draft",
|
||||
approval_stage="待提交",
|
||||
risk_flags_json=[],
|
||||
)
|
||||
existing_claim.items = [
|
||||
ExpenseClaimItem(
|
||||
claim_id=existing_claim.id,
|
||||
item_date=date(2026, 5, 13),
|
||||
item_type="transport",
|
||||
item_reason="原有交通报销",
|
||||
item_location="上海",
|
||||
item_amount=Decimal("32.50"),
|
||||
invoice_id="didi-trip.png",
|
||||
)
|
||||
]
|
||||
db.add(existing_claim)
|
||||
db.commit()
|
||||
|
||||
context_json = {
|
||||
"name": "重复票据员工",
|
||||
"review_action": "link_to_existing_draft",
|
||||
"draft_claim_id": existing_claim.id,
|
||||
"attachment_names": ["didi-trip.png"],
|
||||
"attachment_count": 1,
|
||||
"ocr_documents": [
|
||||
{
|
||||
"filename": "didi-trip.png",
|
||||
"summary": "滴滴出行 支付金额 32.50 元",
|
||||
"text": "滴滴出行 支付金额 32.50 元",
|
||||
"document_type": "taxi_receipt",
|
||||
"scene_code": "transport",
|
||||
"document_fields": [{"key": "amount", "label": "支付金额", "value": "32.50"}],
|
||||
}
|
||||
],
|
||||
}
|
||||
ontology = SemanticOntologyService(db).parse(
|
||||
OntologyParseRequest(
|
||||
query="把这张票据关联到已有草稿",
|
||||
user_id=user_id,
|
||||
context_json=context_json,
|
||||
)
|
||||
)
|
||||
|
||||
result = ExpenseClaimService(db).upsert_draft_from_ontology(
|
||||
run_id=ontology.run_id,
|
||||
user_id=user_id,
|
||||
message="把这张票据关联到已有草稿",
|
||||
ontology=ontology,
|
||||
context_json=context_json,
|
||||
)
|
||||
|
||||
db.refresh(existing_claim)
|
||||
assert result["duplicate_attachment_blocked"] is True
|
||||
assert result["submission_blocked"] is True
|
||||
assert "重复" in result["message"]
|
||||
assert "重新上传不同的票据" in result["message"]
|
||||
assert len(existing_claim.items) == 1
|
||||
assert existing_claim.invoice_count == 1
|
||||
assert float(existing_claim.amount) == 32.5
|
||||
|
||||
|
||||
def test_upsert_travel_draft_uses_ticket_item_types_and_auto_allowance() -> None:
|
||||
user_id = "travel-allowance@example.com"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user