S8OrderDeliveryRuleDefinitions.cs 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. using Admin.NET.Plugin.AiDOP.Infrastructure.S8;
  2. using Admin.NET.Plugin.AiDOP.Service.S8.Rules.DataAccess;
  3. using Admin.NET.Plugin.AiDOP.Service.S8.Rules.DataAccess.Providers;
  4. namespace Admin.NET.Plugin.AiDOP.Service.S8.Rules.Definitions;
  5. /// <summary>
  6. /// S8-RULE02-ORDER-DELIVERY-WARNING-1:订单交付延期<b>预警</b>规则定义。
  7. ///
  8. /// <para><b>与 Rule 01 的本质区别</b>:Rule 01(采购交期延误)判的是「到期日已过而事情没做完」——
  9. /// 事后。本规则判的是「客户交期还没到,但系统自己算出来的建议交期已经晚于它」——事前。
  10. /// 前者用 TIMEOUT(时间轴过了没有),后者用 OUT_OF_RANGE(两个日期之差越了界)。</para>
  11. ///
  12. /// <para><b>为什么是 OUT_OF_RANGE 而不是新造一个 evaluator</b>:本规则的判据展开后就是
  13. /// 「<c>measured_value</c>(延期天数)&gt; 上限 0」,正是 OUT_OF_RANGE 的既有语义。
  14. /// 仓内此前没有任何 OUT_OF_RANGE 规则定义(见 <see cref="S8OutOfRangeRuleEvaluator"/> 类注释),
  15. /// <b>本规则是第一条</b>;evaluator 本身一行未改。</para>
  16. /// </summary>
  17. public sealed class S8OrderDeliveryRuleDefinitions : IS8RuleDefinitionSource
  18. {
  19. /// <summary>
  20. /// Rule 02 编码。命名沿用 Rule 01 的 <c>RULE_{模块}_{业务}</c> 格式。
  21. /// <b>参与 dedup_key,全链身份</b>:任何地方引用必须用本常量,不写字面量。
  22. /// </summary>
  23. public const string OrderDeliveryDelayWarningCode = "RULE_S1_ORDER_DELIVERY_DELAY_WARNING";
  24. /// <summary>
  25. /// 建单使用的异常类型。<b>已存在于种子</b>
  26. /// (<c>S8ExceptionTypeSeedData</c>:订单交付延期预警 / scene S1 / SLA 240 分钟 / 默认 FOLLOW),
  27. /// 因此本批<b>不新建异常类型、不写 migration</b>。
  28. /// </summary>
  29. public const string OrderDeliveryDelayWarningExceptionTypeCode = "ORDER_DELIVERY_DELAY_WARNING";
  30. /// <summary>
  31. /// 源对象类型。<b>参与 dedup_key</b>。
  32. /// <para>是「订单行」而不是「订单」:交期与数量都挂在行上,同一张订单的不同行交期可以不同。
  33. /// 用订单号做身份会让同单多行只留下一条异常,其余行的风险被静默吞掉。</para>
  34. /// </summary>
  35. public const string SalesOrderLineObjectType = "SALES_ORDER_LINE";
  36. public IEnumerable<S8RuleDefinition> GetDefinitions() => new[]
  37. {
  38. new S8RuleDefinition
  39. {
  40. RuleCode = OrderDeliveryDelayWarningCode,
  41. DisplayName = "订单交付延期预警",
  42. Description =
  43. "订单行的系统建议交期晚于客户要求交期,且客户交期尚未到达时提报预警。" +
  44. "延期天数由系统建议交期与客户要求交期相减得到,不做任何推算;" +
  45. "尚未做过评审资源检查(没有系统建议交期)的订单行不参与判定。",
  46. DatasetCode = S8OrderDeliveryDatasetDefinitions.OrderDeliveryRiskCode,
  47. RuleType = S8OutOfRangeRuleEvaluator.RuleTypeCode,
  48. RuleMechanism = "DATE",
  49. SourceObjectType = SalesOrderLineObjectType,
  50. SceneCode = S8ModuleCode.S1,
  51. StageCode = S8ModuleCode.S1,
  52. // 风险在「评审/排产/测算」环节产生:系统建议交期正是该环节的资源检查结果。
  53. OrderFlowCode = "ORDER_REVIEW_PLAN_CALC",
  54. ExceptionTypeCode = OrderDeliveryDelayWarningExceptionTypeCode,
  55. // 面向业务管理员的只读说明。刻意不出现表名 / 列名 / DATEDIFF ——
  56. // 配置页要回答的是「这条规则按什么判」,不是「它怎么实现的」。
  57. JudgementSummary =
  58. "系统按产能测算出的建议交期晚于客户要求的交期,且客户交期尚未到达时预警;"
  59. + "晚出来的天数即预警的严重程度依据。"
  60. + "订单已关闭、或该订单行还没有系统建议交期时,不参与判定。",
  61. DedupIdentitySummary = "销售订单号 + 订单行号",
  62. // ────────────────────────────────────────────────────────────────
  63. // 判定语义。
  64. //
  65. // measured_value = 系统建议交期 - 客户要求交期(天)。由 Provider 用
  66. // DATEDIFF 算好后放在 canonical 的 measured_value 列,evaluator 只做越界比较。
  67. //
  68. // 上限 0 的含义:只要建议交期比要求交期晚哪怕一天就预警。
  69. // 这不是拍脑袋定的阈值 —— 0 是「不延期」这件事本身的定义。
  70. // 真正可调的宽容度留给 ToleranceAbs(见下),管理员要"晚 3 天以内不报"时
  71. // 改的是容差,而不是重新定义什么叫延期。
  72. //
  73. // 不设 LowerBound:建议交期<b>早于</b>要求交期是好事,不构成异常。
  74. // ────────────────────────────────────────────────────────────────
  75. OutOfRange = new S8OutOfRangeSemantics
  76. {
  77. MeasuredValueColumn = S8CanonicalColumns.MeasuredValue,
  78. // 去重身份 = bill_no + '#' + entry_seq,由 Provider 拼好放进 source_object_id。
  79. // 真库证据:crm_seorderentry 390 行,(bill_no, entry_seq) 重复组为 0。
  80. SourceObjectIdColumn = S8CanonicalColumns.SourceObjectId,
  81. RelatedObjectCodeColumn = S8CanonicalColumns.RelatedObjectCode,
  82. UpperBound = 0m,
  83. // ToleranceAbs = 0:本批没有任何业务依据说「晚几天以内可以不报」。
  84. // 凭空填一个 3 会在没有拍板的情况下把一批真实风险静默过滤掉。
  85. // 需要宽容度时由业务给出天数,再改这一处。
  86. ToleranceAbs = 0m,
  87. // ToleranceRatio 对本规则无意义:上限为 0 时 evaluator 会跳过比例判断
  88. // (避免除零),写非 0 值只会造成"配了但不生效"的错觉。
  89. ToleranceRatio = 0m
  90. },
  91. Parameters = new S8RuleParameterPolicy
  92. {
  93. // 数据是「天」粒度,且只在订单评审时变化。5 分钟一轮(Rule 01 的默认)
  94. // 对本规则没有任何信息增益,只是徒增数据库负载。
  95. PollIntervalSecondsDefault = 3600,
  96. TriggerCountRequiredDefault = 1,
  97. // 订单重新评审时系统建议交期会被改写,期间可能短暂缺失。
  98. // 连续两轮不命中才判恢复,避免一次重评审把预警冲掉又建回来。
  99. RecoverCountRequiredDefault = 2,
  100. // 宽限分钟是 TIMEOUT 的概念,OUT_OF_RANGE evaluator 根本不读它。
  101. // 保持 0,不给管理员一个改了不生效的旋钮。
  102. GraceMinutesDefault = 0,
  103. // 预警不是事故:客户交期还没到,还有处置窗口。
  104. // 与异常类型自身的默认严重度(FOLLOW)一致。
  105. SeverityDefault = S8SeverityCode.Follow,
  106. AllowsDepartmentDefaults = true,
  107. // S8-RULE-READINESS-1:本规则的数据集<b>没有部门列</b> ——
  108. // crm_seorderentry / crm_seorder 都不带发生部门或责任部门,
  109. // 命中行带不出 occurrence / responsible,建单时只能取这两个运行参数。
  110. // 不配就是「启用成功、调度成功、异常一条建不出来」的静默失败。
  111. RequiresDepartmentDefaultsForEnable = true,
  112. // 以下三条不是照抄 Rule 01,是按本规则异常的实际去向逐条确认的:
  113. //
  114. // · 处理池:异常带 source_rule_code,认领 / 派单走规则处理池。空池 =
  115. // 预警源源不断建出来、躺在列表里没人认领,而调度器每轮都报成功。
  116. //
  117. // · 复核池:S8VerifierEligibility 对「有来源规则」的异常,检验人候选<b>只</b>取
  118. // 该规则的复核池。空池 → 候选列表为空 → 处理人点不了「提交复核」,
  119. // 单据永远停在 IN_PROGRESS。
  120. //
  121. // · 升级池:本异常类型的 SLA 为 240 分钟 → 建单即写 sla_deadline →
  122. // 必然进入 S8TimeoutAutoEscalationService 的扫描范围。空池时该服务只会记一行
  123. // reason=escalation_pool_empty 然后跳过,超时无人知晓。
  124. RequiresHandlerPoolForEnable = true,
  125. RequiresVerification = true,
  126. SupportsTimeoutEscalation = true
  127. }
  128. }
  129. };
  130. }