PayrollManagementSystem/backend/tests/integration/test_dashboard.py

233 lines
8.2 KiB
Python
Raw Normal View History

"""工作台集成测试(M4 ⑧)。
守的口径:
- 权限:普通用户 403 且文案「无权访问工作台」
- 总览:绩效合计/人数与台账一致;月份参数非法 400
- 待办:月结提交后出现待审批;回款未指定批次可命中;同步未建时显式降级
- 风险预警:审批超时命中、参数关闭后不产生条目、阈值参数化生效
- 最近动态:来源操作日志(写操作可见)
待办与预警为实时派生(不落表),测试只验证查询结果,不清理派生数据。
"""
import uuid
from datetime import date, datetime, timedelta
from decimal import Decimal
import pytest
from fastapi.testclient import TestClient
pytestmark = pytest.mark.integration
_DASHBOARD = "/api/v1/dashboard"
_PERSONNEL = "/api/v1/base/personnel"
_BUSINESSES = "/api/v1/business/businesses"
_SHIPMENTS = "/api/v1/business/shipments"
_RECEIPTS = "/api/v1/business/receipts"
_CALC = "/api/v1/calc"
_SETTLEMENTS = "/api/v1/settlements"
_PARAMS = "/api/v1/system/params"
def _role_id(code: str) -> int:
from sqlalchemy import select
from app.core.db import get_session_factory
from app.models import BusinessRole
factory = get_session_factory()
with factory() as session:
return int(
session.execute(
select(BusinessRole.id).where(
BusinessRole.role_code == code, BusinessRole.status == 1
)
).scalar_one()
)
def _person(client: TestClient, headers: dict) -> dict:
resp = client.post(
_PERSONNEL,
json={
"personnel_name": f"t_工作台_{uuid.uuid4().hex[:8]}",
"business_role_id": _role_id("salesperson"),
"salary_standard": "3000.00",
},
headers=headers,
)
assert resp.status_code == 200, resp.text
return resp.json()["data"]
def _chain(client: TestClient, headers: dict, *, assign_shipment: bool = True) -> dict:
"""客户/业务(100×100,成本 3000)→ 发货(60 天前)→ 回款 → 核算。"""
from tests.integration.factories import business_item, business_payload, customer
cust = customer(client, headers)
person = _person(client, headers)
resp = client.post(
_BUSINESSES,
json=business_payload(
cust["id"],
owner_personnel_id=person["id"],
items=[business_item(cost_amount="3000.00")],
),
headers=headers,
)
assert resp.status_code == 200, resp.text
business = resp.json()["data"]
ship_date = (date.today() - timedelta(days=60)).isoformat()
resp = client.post(
_SHIPMENTS,
json={
"business_id": business["id"],
"ship_date": ship_date,
"items": [{"product_name": "t_工作台发货", "quantity": "1", "sale_price": "1.00"}],
},
headers=headers,
)
assert resp.status_code == 200, resp.text
shipment = resp.json()["data"]
payload = {
"business_id": business["id"],
"receipt_date": date.today().isoformat(),
"amount": "10000.00",
}
if assign_shipment:
payload["shipment_id"] = shipment["id"]
resp = client.post(_RECEIPTS, json=payload, headers=headers)
assert resp.status_code == 200, resp.text
receipt = resp.json()["data"]
if assign_shipment:
resp = client.post(f"{_CALC}/receipts/{receipt['id']}/calc", headers=headers)
assert resp.status_code == 200, resp.text
return {"business": business, "person": person, "receipt": receipt}
def _month() -> str:
return date.today().strftime("%Y-%m")
def _dashboard(client: TestClient, headers: dict, month: str | None = None) -> dict:
params = {"month": month} if month else {}
resp = client.get(_DASHBOARD, params=params, headers=headers)
assert resp.status_code == 200, resp.text
return resp.json()["data"]
def test_权限_普通用户403文案(client: TestClient, normal: dict):
resp = client.get(_DASHBOARD, headers=normal["headers"])
assert resp.status_code == 403
assert resp.json()["message"] == "无权访问工作台"
def test_总览_绩效合计与台账一致且非法月份400(client: TestClient, admin: dict):
_chain(client, admin["headers"])
data = _dashboard(client, admin["headers"], _month())
from sqlalchemy import func, select
from app.core.db import get_session_factory
from app.models import PerformanceLedger
factory = get_session_factory()
with factory() as session:
expected = Decimal(
str(
session.execute(
select(
func.coalesce(func.sum(PerformanceLedger.performance_amount), 0)
).where(
PerformanceLedger.settlement_month == _month(),
PerformanceLedger.calc_type == 1,
PerformanceLedger.is_active == 1,
)
).scalar_one()
)
)
assert Decimal(str(data["overview"]["performance_total"])) == expected
assert data["overview"]["person_count"] >= 1
assert len(data["overview"]["trend"]) == 6
resp = client.get(_DASHBOARD, params={"month": "2026-13"}, headers=admin["headers"])
assert resp.status_code == 400
def test_待办_待审批与未指定批次与同步降级(client: TestClient, admin: dict):
# 先造一笔已核算业务(月结生成要求有可结算记录),再补一笔未指定批次的回款
_chain(client, admin["headers"], assign_shipment=True)
ctx = _chain(client, admin["headers"], assign_shipment=False)
# 月结提交 → 产生待审批单
resp = client.post(
f"{_SETTLEMENTS}/generate", json={"month": _month()}, headers=admin["headers"]
)
assert resp.status_code == 200, resp.text
settlement_id = resp.json()["data"]["id"]
resp = client.post(
f"{_SETTLEMENTS}/{settlement_id}/submit", headers=admin["headers"]
)
assert resp.status_code == 200, resp.text
data = _dashboard(client, admin["headers"])
assert data["todos"]["pending_approval_total"] >= 1
assert any(
item["order_type"] == 2 for item in data["todos"]["pending_approvals"]
)
assert data["todos"]["unassigned_receipt_total"] >= 1
assert data["todos"]["unassigned_receipts"][0]["receipt_id"] == ctx["receipt"]["id"]
# ⑥ 未建:显式降级
assert data["todos"]["sync_available"] is False
def test_预警_审批超时命中且参数关闭后不产生(client: TestClient, admin: dict):
_chain(client, admin["headers"])
resp = client.post(
f"{_SETTLEMENTS}/generate", json={"month": _month()}, headers=admin["headers"]
)
settlement_id = resp.json()["data"]["id"]
client.post(f"{_SETTLEMENTS}/{settlement_id}/submit", headers=admin["headers"])
# 把待审批单提交时间改到 4 天前(阈值默认 3 天)
from sqlalchemy import select, update
from app.core.db import get_session_factory
from app.models import ApprovalOrder
factory = get_session_factory()
with factory() as session:
session.execute(
update(ApprovalOrder)
.where(ApprovalOrder.status.in_((1, 2)))
.values(create_time=datetime.now() - timedelta(days=4))
)
session.commit()
data = _dashboard(client, admin["headers"])
assert data["warnings"]["enabled"] is True
assert any(item["code"] == "approval_timeout" for item in data["warnings"]["items"])
# 关闭预警开关 → 不产生条目
resp = client.post(
f"{_PARAMS}/changes",
json={
"change_type": 1,
"remark": "e2e 工作台测试:关闭风险预警",
"items": [{"param_code": "warning.enabled", "dimension_value": "", "value": "0"}],
},
headers=admin["headers"],
)
assert resp.status_code == 200, resp.text
data = _dashboard(client, admin["headers"])
assert data["warnings"]["enabled"] is False
assert data["warnings"]["items"] == []
def test_动态_来源操作日志(client: TestClient, admin: dict):
_chain(client, admin["headers"])
data = _dashboard(client, admin["headers"])
assert data["activities"], "工作台动态为空:写操作应产生操作日志"
assert {"module", "action", "summary", "create_time"} <= set(data["activities"][0])