feat(核算)!: 添加 T+ 正式核算与采集托管
补齐源快照、资料预演、正式导入及工资核算的依赖链路。 新增版本化输入、逐段 FIFO、生成锁和结构化当月调整, 并提供默认关闭的周期采集、有限重试及 Windows 托管。 原始客户资料、私有配置及首页改动不纳入本次提交。 BREAKING CHANGE: 升级前需应用新增迁移,并通过 PAYROLL_MAKER_USER_ID 绑定实际工资制作人,否则业务只读。 旧单批次核算默认关闭;历史兼容需显式启用 PAYROLL_ALLOW_LEGACY_CALCULATION。生产启用和真实金额待验收。
This commit is contained in:
1 parent
1b074576fe
commit
108bf87225
154 files changed
+9515
-645
No files matched your search
@@ -0,0 +1,53 @@
|
||||
"""工作区只使用成功完整读取发布的当前源快照。"""
|
||||
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.models import TPlusSourceHead, TPlusSyncBatch, TPlusSyncRecord
|
||||
from app.services.tplus.workbench import WorkbenchError
|
||||
|
||||
|
||||
def source_statement(session, source, entity):
|
||||
"""只发布最后一次完整成功读取;中断覆盖的指针不得部分发布。"""
|
||||
batches = session.scalars(
|
||||
select(TPlusSyncBatch)
|
||||
.where(
|
||||
TPlusSyncBatch.source_database == source,
|
||||
TPlusSyncBatch.sync_mode == "FULL",
|
||||
)
|
||||
.order_by(TPlusSyncBatch.id.desc())
|
||||
)
|
||||
batch_no = None
|
||||
for batch in batches:
|
||||
report = batch.audit_result_json or {}
|
||||
if entity in (batch.entity_types_json or []) and batch.status != "SUCCEEDED":
|
||||
raise WorkbenchError("SOURCE_REFRESH_REQUIRED")
|
||||
if entity in report.get("observed_entities", []):
|
||||
batch_no = batch.batch_no
|
||||
break
|
||||
if batch_no is None:
|
||||
raise WorkbenchError("SOURCE_REFRESH_REQUIRED")
|
||||
return (
|
||||
select(TPlusSyncRecord)
|
||||
.join(TPlusSourceHead, TPlusSourceHead.record_id == TPlusSyncRecord.id)
|
||||
.where(
|
||||
TPlusSourceHead.source_database == source,
|
||||
TPlusSourceHead.entity_type == entity,
|
||||
TPlusSourceHead.batch_no == batch_no,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def source_records(session, source, entity, ids=None, parent_ids=None):
|
||||
"""旧暂存未建立当前指针时要求重新读取,禁止 max(record.id) 猜最新值。"""
|
||||
if ids == [] or parent_ids == []:
|
||||
return []
|
||||
statement = source_statement(session, source, entity)
|
||||
if ids:
|
||||
statement = statement.where(TPlusSourceHead.source_id.in_(ids))
|
||||
if parent_ids is not None:
|
||||
statement = statement.where(
|
||||
TPlusSyncRecord.payload_json["source_parent_id"]
|
||||
.as_integer()
|
||||
.in_([int(v) for v in parent_ids])
|
||||
)
|
||||
return list(session.scalars(statement.order_by(TPlusSyncRecord.source_id)))
|
||||
Reference in new issue
Block a user