feat(核算)!: 添加 T+ 正式核算与采集托管

补齐源快照、资料预演、正式导入及工资核算的依赖链路。
新增版本化输入、逐段 FIFO、生成锁和结构化当月调整,
并提供默认关闭的周期采集、有限重试及 Windows 托管。

原始客户资料、私有配置及首页改动不纳入本次提交。

BREAKING CHANGE: 升级前需应用新增迁移,并通过
PAYROLL_MAKER_USER_ID 绑定实际工资制作人,否则业务只读。
旧单批次核算默认关闭;历史兼容需显式启用
PAYROLL_ALLOW_LEGACY_CALCULATION。生产启用和真实金额待验收。
This commit is contained in:
liuwenxin committed 2026-11-02 09:42:53 +08:00
1 parent 1b074576fe
commit 108bf87225
154 files changed
+9515 -645

No files matched your search

+34 -1
View File
@@ -19,6 +19,7 @@
评级用例会删改种子构造重叠/相邻场景,恢复保证跨文件幂等
"""
import os
import uuid
from decimal import Decimal
from pathlib import Path
@@ -81,7 +82,12 @@ def _purge_test_data() -> None:
``t\\_%`` 中的下划线做了转义 —— 不转义时 `_` 是 LIKE 的单字符通配,
会把 tn... 这类无关账号一并卷入。
"""
from app.core.config import get_settings
from tests.isolation import ensure_test_database
ensure_test_database(get_settings().mysql_database, os.environ.get("PAYROLL_TEST_DATABASE"))
factory = get_session_factory()
ensure_test_database(factory.kw["bind"].url.database, os.environ.get("PAYROLL_TEST_DATABASE"))
with factory() as session:
users = list(
session.execute(select(User.id, User.username).where(User.username.like("t\\_%"))).all()
@@ -104,6 +110,11 @@ def _purge_test_data() -> None:
# ---- M3-core:核算台账 / 月结 / 调整单(必须先于回款/业务/人员删除——
# ledger.receipt_id/personnel_id/settlement_id 外键 RESTRICT 会挡住后面
# 的单据与人员清理;仅测试写入,全量清)----
from app.models import PayrollAllocation, PayrollInput, PayrollMonth, TPlusAutomationState
session.execute(delete(TPlusAutomationState))
session.execute(delete(PayrollAllocation))
session.execute(delete(PayrollInput))
session.execute(delete(PayrollMonth))
session.execute(delete(PerformanceLedger))
session.execute(delete(SettlementItem))
session.execute(delete(MonthlySettlement))
@@ -216,6 +227,11 @@ def _purge_test_data() -> None:
@pytest.fixture(autouse=True)
def _purge_after_each_test():
"""每个集成用例结束后兜底清理,避免残留影响后续用例的计数断言。"""
from app.core.config import get_settings
from tests.isolation import ensure_test_database
ensure_test_database(get_settings().mysql_database, os.environ.get("PAYROLL_TEST_DATABASE"))
ensure_test_database(get_session_factory().kw["bind"].url.database, os.environ.get("PAYROLL_TEST_DATABASE"))
yield
_purge_test_data()
@@ -311,9 +327,11 @@ def make_role():
@pytest.fixture
def admin(make_user, auth_headers) -> dict:
def admin(make_user, auth_headers, monkeypatch) -> dict:
"""管理员账号(含请求头)。"""
user = make_user([ROLE_ADMIN])
from app.core.config import get_settings
monkeypatch.setattr(get_settings(), "payroll_maker_user_id", user["id"])
user["headers"] = auth_headers(user)
return user
@@ -357,3 +375,18 @@ def tplus_tasks(monkeypatch):
db.execute(delete(TPlusSyncError).where(TPlusSyncError.batch_no.in_(numbers)))
db.execute(delete(TPlusSyncBatch).where(TPlusSyncBatch.batch_no.in_(numbers)))
db.commit()
@pytest.fixture
def legacy_calculation(monkeypatch):
"""明确启用旧引擎兼容回归;生产和正式工资测试默认关闭。"""
from app.core.config import get_settings
monkeypatch.setattr(get_settings(), "payroll_allow_legacy_calculation", True)
@pytest.fixture
def writer(normal, admin, monkeypatch):
"""指定一个普通角色账号为工资制作人,证明身份授权与管理员角色无关。"""
from app.core.config import get_settings
monkeypatch.setattr(get_settings(), "payroll_maker_user_id", normal["id"])
return normal