using System.Linq;
using Admin.NET.Plugin.AiDOP.FinishedWarehouse.Dto;
using Admin.NET.Plugin.AiDOP.Infrastructure;
namespace Admin.NET.Plugin.AiDOP.FinishedWarehouse;
///
/// S7 FQC 成品质量:检验任务列表 / 检验单(结果)列表 / 检验单详情 只读 list 服务。
///
/// 数据源(2026-07-30 起):DOP 数据中台标准层(CTO 拍板:可接受批次快照延迟、全迁 std):
/// - task-list :mdp_std_fqc_task(源 qms_fqcbj)
/// - result-list:mdp_std_fqc_result(源 qms_qcpp_inspbill + WorkOrdMaster 富化 SAP 工单号)
/// - detail :mdp_std_fqc_result 头 + mdp_std_fqc_result_detail 明细(glid=源头 id;j1..j50 样本 → Samples[])
/// 三 std 由 FqcMdpSyncService 从源同步标准化而来,且已双源(dopdemorq status=0 就位不启用)。
///
/// 只读边界:纯 SELECT + POST mdp/refresh 触发同步(RunFullAsync,只读源/仅写 mdp_*);
/// 绝不 INSERT/UPDATE/DELETE 业务表、不接 ApprovalFlow、不做录入/审核/处置。WP0-D4:按 tenant_id 过滤(std 保留源 tenant)。
/// id 语义:list 返回源主键(source_row_id),detail 按 source_row_id/glid 关联,前端契约不变。
///
[ApiDescriptionSettings(Order = 330, Description = "FQC检验单列表")]
[Route("api/S7FqcInspBill")]
[NonUnify]
public class FqcInspBillService : IDynamicApiController, ITransient
{
/// 主/系统租户哨兵:超管未选择目标租户时其 JWT TenantId 即此值,拒绝作为业务租户。
private const long MainTenantId = 1300000000001L;
private readonly ISqlSugarClient _db;
private readonly UserManager _userManager;
private readonly FqcMdpSyncService _fqcMdpSyncService;
public FqcInspBillService(ISqlSugarClient db, UserManager userManager, FqcMdpSyncService fqcMdpSyncService)
{
_db = db;
_userManager = userManager;
_fqcMdpSyncService = fqcMdpSyncService;
}
/// 严格可信租户解析:只来自认证后 JWT;无 Token(<=0) 或超管未选主租户 → 拒绝;不读前端 tenantId、无默认回退。
private long ResolveTenantOrThrow()
{
var tid = _userManager.TenantId;
if (tid <= 0)
throw Oops.Oh("无法确定当前租户,请重新登录或选择目标租户");
if (_userManager.SuperAdmin && tid == MainTenantId)
throw Oops.Oh("超级管理员操作前必须选择目标租户");
return tid;
}
/// 检验任务列表 排序白名单(前端 prop → std 物理列,杜绝 orderBy 注入)。
private static readonly Dictionary TaskOrderWhitelist = new(StringComparer.OrdinalIgnoreCase)
{
["applyTime"] = "t.apply_time",
["productionBatchNo"] = "t.production_batch_no",
["productionOrderNo"] = "t.production_order_no",
["materialCode"] = "t.material_code",
["priority"] = "t.priority",
["inspectStartTime"] = "t.inspect_start_time",
["inspectFinishTime"] = "t.inspect_finish_time",
["inspectProgress"] = "t.inspect_progress",
};
/// 检验单列表 排序白名单(前端 prop → std 物理列)。
private static readonly Dictionary ResultOrderWhitelist = new(StringComparer.OrdinalIgnoreCase)
{
["inspectTime"] = "a.inspect_time",
["billNo"] = "a.bill_no",
["productionBatchNo"] = "a.production_batch_no",
["materialCode"] = "a.material_code",
["inspector"] = "a.inspector",
["inspectQty"] = "a.inspect_qty",
["productionOrderNo"] = "a.production_order_no",
};
///
/// FQC 数据中台同步刷新(RunFullAsync:只读源 qms_fqcbj/qms_qcpp_inspbill/qms_qcpp_inspbillst,仅写 mdp_*)。
///
[DisplayName("FQC数据中台刷新")]
[HttpPost("mdp/refresh")]
public async Task