feat(platform): close AI expense value loop

Add tenant-safe value, telemetry, connector, commercial, and production-readiness foundations.
This commit is contained in:
caoxiaozhu
2026-07-17 14:14:08 +08:00
parent 242d68c36f
commit 787bc3a481
507 changed files with 82072 additions and 6344 deletions

View File

@@ -439,26 +439,34 @@ def test_claim_standard_adjustment_endpoint_recalculates_and_marks_reviewer_noti
"item_id": item_id,
"title": "住宿超标待说明",
"risk": "住宿票据金额超过职级标准。",
"application_days": 2,
"original_amount": "1000.00",
"reimbursable_amount": "1000.00",
"application_days": 99,
"original_amount": "1.00",
"reimbursable_amount": "999999.00",
}
]
],
"request_id": "standard-adjustment-endpoint-1",
},
headers={"x-auth-username": "emp-1", "x-auth-name": "Zhang San", "x-auth-grade": "P4"},
)
assert response.status_code == 200
payload = response.json()
assert payload["amount"] == "900.00"
assert payload["amount"] == "450.00"
standard_flag = next(
flag
for flag in payload["risk_flags_json"]
if isinstance(flag, dict) and flag.get("source") == "reimbursement_standard_adjustment"
)
assert standard_flag["original_amount"] == "1000.00"
assert standard_flag["reimbursable_amount"] == "900.00"
assert standard_flag["employee_absorbed_amount"] == "100.00"
assert standard_flag["reimbursable_amount"] == "450.00"
assert standard_flag["employee_absorbed_amount"] == "550.00"
assert standard_flag["calculation_source"] == "server_policy"
assert standard_flag["policy_days"] == 1
assert standard_flag["policy_grade"] == "P4"
assert standard_flag["policy_matched_city"] == "北京"
assert standard_flag["policy_hotel_rate"] == "450.00"
assert standard_flag["policy_rule_name"]
assert standard_flag["policy_rule_version"]
assert standard_flag["visibility_scope"] == "leader"
@@ -781,6 +789,82 @@ def test_approve_claim_endpoint_routes_direct_manager_claim_to_finance_review()
assert ledgers[0].completed_at is not None
def test_claim_owner_cannot_discover_or_operate_manager_approval_task() -> None:
client, session_factory = build_client()
with session_factory() as db:
superior = Employee(
id="manager-self-approval-superior",
employee_no="E-SELF-APPROVAL-SUPERIOR",
name="王总",
email="superior-self-approval-api@example.com",
)
manager = Employee(
id="manager-self-approval-owner",
employee_no="E-SELF-APPROVAL-OWNER",
name="李经理",
email="manager-self-approval-api@example.com",
manager=superior,
)
claim = ExpenseClaim(
id="claim-self-approval-api",
claim_no="EXP-SELF-APPROVAL-API",
employee_id=manager.id,
employee_name=manager.name,
department_name="市场部",
expense_type="transport",
reason="交通报销",
location="上海",
amount=Decimal("66.00"),
currency="CNY",
invoice_count=1,
occurred_at=datetime(2026, 5, 13, tzinfo=UTC),
submitted_at=datetime(2026, 5, 13, 10, 0, tzinfo=UTC),
status="submitted",
approval_stage="直属领导审批",
risk_flags_json=[],
)
db.add_all([superior, manager, claim])
db.commit()
headers = {
"X-Auth-Username": "manager-self-approval-api@example.com",
"X-Auth-Name": "manager-self-approval-api@example.com",
"X-Auth-Role-Codes": "manager",
}
approve_response = client.post(
"/api/v1/reimbursements/claims/claim-self-approval-api/approve",
json={
"opinion": "同意",
"request_id": "claim-self-approval-approve",
"expected_status": "submitted",
"expected_approval_stage": "直属领导审批",
},
headers=headers,
)
return_response = client.post(
"/api/v1/reimbursements/claims/claim-self-approval-api/return",
json={
"reason": "退回",
"request_id": "claim-self-approval-return",
"expected_status": "submitted",
"expected_approval_stage": "直属领导审批",
},
headers=headers,
)
assert approve_response.status_code == 404
assert return_response.status_code == 404
assert approve_response.json()["detail"] == "Approval task not found."
assert return_response.json()["detail"] == "Approval task not found."
with session_factory() as db:
claim = db.get(ExpenseClaim, "claim-self-approval-api")
assert claim is not None
assert claim.status == "submitted"
assert claim.approval_stage == "直属领导审批"
assert claim.risk_flags_json == []
assert db.scalar(select(func.count(ApprovalActionLedger.id))) == 0
def test_approve_claim_endpoint_blocks_open_high_risk_with_machine_readable_detail() -> None:
client, session_factory = build_client()
with session_factory() as db: