打通 T+ 快照、业务、发货、回款和核算准备链路,保留来源差异并提供待补状态。 新增业务自动接入任务、跨平台读取服务托管、权限复核、来源关联和前端核对页面。 补充迁移、隔离集成测试、浏览器模拟验收及角色授权边界修复。
115 lines
4.6 KiB
Python
115 lines
4.6 KiB
Python
"""T+ 基础导入和预演的管理员入口;HTTP 不执行长查询或核算。"""
|
||
|
||
from typing import Annotated
|
||
|
||
from fastapi import APIRouter, Path, Query
|
||
from starlette.responses import StreamingResponse
|
||
|
||
from app.api.deps import DbSession, PayrollWriter, WorkbenchReader
|
||
from app.core.response import ApiResponse, PageQuery, PageResult, ok
|
||
from app.schemas.tplus_business_sync import BusinessSyncRun, BusinessSyncUpdate, SyncRange
|
||
from app.schemas.tplus_workbench import (
|
||
CorrectionPairIn,
|
||
FactIn,
|
||
SourceChoiceQuery,
|
||
WorkCreateIn,
|
||
WorkQuery,
|
||
)
|
||
from app.services.tplus import business_sync_control, workbench
|
||
|
||
router = APIRouter()
|
||
RunNo = Annotated[str, Path(pattern=r"^[A-Za-z0-9_-]+$", max_length=32)]
|
||
|
||
@router.get("/business-sync")
|
||
def business_sync_config(session: DbSession, _: WorkbenchReader):
|
||
return ok(business_sync_control.configuration(session))
|
||
|
||
|
||
@router.put("/business-sync")
|
||
def business_sync_save(session: DbSession, actor: PayrollWriter, payload: BusinessSyncUpdate):
|
||
return ok(business_sync_control.save(session, payload, actor.id))
|
||
|
||
|
||
@router.post("/business-sync/preview")
|
||
def business_sync_preview(session: DbSession, _: PayrollWriter, payload: SyncRange):
|
||
return ok(business_sync_control.readonly_preview(session, payload.start_date))
|
||
|
||
|
||
@router.post("/business-sync/runs")
|
||
def business_sync_run(session: DbSession, actor: PayrollWriter, payload: BusinessSyncRun):
|
||
return ok(business_sync_control.queue(session, actor.id, payload.start_date, payload.request_key))
|
||
|
||
|
||
@router.post("/correction-pairs")
|
||
def save_correction_pair(session: DbSession, actor: PayrollWriter, payload: CorrectionPairIn):
|
||
"""成对排除转客户更正单,将实际回款关联原业务,保留实际到账日。"""
|
||
from app.services.tplus.corrections import save_pair
|
||
|
||
return ok(save_pair(session, payload, actor.id))
|
||
|
||
|
||
@router.get("/facts", response_model=ApiResponse[PageResult[dict]])
|
||
def facts(session: DbSession, _: WorkbenchReader, query: Annotated[WorkQuery, Query()]):
|
||
"""有界查询每项补充资料的当前版本。"""
|
||
return ok(workbench.list_facts(session, query))
|
||
|
||
|
||
@router.post("/facts", response_model=ApiResponse[dict])
|
||
def save_fact(session: DbSession, admin: PayrollWriter, payload: FactIn):
|
||
"""追加有依据的人工事实;旧版本不覆盖。"""
|
||
return ok(workbench.save_fact(session, payload, admin.id))
|
||
|
||
|
||
@router.get("/facts/{fact_id}/history", response_model=ApiResponse[PageResult[dict]])
|
||
def fact_history(
|
||
session: DbSession, _: WorkbenchReader, fact_id: int, query: Annotated[PageQuery, Query()]
|
||
):
|
||
"""按事实身份分页查看修改历史,不允许跨账套读取。"""
|
||
return ok(workbench.fact_history(session, fact_id, query))
|
||
|
||
|
||
@router.post("/work-runs", response_model=ApiResponse[dict])
|
||
def create_work(session: DbSession, admin: PayrollWriter, payload: WorkCreateIn):
|
||
"""创建导入预览、执行导入或核算预演任务。"""
|
||
return ok(workbench.queue_work(session, payload, admin.id))
|
||
|
||
|
||
@router.get("/work-runs", response_model=ApiResponse[PageResult[dict]])
|
||
def work_runs(session: DbSession, _: WorkbenchReader, query: Annotated[WorkQuery, Query()]):
|
||
"""分页查询任务,所有金额结果仅为预演。"""
|
||
return ok(workbench.list_work(session, query))
|
||
|
||
|
||
@router.get("/work-runs/{run_no}", response_model=ApiResponse[dict])
|
||
def work_detail(session: DbSession, _: WorkbenchReader, run_no: RunNo):
|
||
"""查询规则版本、输入指纹及结果。"""
|
||
return ok(workbench.work_out(workbench.get_run(session, run_no)))
|
||
|
||
|
||
@router.get("/work-runs/{run_no}/lines", response_model=ApiResponse[PageResult[dict]])
|
||
def work_lines(
|
||
session: DbSession, _: WorkbenchReader, run_no: RunNo, query: Annotated[WorkQuery, Query()]
|
||
):
|
||
"""分页查看新增、待补及分配明细,不公开原始快照 JSON。"""
|
||
return ok(workbench.list_lines(session, run_no, query))
|
||
|
||
|
||
@router.get("/source-records", response_model=ApiResponse[PageResult[dict]])
|
||
def source_choices(
|
||
session: DbSession,
|
||
_: WorkbenchReader,
|
||
query: Annotated[SourceChoiceQuery, Query()],
|
||
):
|
||
"""仅提供挑选资料所需的源编号和名称,不返回电话、地址和原始载荷。"""
|
||
return ok(workbench.source_choices(session, query))
|
||
|
||
|
||
@router.get("/work-runs/{run_no}/report")
|
||
def work_report(session: DbSession, _: WorkbenchReader, run_no: RunNo):
|
||
"""导出全部预演明细,防止源单号被电子表格当作公式执行。"""
|
||
return StreamingResponse(
|
||
workbench.report_csv(session, run_no),
|
||
media_type="text/csv; charset=utf-8",
|
||
headers={"Content-Disposition": f'attachment; filename="{run_no}-preview.csv"'},
|
||
)
|