S6ProcessInspectionFlowSeed.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. using System.Diagnostics;
  2. using System.Text.Json;
  3. using Admin.NET.Plugin.AiDOP.Entity;
  4. using Admin.NET.Plugin.AiDOP.Manufacturing;
  5. using Admin.NET.Plugin.ApprovalFlow;
  6. using Admin.NET.Plugin.ApprovalFlow.Service;
  7. using Yitter.IdGenerator;
  8. using ApprovalFlowEntity = Admin.NET.Plugin.ApprovalFlow.ApprovalFlow;
  9. namespace Admin.NET.Plugin.AiDOP.Infrastructure;
  10. /// <summary>
  11. /// S6 Phase 1D 过程检验单(Phase 1C bill)审核流程种子(幂等)。
  12. ///
  13. /// 桥接:让 ado_s6_process_inspection_bill 接入 ApprovalFlow(BizType=S6_PROCESS_INSPECTION)。
  14. /// 复用 IpqcInspectionFlowSeed 已建的角色(ROLE_S6_IPQC_SUPERVISOR/QUALITY_ENGINEER)与处置表 ado_s6_ipqc_quality_disposition,
  15. /// 本种子只:① 确保引擎/处置表存在(CodeFirst 幂等)② 注册 BizType ③ 发布流程定义
  16. /// (start→N1_SUBMIT(Initiator 发起人)→N2_SUP_REVIEW(主管)→GW_RESULT(disposition_required==1→N3 / 否则→end)→N3_QE_DISPOSITION(QE)→end)。
  17. /// N1=发起人本人(提交审核人),无需检验员角色成员;N2/N3 复用 IPQC 主管/QE 角色。已发布则跳过(幂等)。
  18. /// </summary>
  19. public static class S6ProcessInspectionFlowSeed
  20. {
  21. public static void EnsureSeed(ISqlSugarClient db)
  22. {
  23. try
  24. {
  25. db.CodeFirst.InitTables(
  26. typeof(ApprovalBizType),
  27. typeof(ApprovalFlowEntity),
  28. typeof(ApprovalFlowVersion));
  29. db.CodeFirst.InitTables(typeof(AdoS6IpqcQualityDisposition));
  30. // S6-TENANT-AUTHORITY-1:与 IPQC 链共用同一套租户本地角色。幂等,重复调用无副作用;
  31. // 显式调用而不依赖 Startup 的种子顺序,避免顺序被调整后本链拿不到本地角色。
  32. S6TenantAuthoritySeed.EnsureTenantAuthority(db);
  33. EnsureBizType(db);
  34. EnsurePublishedFlow(db);
  35. }
  36. catch (Exception ex)
  37. {
  38. Trace.TraceWarning("S6 process inspection review flow seed: " + ex);
  39. }
  40. }
  41. private static void EnsureBizType(ISqlSugarClient db)
  42. {
  43. var exists = db.Queryable<ApprovalBizType>().Any(x => x.Code == S6ProcessInspectionFlowConst.BizType);
  44. if (exists) return;
  45. db.Insertable(new ApprovalBizType
  46. {
  47. Id = YitIdHelper.NextId(),
  48. Code = S6ProcessInspectionFlowConst.BizType,
  49. Name = "过程检验单(Phase1C)审核",
  50. Remark = "S6 过程检验单(ado_s6_process_inspection_bill) 提交审核→主管→质量处置 审批流",
  51. IsEnabled = true,
  52. CreateTime = DateTime.Now,
  53. UpdateTime = DateTime.Now,
  54. }).ExecuteCommand();
  55. }
  56. /// <summary>
  57. /// S6-TENANT-AUTHORITY-1:副本维度由 **Org 改为 Tenant**,并显式落 TenantId。
  58. ///
  59. /// 原实现按 SysOrg 建 20 份且**从不设置 TenantId**(全部落 NULL),而 Runtime 只按 TenantId 选取 ——
  60. /// 20 份在「本租户优先」「版本号」两个排序键上全部并列,FirstAsync 选谁不确定。
  61. /// 改为每租户一份且 TenantId 明确后,本租户定义在排序键① 上稳定胜出 legacy 的 NULL 定义。
  62. ///
  63. /// 只为通过 Activation Gate 的租户发布;历史 20 份 NULL 定义**不下架、不删除**(纯增量),
  64. /// 未就绪的租户继续沿用原路径,等成员补齐后下次启动自动补发。
  65. /// </summary>
  66. private static void EnsurePublishedFlow(ISqlSugarClient db)
  67. {
  68. var now = DateTime.Now;
  69. var flowJson = BuildFlowJson();
  70. foreach (var tenantId in S6TenantAuthoritySeed.GetActivatableTenantIds(db))
  71. {
  72. var existing = db.Queryable<ApprovalFlowEntity>()
  73. .ClearFilter()
  74. .Where(x => x.BizType == S6ProcessInspectionFlowConst.BizType && x.TenantId == tenantId && !x.IsDelete)
  75. .OrderByDescending(x => x.Version)
  76. .First();
  77. // 已有 RoleCode 版则跳过(幂等);仍是纯数字 RoleId 的需要补发。
  78. if (existing != null && (existing.FlowJson ?? "").Contains(S6ProcessInspectionFlowConst.RoleSupervisorCode))
  79. continue;
  80. var flow = new ApprovalFlowEntity
  81. {
  82. Id = YitIdHelper.NextId(),
  83. Code = $"S6PI_T{tenantId}",
  84. Name = "过程检验单(Phase1C)审核",
  85. BizType = S6ProcessInspectionFlowConst.BizType,
  86. FormJson = "{}",
  87. FlowJson = flowJson,
  88. Status = 1,
  89. Remark = "租户级流程:开始-提交审核(发起人)-检验主管审核-网关(质量结果)-质量处置-结束(审批权威=RoleCode)",
  90. Version = (existing?.Version ?? 0) + 1,
  91. IsPublished = true,
  92. OrgId = ResolveTenantOrgId(db, tenantId),
  93. TenantId = tenantId,
  94. CreateTime = now,
  95. UpdateTime = now,
  96. };
  97. db.Insertable(flow).ExecuteCommand();
  98. db.Insertable(new ApprovalFlowVersion
  99. {
  100. Id = YitIdHelper.NextId(),
  101. FlowId = flow.Id,
  102. Version = flow.Version,
  103. FlowJson = flow.FlowJson,
  104. FormJson = flow.FormJson,
  105. PublishTime = now,
  106. PublisherName = "系统初始化",
  107. CreateTime = now,
  108. UpdateTime = now,
  109. }).ExecuteCommand();
  110. }
  111. }
  112. /// <summary>取租户主机构 Id 落 OrgId;OrgId 不参与 Runtime 选取,仅保持字段有意义。</summary>
  113. private static long ResolveTenantOrgId(ISqlSugarClient db, long tenantId)
  114. {
  115. var orgId = db.Queryable<SysTenant>()
  116. .ClearFilter<ITenantIdFilter>()
  117. .Where(x => x.Id == tenantId)
  118. .Select(x => x.OrgId)
  119. .First();
  120. return orgId > 0 ? orgId : 0;
  121. }
  122. private static string BuildFlowJson()
  123. {
  124. var item = new ApprovalFlowItem
  125. {
  126. Nodes = new List<ApprovalFlowNodeItem>
  127. {
  128. new()
  129. {
  130. Id = "start", Type = "bpmn:startEvent", X = 100, Y = 120,
  131. Properties = new FlowProperties { NodeName = "开始" },
  132. Text = new FlowTextItem { X = 100, Y = 154, Value = "开始" },
  133. },
  134. new()
  135. {
  136. Id = S6ProcessInspectionFlowConst.NodeSubmit, Type = "bpmn:userTask", X = 300, Y = 120,
  137. Properties = new FlowProperties
  138. {
  139. NodeName = "提交审核",
  140. ApproverType = nameof(ApproverTypeEnum.Initiator),
  141. ApproverIds = "",
  142. ApproverNames = "发起人",
  143. MultiApproveMode = "Any",
  144. },
  145. Text = new FlowTextItem { X = 300, Y = 154, Value = "提交审核" },
  146. },
  147. new()
  148. {
  149. Id = S6ProcessInspectionFlowConst.NodeSupervisor, Type = "bpmn:userTask", X = 500, Y = 120,
  150. Properties = new FlowProperties
  151. {
  152. NodeName = "检验主管审核",
  153. ApproverType = nameof(ApproverTypeEnum.Role),
  154. // S6-TENANT-AUTHORITY-1:审批权威用 RoleCode,由引擎在当前租户内重新解析本地 RoleId。
  155. ApproverIds = S6ProcessInspectionFlowConst.RoleSupervisorCode,
  156. ApproverNames = "过程检验主管",
  157. MultiApproveMode = "Any",
  158. },
  159. Text = new FlowTextItem { X = 500, Y = 154, Value = "检验主管审核" },
  160. },
  161. new()
  162. {
  163. Id = S6ProcessInspectionFlowConst.GatewayResult, Type = "bpmn:exclusiveGateway", X = 660, Y = 120,
  164. Properties = new FlowProperties
  165. {
  166. NodeName = "质量结果分流",
  167. Conditions = new List<GatewayCondition>
  168. {
  169. new()
  170. {
  171. Expression = "disposition_required == 1",
  172. TargetNodeId = S6ProcessInspectionFlowConst.NodeDisposition,
  173. IsDefault = false,
  174. Label = "不合格转质量处置",
  175. },
  176. new()
  177. {
  178. TargetNodeId = "end",
  179. IsDefault = true,
  180. Label = "合格完成",
  181. },
  182. },
  183. },
  184. Text = new FlowTextItem { X = 660, Y = 154, Value = "质量结果分流" },
  185. },
  186. new()
  187. {
  188. Id = S6ProcessInspectionFlowConst.NodeDisposition, Type = "bpmn:userTask", X = 820, Y = 120,
  189. Properties = new FlowProperties
  190. {
  191. NodeName = "质量处置",
  192. ApproverType = nameof(ApproverTypeEnum.Role),
  193. ApproverIds = S6ProcessInspectionFlowConst.RoleQualityEngineerCode,
  194. ApproverNames = "过程检验质量工程师",
  195. MultiApproveMode = "Any",
  196. },
  197. Text = new FlowTextItem { X = 820, Y = 154, Value = "质量处置" },
  198. },
  199. new()
  200. {
  201. Id = "end", Type = "bpmn:endEvent", X = 980, Y = 120,
  202. Properties = new FlowProperties { NodeName = "结束" },
  203. Text = new FlowTextItem { X = 980, Y = 154, Value = "结束" },
  204. },
  205. },
  206. Edges = new List<ApprovalFlowEdgeItem>
  207. {
  208. BuildEdge("edge-start-n1", "start", S6ProcessInspectionFlowConst.NodeSubmit, 140, 120, 260, 120),
  209. BuildEdge("edge-n1-n2", S6ProcessInspectionFlowConst.NodeSubmit, S6ProcessInspectionFlowConst.NodeSupervisor, 340, 120, 460, 120),
  210. BuildEdge("edge-n2-gw", S6ProcessInspectionFlowConst.NodeSupervisor, S6ProcessInspectionFlowConst.GatewayResult, 540, 120, 640, 120),
  211. BuildEdge("edge-gw-n3", S6ProcessInspectionFlowConst.GatewayResult, S6ProcessInspectionFlowConst.NodeDisposition, 680, 120, 780, 120),
  212. BuildEdge("edge-gw-end", S6ProcessInspectionFlowConst.GatewayResult, "end", 680, 120, 960, 120),
  213. BuildEdge("edge-n3-end", S6ProcessInspectionFlowConst.NodeDisposition, "end", 860, 120, 960, 120),
  214. }
  215. };
  216. return JsonSerializer.Serialize(item);
  217. }
  218. private static ApprovalFlowEdgeItem BuildEdge(string id, string source, string target, float sx, float sy, float ex, float ey)
  219. {
  220. return new ApprovalFlowEdgeItem
  221. {
  222. Id = id, Type = "bpmn:sequenceFlow", SourceNodeId = source, TargetNodeId = target,
  223. StartPoint = new FlowEdgePointItem { X = sx, Y = sy },
  224. EndPoint = new FlowEdgePointItem { X = ex, Y = ey },
  225. Properties = new FlowProperties(),
  226. PointsList = new List<FlowEdgePointItem> { new() { X = sx, Y = sy }, new() { X = ex, Y = ey } },
  227. };
  228. }
  229. }