namespace Admin.NET.Plugin.AiDOP.Service.S8.Rules.DataAccess.Providers;
///
/// S8-RULE01-PURCHASE-DELIVERY-A:业务数据集定义的集中登记处。
///
/// 为什么集中一处而不是每个数据集一个 source 类:
/// 对重复 DatasetCode 抛 InvalidOperationException,
/// 而它注册为 AddTransient —— 重复登记的失败形态是「首次解析该服务的那个请求 500」,
/// 不是启动崩溃。启动日志干净 ≠ 目录健康。集中在一个数组里,重复项人工一眼可查;
/// 将来新增数据集只加一个元素,不必再改 Startup.cs。
///
/// 本类只回答「系统认知中存在哪些数据集、各自能力如何」,不回答「谁供数」——
/// 后者是 的职责。二者分离才能表达
/// 「数据集已定义、规则可存草稿、但 Provider 未上线因此不可启用」这个必要的中间状态。
///
public sealed class S8BusinessDatasetDefinitions : IS8DatasetDefinitionSource
{
/// 采购交付(订单行级)数据集编码。Provider 与规则的 dataset_code 必须逐字引用本常量。
public const string PurchaseDeliveryCode = "PURCHASE_DELIVERY";
///
/// 采购交付 Authority 的生产者作业码。唯一来源:
/// 下方 AuthoritySpec 与 的发布证据查询
/// 都引用本常量。两处各写一份字面量必然漂移,而漂移的表现是
/// 「健康判定看着 A 作业、取数却认 B 作业的发布」—— 两边各自都不报错。
///
public const string PurchaseDeliveryProducerJobCode = "S3_MDP_SYNC_TRANSFORM";
///
/// 采购交付数据集的能力声明。唯一来源:
/// 直接引用本字段,不另写一份字面量。
///
/// 目录与 Provider 各写一份会漂移,而漂移只在 第 ⑨ 步
/// (运行期启用时)才被发现,报 dataset_capability_mismatch——那时已经晚了。
///
/// 刻意不声明数量类能力(SupportsShortage / HasTargetQty / HasActualQty):
/// dwd_supplier_delivery 物理上有 order_qty / remaining_qty,但字段级审计
/// 已证 remaining_qty 在 781/784 行恒等于 order_qty(收货量在源系统、STD、DWD 三层全断),
/// 不可作为机制判断依据。Capability 的语义是「当前可可信用于机制判断」,不是「表里有这一列」;
/// 声明 true 会让 Enable Gate 放行 SHORTAGE 规则,把不可信数据变成可启用能力。
/// 两列仍随 payload 落盘,仅供排查与后续数据治理观察。
///
internal static readonly S8DatasetCapabilities PurchaseDeliveryCapabilities = new()
{
SupportsTimeout = true,
SupportsShortage = false,
SupportsOutOfRange = false,
HasSourceObjectId = true,
HasRelatedObjectCode = true,
HasDueAt = true,
HasStatus = true,
HasMeasuredValue = false,
HasTargetQty = false,
HasActualQty = false
};
public IEnumerable GetDefinitions() => new[]
{
new S8DatasetDefinition
{
DatasetCode = PurchaseDeliveryCode,
DisplayName = "采购交付(订单行级)",
Kind = S8DatasetKind.Object,
Status = S8DatasetStatus.Defined,
Capabilities = PurchaseDeliveryCapabilities,
// ProducerTrusted 由 false 解冻为 true。冻结时列出的三个解冻条件已全部达成:
//
// ① 单一写入方 —— DeliveryScheduleService 与 ProcessOutsourceOrderService 对
// dwd_supplier_delivery 的直写已移除(前者曾写出 CANCELLED/CLOSED/PARTIAL 三个
// 没有任何读方认识的值,后者把 delivery_status 硬编码为 'OPEN' 并放进 ODKU,
// 一次建单就能把 MDP 刚判定的 COMPLETED / DELAYED 无条件翻回 OPEN)。
// 运行期写入方现在只剩 S3MdpSyncTransformService,它写 mdp_transform_run_log,
// 因此运行日志能完整解释这张表的状态。
//
// ② membership 淘汰 —— 标准层 INSERT 按 @BatchId 收窄驱动行,并在全量路径追加
// 每轮淘汰;DWD 追加同日淘汰。源侧退出的采购订单行会在下一轮跑批退出当前快照。
// (此前实测租户 797403760988229 的标准层 518 行里有 336 行源侧已不存在。)
//
// ③ 租户 JOIN 谓词 —— 标准层的两个 enrichment JOIN 已补 tenant_id 相等条件,
// 并按 (租户, 键) 去重取最新一行。此前实测单租户扇出 3.95 倍。
//
// 同日淘汰还顺带满足了快照原子性:MAX(stat_date) 取到的必然是单一批次
// (此前实测同一天混入两个批次已发生 3 次)。
//
// ④ 空快照语义 —— 当前态改由**发布证据**指认(RequiresPublicationEvidence)。
// 此前 Provider 按「最新的那一天」挑当前快照,在合法的空面前会静默失效:
// 源侧清空的那一轮产出 0 行 DWD,当天没有 stat_date 分区,同日淘汰也无行可删,
// 于是回落到上一天的旧行。实测租户 838257212780613 源侧清零、STD 归 0、
// 跑批 SUCCESS,Provider 仍返回前一日的 10 行。
// 现在 currentRows=0 是「成功发布的空快照」,健康且允许恢复;
// 而没有发布证据判 NO_PUBLICATION → UNKNOWN,由 Wave2 闸门拦下恢复。
//
// 本数据集是 CurrentStateMirror,健康判定不读任何行数,
// 因此某租户源侧合法清空时仍判 HEALTHY、恢复正常放行。
//
// ⚠️ 以上结论已在本地沙箱跑通(membership 182/182/182、扇出 1.00×、
// 跨租户 0、phantom 0、空快照 0 行不回落)。共享库运行态验证待统一部署后完成。
AuthoritySpec = new Health.S8AuthoritySpec
{
ProducerJobCode = PurchaseDeliveryProducerJobCode,
AuthorityKind = Health.S8AuthorityKind.CurrentStateMirror,
RequiresPublicationEvidence = true
},
// S8-RULE01-PURCHASE-DELIVERY-CLOSURE-1:due_at 的业务语义已完成血缘取证,
// 从「UAT_EXECUTION_MAPPING / 待生产确认」升级为「与中台自身逾期判据同源」。
//
// 证据链(S3MdpSyncTransformService.cs 的 dwd_supplier_delivery INSERT):
// · 落库 need_date := COALESCE(ds.need_date, po.need_date)
// 其中 ds = mdp_std_delivery_schedule 按 (tenant,factory,po_no,po_line) 聚合的 MIN(need_date)
// · 落库 due_date := po.due_date 原样
// · delivery_status 的 DELAYED 判据 := COALESCE(ds.need_date, po.need_date, po.due_date) < now
// 因此 Provider 的 COALESCE(need_date, due_date) 展开后与该判据**逐字等价**,
// 不是另起一套口径。实测佐证:UAT 租户最新快照 36 行中,evaluator 判定命中 28 条,
// 与中台自行标记的 DELAYED 28 条完全一致(COMPLETED 7 / OPEN 1 均正确不命中)。
Description =
"来源 dwd_supplier_delivery;粒度为采购订单行(po_no + po_line,实测每快照日唯一);" +
"按 Tenant 最新 stat_date 收敛(factory 不参与本数据集过滤,见 Provider 注释);" +
"due_at = COALESCE(need_date, due_date),已证与数据中台自身的 DELAYED 判据 " +
"COALESCE(交付计划 need_date, 采购订单 need_date, 采购订单 due_date) 同源等价;" +
"status 域 = COMPLETED / DELAYED / OPEN / CLOSED / CANCELLED / PARTIAL(三个写入方合并)。" +
"⚠️ UNPROVEN_CONTRACT_SEMANTICS:上述只证明了 S8 与中台口径一致," +
"并未证明 need_date 等同于与供应商约定的承诺交期 —— 该问题需追至源系统 " +
"PurOrdDetail.NeedDate 并由业务确认,在此之前 due_at 不得被当作交付合同依据。"
}
};
}