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

@@ -32,7 +32,7 @@ def build_session() -> Session:
def test_employee_directory_seeds_rich_employee_data() -> None:
with build_session() as db:
service = EmployeeService(db)
service = EmployeeService(db, tenant_id="default")
employees = service.list_employees()
meta = service.get_employee_meta()
@@ -59,7 +59,7 @@ def test_employee_directory_seeds_rich_employee_data() -> None:
def test_employee_detail_contains_department_and_roles() -> None:
with build_session() as db:
service = EmployeeService(db)
service = EmployeeService(db, tenant_id="default")
employee = service.list_employees()[0]
detail = service.get_employee(employee.id)
@@ -72,7 +72,7 @@ def test_employee_detail_contains_department_and_roles() -> None:
def test_update_employee_persists_changes_and_hashes_password() -> None:
with build_session() as db:
service = EmployeeService(db)
service = EmployeeService(db, tenant_id="default")
employee = service.list_employees()[0]
updated = service.update_employee(
@@ -115,7 +115,7 @@ def test_update_employee_persists_changes_and_hashes_password() -> None:
def test_disable_employee_marks_status_and_logs_change() -> None:
with build_session() as db:
service = EmployeeService(db)
service = EmployeeService(db, tenant_id="default")
employee = next(item for item in service.list_employees() if item.status != "停用")
updated = service.disable_employee(employee.id)
@@ -131,7 +131,7 @@ def test_disable_employee_marks_status_and_logs_change() -> None:
def test_enable_employee_restores_status_and_logs_change() -> None:
with build_session() as db:
service = EmployeeService(db)
service = EmployeeService(db, tenant_id="default")
employee = next(item for item in service.list_employees() if item.status != "停用")
service.disable_employee(employee.id)
@@ -148,7 +148,7 @@ def test_enable_employee_restores_status_and_logs_change() -> None:
def test_profile_repairs_do_not_run_on_every_list() -> None:
with build_session() as db:
service = EmployeeService(db)
service = EmployeeService(db, tenant_id="default")
employee = service.list_employees()[0]
updated = service.update_employee(
@@ -163,7 +163,7 @@ def test_profile_repairs_do_not_run_on_every_list() -> None:
def test_role_update_appends_recent_history() -> None:
with build_session() as db:
service = EmployeeService(db)
service = EmployeeService(db, tenant_id="default")
employee = service.list_employees()[0]
current_codes = list(employee.roleCodes)
next_codes = ["finance", "user"] if "finance" not in current_codes else ["user"]
@@ -175,7 +175,7 @@ def test_role_update_appends_recent_history() -> None:
def test_employee_change_logs_keep_only_latest_five() -> None:
with build_session() as db:
service = EmployeeService(db)
service = EmployeeService(db, tenant_id="default")
employee = service.list_employees()[0]
persisted = db.get(Employee, employee.id)
assert persisted is not None
@@ -198,7 +198,7 @@ def test_employee_change_logs_keep_only_latest_five() -> None:
def test_employee_meta_includes_organization_options() -> None:
with build_session() as db:
service = EmployeeService(db)
service = EmployeeService(db, tenant_id="default")
meta = service.get_employee_meta()
assert meta.organizationOptions
@@ -215,7 +215,7 @@ def test_employee_meta_includes_organization_options() -> None:
def test_employee_directory_normalizes_legacy_departments() -> None:
with build_session() as db:
service = EmployeeService(db)
service = EmployeeService(db, tenant_id="default")
service.list_employees()
legacy_department = OrganizationUnit(
@@ -239,11 +239,17 @@ def test_employee_directory_normalizes_legacy_departments() -> None:
assert refreshed.organization is not None
assert refreshed.organization.code == "TECH-DEPT"
assert "RND-CENTER" not in {item.code for item in meta.organizationOptions}
persisted = db.execute(
select(Employee).where(Employee.employee_no == "E11745")
).scalar_one()
assert persisted.tenant_id == "default"
assert persisted.organization_unit is not None
assert persisted.organization_unit.unit_code == "TECH-DEPT"
def test_update_employee_changes_organization() -> None:
with build_session() as db:
service = EmployeeService(db)
service = EmployeeService(db, tenant_id="default")
employee = service.list_employees()[0]
organizations = service.repository.list_organization_units()
current_code = employee.organization.code if employee.organization else None
@@ -266,7 +272,7 @@ def test_update_employee_changes_organization() -> None:
def test_update_employee_rejects_unknown_organization() -> None:
with build_session() as db:
service = EmployeeService(db)
service = EmployeeService(db, tenant_id="default")
employee = service.list_employees()[0]
with pytest.raises(ValueError, match="部门编码"):
@@ -278,7 +284,7 @@ def test_update_employee_rejects_unknown_organization() -> None:
def test_update_employee_changes_manager() -> None:
with build_session() as db:
service = EmployeeService(db)
service = EmployeeService(db, tenant_id="default")
employees = service.list_employees()
employee = employees[0]
manager = next(item for item in employees if item.id != employee.id)
@@ -302,7 +308,7 @@ def test_format_history_datetime_uses_local_timezone_without_seconds() -> None:
def test_update_employee_rejects_invalid_date_format() -> None:
with build_session() as db:
service = EmployeeService(db)
service = EmployeeService(db, tenant_id="default")
employee = service.list_employees()[0]
with pytest.raises(ValueError, match="出生日期格式必须为 YYYY-MM-DD。"):