PayrollManagementSystem/backend/app/services/tplus/source_heads.py
liuwenxin 108bf87225 feat(核算)!: 添加 T+ 正式核算与采集托管
补齐源快照、资料预演、正式导入及工资核算的依赖链路。
新增版本化输入、逐段 FIFO、生成锁和结构化当月调整,
并提供默认关闭的周期采集、有限重试及 Windows 托管。

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

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

60 lines
2.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""保留成功读取实际观察到的当前版本,不把历史最大ID当成当前值。"""
from sqlalchemy import inspect, select, tuple_
from app.models import TPlusSourceHead, TPlusSyncRecord
from app.services.tplus.staging import prepare_row
def observe_rows(session, batch, mapping, rows):
"""每页提交前记录所有读到的源版本,包括去重跳过的 A→B→A。"""
# V15 运行中的升级窗口继续允许原读取功能;工作区本身要求 V16。
if not inspect(session.connection()).has_table(TPlusSourceHead.__tablename__):
return
prepared = [
prepare_row(batch.batch_no, mapping.entity_type, mapping.source_table, r)
for r in rows
if r.get("id") is not None
]
wanted = [(r["source_id"], r["row_hash"]) for r in prepared]
if not wanted:
return
records = {
(r.source_id, r.row_hash): r.id
for r in session.scalars(
select(TPlusSyncRecord).where(
TPlusSyncRecord.entity_type == mapping.entity_type,
tuple_(TPlusSyncRecord.source_id, TPlusSyncRecord.row_hash).in_(wanted),
)
)
}
source = batch.source_database
if not source:
raise ValueError("源账套身份缺失,不能发布当前快照")
current = {
h.source_id: h
for h in session.scalars(
select(TPlusSourceHead).where(
TPlusSourceHead.source_database == source,
TPlusSourceHead.entity_type == mapping.entity_type,
TPlusSourceHead.source_id.in_([v[0] for v in wanted]),
)
)
}
for row in prepared:
record_id = records.get((row["source_id"], row["row_hash"]))
if record_id is None:
continue
head = current.get(row["source_id"])
if head is None:
head = TPlusSourceHead(
source_database=source,
entity_type=mapping.entity_type,
source_id=row["source_id"],
record_id=record_id,
batch_no=batch.batch_no,
)
session.add(head)
current[row["source_id"]] = head
head.record_id, head.batch_no = record_id, batch.batch_no