打通 T+ 快照、业务、发货、回款和核算准备链路,保留来源差异并提供待补状态。 新增业务自动接入任务、跨平台读取服务托管、权限复核、来源关联和前端核对页面。 补充迁移、隔离集成测试、浏览器模拟验收及角色授权边界修复。
67 lines
2.5 KiB
Python
67 lines
2.5 KiB
Python
"""工作区只使用成功完整读取发布的当前源快照。"""
|
|
|
|
from sqlalchemy import select
|
|
|
|
from app.models import TPlusSourceHead, TPlusSyncBatch, TPlusSyncRecord
|
|
from app.services.tplus.workbench import WorkbenchError
|
|
|
|
|
|
def published_batch(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 batch_no
|
|
|
|
|
|
def source_manifest(session, source):
|
|
"""核算与接入共同使用的完整来源批次清单,包含空实体。"""
|
|
entities = ("personnel", "customers", "products", "businesses", "business_items",
|
|
"shipments", "shipment_items", "receipts")
|
|
return {entity: published_batch(session, source, entity) for entity in entities}
|
|
|
|
|
|
def source_statement(session, source, entity):
|
|
"""只发布最后一次完整成功读取;中断覆盖的指针不得部分发布。"""
|
|
batch_no = published_batch(session, source, entity)
|
|
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)))
|