123 lines
5.1 KiB
Python
123 lines
5.1 KiB
Python
|
|
"""同步管理只读查询;只公开受控元数据,不公开源数据和原异常。"""
|
||
|
|
|
||
|
|
from app.core.exceptions import NotFoundError
|
||
|
|
from app.core.response import PageResult
|
||
|
|
from app.core.tplus_db import resolve_tplus_settings
|
||
|
|
from app.repositories import tplus_repository as repository
|
||
|
|
from app.schemas.tplus import (
|
||
|
|
TPlusBatchDetailOut,
|
||
|
|
TPlusBatchOut,
|
||
|
|
TPlusErrorOut,
|
||
|
|
TPlusOverviewOut,
|
||
|
|
)
|
||
|
|
from app.services.tplus import task_service
|
||
|
|
from app.services.tplus.mapping import DETAIL_MAPPINGS, TPLUS_MAPPINGS
|
||
|
|
from app.services.tplus.relationship_audit import public_report
|
||
|
|
|
||
|
|
ERROR_MESSAGES = {
|
||
|
|
"READ_ERROR": "源数据读取失败,请检查只读连接和字段映射",
|
||
|
|
"STAGE_ERROR": "原始数据保存失败,请核对字段类型后重试",
|
||
|
|
"INTERRUPTED": "执行进程中断,可重新读取,已保存数据会自动去重",
|
||
|
|
"EXECUTION_ERROR": "任务执行失败,请检查只读连接和字段映射",
|
||
|
|
"TYPE_UNRESOLVED": "关联核验所需的单据类型缺失或不唯一",
|
||
|
|
}
|
||
|
|
_ENTITY_TYPES = {item.entity_type for item in (*TPLUS_MAPPINGS, *DETAIL_MAPPINGS)}
|
||
|
|
|
||
|
|
|
||
|
|
def batch_out(batch, active_parents: set[str]) -> TPlusBatchOut:
|
||
|
|
"""只使用元数据字段组装输出,历史批次范围保持未知。"""
|
||
|
|
entities = batch.entity_types_json
|
||
|
|
scope_valid = (
|
||
|
|
isinstance(entities, list)
|
||
|
|
and bool(entities)
|
||
|
|
and all(isinstance(item, str) and item in _ENTITY_TYPES for item in entities)
|
||
|
|
)
|
||
|
|
return TPlusBatchOut(
|
||
|
|
batch_no=batch.batch_no,
|
||
|
|
task_type="AUDIT" if batch.sync_mode == "AUDIT" else "READ",
|
||
|
|
status=batch.status,
|
||
|
|
entity_types=entities if scope_valid else None,
|
||
|
|
source_record_count=batch.source_record_count,
|
||
|
|
staged_record_count=batch.staged_record_count,
|
||
|
|
skipped_record_count=batch.skipped_record_count,
|
||
|
|
failed_record_count=batch.failed_record_count,
|
||
|
|
parent_batch_no=batch.parent_batch_no,
|
||
|
|
create_time=batch.create_time,
|
||
|
|
started_at=batch.started_at,
|
||
|
|
finished_at=batch.finished_at,
|
||
|
|
can_retry=batch.status in ("FAILED", "PARTIAL")
|
||
|
|
and (batch.sync_mode == "AUDIT" or (batch.sync_mode == "FULL" and scope_valid))
|
||
|
|
and batch.batch_no not in active_parents,
|
||
|
|
error_summary="任务有异常,请查看异常明细"
|
||
|
|
if batch.status in ("FAILED", "PARTIAL")
|
||
|
|
else None,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def get_batch(session, number: str) -> TPlusBatchDetailOut:
|
||
|
|
"""按需加载报告,未找到时统一返回 404。"""
|
||
|
|
batch = repository.get_batch(session, number, with_result=True)
|
||
|
|
if batch is None:
|
||
|
|
raise NotFoundError("同步批次不存在")
|
||
|
|
out = batch_out(batch, repository.active_retry_parents(session, [number]))
|
||
|
|
return TPlusBatchDetailOut(
|
||
|
|
**out.model_dump(), audit_result=public_report(batch.audit_result_json)
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def list_batches(session, query):
|
||
|
|
"""分页批次并批量查询重试状态。"""
|
||
|
|
rows, total = repository.list_batches(session, query)
|
||
|
|
active = repository.active_retry_parents(session, [row.batch_no for row in rows])
|
||
|
|
return PageResult[TPlusBatchOut](
|
||
|
|
records=[batch_out(row, active) for row in rows],
|
||
|
|
total=total,
|
||
|
|
page_num=query.page_num,
|
||
|
|
page_size=query.page_size,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def _safe_id(value) -> str | None:
|
||
|
|
if isinstance(value, str) and value.isascii() and value.isdigit() and len(value) <= 10:
|
||
|
|
return value if int(value) <= 2147483647 else None
|
||
|
|
return None
|
||
|
|
|
||
|
|
|
||
|
|
def list_errors(session, number, query):
|
||
|
|
"""错误按编码生成固定说明,避免驱动异常泄漏连接参数。"""
|
||
|
|
if repository.get_batch(session, number) is None:
|
||
|
|
raise NotFoundError("同步批次不存在")
|
||
|
|
rows, total = repository.list_errors(session, number, query)
|
||
|
|
records = [
|
||
|
|
TPlusErrorOut(
|
||
|
|
id=row.id,
|
||
|
|
entity_type=row.entity_type if row.entity_type in _ENTITY_TYPES else "system",
|
||
|
|
source_id=_safe_id(row.source_id),
|
||
|
|
error_code=row.error_code if row.error_code in ERROR_MESSAGES else "UNKNOWN",
|
||
|
|
error_message=ERROR_MESSAGES.get(row.error_code, "任务未完成,请核对配置后重试"),
|
||
|
|
retry_count=row.retry_count,
|
||
|
|
create_time=row.create_time,
|
||
|
|
)
|
||
|
|
for row in rows
|
||
|
|
]
|
||
|
|
return PageResult[TPlusErrorOut](
|
||
|
|
records=records, total=total, page_num=query.page_num, page_size=query.page_size
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def overview(session) -> TPlusOverviewOut:
|
||
|
|
"""只校验配置,不在页面查询期间建立 T+ 连接。"""
|
||
|
|
try:
|
||
|
|
resolve_tplus_settings(task_service.get_settings())
|
||
|
|
configured, message = True, "只读配置已就绪,实际连接结果以任务执行为准"
|
||
|
|
except RuntimeError:
|
||
|
|
configured, message = False, "T+ 只读配置未就绪,请联系管理员配置"
|
||
|
|
latest = repository.latest_audit(session)
|
||
|
|
return TPlusOverviewOut(
|
||
|
|
is_configured=configured,
|
||
|
|
configuration_message=message,
|
||
|
|
status_counts=repository.status_counts(session),
|
||
|
|
total_staged_records=repository.total_staged_records(session),
|
||
|
|
latest_audit=public_report(latest.audit_result_json) if latest else None,
|
||
|
|
)
|