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

@@ -26,6 +26,7 @@ from app.test_helpers.db import build_in_memory_session_factory
def seed_employee_and_application(db: Session) -> None:
employee = Employee(
id="emp-linked-draft-fast",
tenant_id="tenant-a",
employee_no="E10001",
name="张三",
email="zhangsan@example.com",
@@ -34,6 +35,7 @@ def seed_employee_and_application(db: Session) -> None:
)
application = ExpenseClaim(
id="application-linked-draft-fast",
tenant_id="tenant-a",
claim_no="AP-202606-FAST",
employee_id=employee.id,
employee_name=employee.name,
@@ -76,9 +78,11 @@ def build_client(monkeypatch) -> tuple[TestClient, object]:
def test_linked_reimbursement_draft_job_runs_after_conversation_leaves(monkeypatch) -> None:
clear_linked_reimbursement_draft_jobs_for_tests()
captured_messages = []
captured_users = []
def fake_run(self, payload):
def fake_run(self, payload, *, current_user=None):
captured_messages.append(payload.message)
captured_users.append(current_user)
return OrchestratorResponse(
run_id="run-linked-draft-job",
conversation_id=None,
@@ -148,7 +152,13 @@ def test_linked_reimbursement_draft_job_runs_after_conversation_leaves(monkeypat
assert payload["status"] == "succeeded"
assert payload["draft_payload"]["claim_no"] == "RE-202606-009"
assert payload["run_id"] == "run-linked-draft-job"
assert captured_messages == ["我要报销\n用户选择报销场景:差旅费\n关联申请单AP-202606-001"]
assert captured_messages == [
"我要报销\n用户选择报销场景:差旅费\n关联申请单AP-202606-001"
]
assert len(captured_users) == 1
assert captured_users[0] is not None
assert captured_users[0].username == "zhangsan@example.com"
assert captured_users[0].tenant_id == "default"
finally:
clear_linked_reimbursement_draft_jobs_for_tests()
@@ -158,9 +168,11 @@ def test_linked_job_overrides_forged_tenant_and_rejects_same_owner_cross_tenant(
) -> None:
clear_linked_reimbursement_draft_jobs_for_tests()
captured_contexts = []
captured_users = []
def fake_run(self, payload):
def fake_run(self, payload, *, current_user=None):
captured_contexts.append(dict(payload.context_json or {}))
captured_users.append(current_user)
return OrchestratorResponse(
run_id="run-linked-tenant-guard",
conversation_id=None,
@@ -217,6 +229,10 @@ def test_linked_job_overrides_forged_tenant_and_rejects_same_owner_cross_tenant(
"session_type": "expense",
}
]
assert len(captured_users) == 1
assert captured_users[0] is not None
assert captured_users[0].tenant_id == "tenant-a"
assert captured_users[0].tenant_id != "default"
job_id = response.json()["job_id"]
cross_tenant_response = client.get(
f"/api/v1/reimbursements/linked-reimbursement-draft-jobs/{job_id}",
@@ -286,6 +302,7 @@ def test_linked_reimbursement_draft_job_uses_direct_save_path(monkeypatch) -> No
"x-auth-name": "Zhang San",
"x-auth-employee-no": "E10001",
"x-auth-role-codes": "user",
"x-auth-tenant-id": "tenant-a",
}
response = client.post(
@@ -345,11 +362,15 @@ def test_linked_reimbursement_draft_job_uses_direct_save_path(monkeypatch) -> No
clear_linked_reimbursement_draft_jobs_for_tests()
def test_linked_reimbursement_draft_job_uses_direct_save_path_with_application_no_only(monkeypatch) -> None:
def test_linked_reimbursement_draft_job_uses_direct_save_path_with_application_no_only(
monkeypatch,
) -> None:
clear_linked_reimbursement_draft_jobs_for_tests()
def fail_if_orchestrator_runs(self, payload):
raise AssertionError("linked draft job should resolve application no without full orchestrator")
raise AssertionError(
"linked draft job should resolve application no without full orchestrator"
)
monkeypatch.setattr(OrchestratorService, "run", fail_if_orchestrator_runs)
try:
@@ -362,6 +383,7 @@ def test_linked_reimbursement_draft_job_uses_direct_save_path_with_application_n
"x-auth-name": "Zhang San",
"x-auth-employee-no": "E10001",
"x-auth-role-codes": "user",
"x-auth-tenant-id": "tenant-a",
}
response = client.post(
@@ -409,9 +431,7 @@ def test_linked_reimbursement_draft_job_uses_direct_save_path_with_application_n
draft = db.get(ExpenseClaim, payload["draft_payload"]["claim_id"])
assert draft is not None
link_flag = next(
flag
for flag in draft.risk_flags_json
if flag.get("source") == "application_link"
flag for flag in draft.risk_flags_json if flag.get("source") == "application_link"
)
assert link_flag["application_claim_no"] == "AP-202606-FAST"
assert link_flag["application_claim_id"] == "application-linked-draft-fast"