IpqcInspectionFlowSeed.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  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 过程检验单(IPQC)检验员+主管流程种子(幂等)。
  12. ///
  13. /// 顺序:① 扩展表 ado_s6_ipqc_inspection_flow_state + 处置表 ado_s6_ipqc_quality_disposition(CodeFirst 建表,无 SQL)
  14. /// ② 角色 ROLE_S6_IPQC_INSPECTOR / SUPERVISOR / QUALITY_ENGINEER ③ UAT 成员绑定(superAdmin.NET 同绑三角色,仅 UAT 自审)
  15. /// ④ ApprovalBizType(IPQC_INSPECTION) ⑤ ApprovalFlow v2 已发布定义
  16. /// (start→N1_INSPECT→N2_SUP_REVIEW→GW_RESULT(disposition_required==1→N3 / 否则→end)→N3_QUALITY_DISPOSITION→end)。
  17. /// v1→v2 幂等升级:已发布定义含 N3 则跳过;仍为 v1 则下架旧版发布 v2;历史 Running 实例用各自 FlowJsonSnapshot 冻结不受影响。
  18. /// superAdmin.NET 不存在时仅告警不崩溃。
  19. /// </summary>
  20. public static class IpqcInspectionFlowSeed
  21. {
  22. public static void EnsureSeed(ISqlSugarClient db)
  23. {
  24. try
  25. {
  26. db.CodeFirst.InitTables(
  27. typeof(ApprovalBizType),
  28. typeof(ApprovalFlowEntity),
  29. typeof(ApprovalFlowVersion));
  30. // 流程状态轻量扩展表 + 质量处置表(CodeFirst 建表,无 SQL)
  31. db.CodeFirst.InitTables(typeof(AdoS6IpqcInspectionFlowState));
  32. db.CodeFirst.InitTables(typeof(AdoS6IpqcQualityDisposition));
  33. // S6-TENANT-AUTHORITY-1:三个 S6 角色改由共享种子按租户建立(原实现全库单例挂默认租户),
  34. // 并把既有跨租户成员语义等价复制到各自租户。两条 S6 链共用,重复调用幂等。
  35. S6TenantAuthoritySeed.EnsureTenantAuthority(db);
  36. EnsureUatMembers(db);
  37. EnsureBizType(db);
  38. EnsurePublishedFlow(db);
  39. }
  40. catch (Exception ex)
  41. {
  42. Trace.TraceWarning("S6 IPQC inspection flow seed: " + ex);
  43. }
  44. }
  45. private static void EnsureUatMembers(ISqlSugarClient db)
  46. {
  47. var user = db.Queryable<SysUser>().ClearFilter()
  48. .Where(x => x.Account == IpqcInspectionFlowConst.UatAccount)
  49. .First();
  50. if (user == null)
  51. {
  52. Trace.TraceWarning($"S6 IPQC flow seed: UAT 账号 {IpqcInspectionFlowConst.UatAccount} 不存在,跳过成员绑定(阶段2 冒烟前须指定成员)");
  53. return;
  54. }
  55. EnsureUserRole(db, user.Id, IpqcInspectionFlowConst.RoleInspectorId);
  56. EnsureUserRole(db, user.Id, IpqcInspectionFlowConst.RoleSupervisorId);
  57. EnsureUserRole(db, user.Id, IpqcInspectionFlowConst.RoleQualityEngineerId);
  58. }
  59. private static void EnsureUserRole(ISqlSugarClient db, long userId, long roleId)
  60. {
  61. var exists = db.Queryable<SysUserRole>().ClearFilter().Any(x => x.UserId == userId && x.RoleId == roleId);
  62. if (exists) return;
  63. db.Insertable(new SysUserRole
  64. {
  65. Id = YitIdHelper.NextId(),
  66. UserId = userId,
  67. RoleId = roleId,
  68. }).ExecuteCommand();
  69. }
  70. private static void EnsureBizType(ISqlSugarClient db)
  71. {
  72. var exists = db.Queryable<ApprovalBizType>().Any(x => x.Code == IpqcInspectionFlowConst.BizType);
  73. if (exists) return;
  74. db.Insertable(new ApprovalBizType
  75. {
  76. Id = YitIdHelper.NextId(),
  77. Code = IpqcInspectionFlowConst.BizType,
  78. Name = "过程检验单审批",
  79. Remark = "S6 过程检验单 检验员→主管 审批流",
  80. IsEnabled = true,
  81. CreateTime = DateTime.Now,
  82. UpdateTime = DateTime.Now,
  83. }).ExecuteCommand();
  84. }
  85. /// <summary>
  86. /// S6-TENANT-AUTHORITY-1:副本维度由 **Org 改为 Tenant**。
  87. ///
  88. /// 原实现按 SysOrg 逐组织建一份,而 FlowEngineService.StartFlowCore 选定义时**只按 TenantId** 过滤、
  89. /// 完全不看 OrgId —— 维度错配导致同一租户下出现多份排序键全并列的候选,FirstAsync 选谁不确定。
  90. /// 改为每租户一份后,租户内候选唯一,且新版本号更高,选取具确定性。
  91. ///
  92. /// 只为通过 Activation Gate(三个角色在本租户均有同租户成员)的租户发布,
  93. /// 避免把目前仍靠 legacy 跨租户 RoleId 工作的租户迁成 0 审批人。
  94. /// 历史定义(按 Org 建的、以及 TenantId 为空的)**一律不下架、不删除**:本方法纯增量,
  95. /// 依靠「本租户优先 + 版本号更高」让新定义被稳定命中;legacy 清理属后续独立批次。
  96. /// </summary>
  97. private static void EnsurePublishedFlow(ISqlSugarClient db)
  98. {
  99. var now = DateTime.Now;
  100. var flowJson = BuildFlowJson();
  101. foreach (var tenantId in S6TenantAuthoritySeed.GetActivatableTenantIds(db))
  102. {
  103. var existing = db.Queryable<ApprovalFlowEntity>()
  104. .ClearFilter()
  105. .Where(x => x.BizType == IpqcInspectionFlowConst.BizType && x.TenantId == tenantId && !x.IsDelete)
  106. .OrderByDescending(x => x.Version)
  107. .First();
  108. // 目标形态判据:审批权威已是 RoleCode。含 RoleCode 即为当前版,跳过(幂等)。
  109. // 仍是纯数字 RoleId 的(无论 v1 还是 v2 拓扑)都需要补发一版 RoleCode 定义。
  110. if (existing != null && (existing.FlowJson ?? "").Contains(IpqcInspectionFlowConst.RoleSupervisorCode))
  111. continue;
  112. var nextVersion = (existing?.Version ?? 0) + 1;
  113. var flow = new ApprovalFlowEntity
  114. {
  115. Id = YitIdHelper.NextId(),
  116. Code = $"S6IPQC_T{tenantId}",
  117. Name = "过程检验单审批",
  118. BizType = IpqcInspectionFlowConst.BizType,
  119. FormJson = "{}",
  120. FlowJson = flowJson,
  121. Status = 1,
  122. Remark = "租户级流程:开始-检验员检验-检验主管审核-网关(整批判定)-质量处置-结束(审批权威=RoleCode)",
  123. Version = nextVersion,
  124. IsPublished = true,
  125. OrgId = ResolveTenantOrgId(db, tenantId),
  126. TenantId = tenantId,
  127. CreateTime = now,
  128. UpdateTime = now,
  129. };
  130. db.Insertable(flow).ExecuteCommand();
  131. db.Insertable(new ApprovalFlowVersion
  132. {
  133. Id = YitIdHelper.NextId(),
  134. FlowId = flow.Id,
  135. Version = flow.Version,
  136. FlowJson = flow.FlowJson,
  137. FormJson = flow.FormJson,
  138. PublishTime = now,
  139. PublisherName = "系统初始化",
  140. CreateTime = now,
  141. UpdateTime = now,
  142. }).ExecuteCommand();
  143. }
  144. }
  145. /// <summary>
  146. /// 取租户的主机构 Id 落到定义的 OrgId 上。OrgId 不参与 Runtime 选取(StartFlowCore 已 ClearFilter),
  147. /// 仅为保持实体字段有意义;取不到时落 0。
  148. /// </summary>
  149. private static long ResolveTenantOrgId(ISqlSugarClient db, long tenantId)
  150. {
  151. var orgId = db.Queryable<SysTenant>()
  152. .ClearFilter<ITenantIdFilter>()
  153. .Where(x => x.Id == tenantId)
  154. .Select(x => x.OrgId)
  155. .First();
  156. return orgId > 0 ? orgId : 0;
  157. }
  158. private static string BuildFlowJson()
  159. {
  160. var item = new ApprovalFlowItem
  161. {
  162. Nodes = new List<ApprovalFlowNodeItem>
  163. {
  164. new()
  165. {
  166. Id = "start",
  167. Type = "bpmn:startEvent",
  168. X = 100,
  169. Y = 120,
  170. Properties = new FlowProperties { NodeName = "开始" },
  171. Text = new FlowTextItem { X = 100, Y = 154, Value = "开始" },
  172. },
  173. new()
  174. {
  175. Id = IpqcInspectionFlowConst.NodeInspect,
  176. Type = "bpmn:userTask",
  177. X = 300,
  178. Y = 120,
  179. Properties = new FlowProperties
  180. {
  181. NodeName = "检验员检验",
  182. ApproverType = nameof(ApproverTypeEnum.Role),
  183. // S6-TENANT-AUTHORITY-1:审批权威用 RoleCode,不用默认租户物理 RoleId。
  184. // FlowEngineService 的 Code 分支会按 (Code, effectiveTenantId, Status=Enable)
  185. // 在**当前租户内**重新解析出本地 RoleId,天然租户安全。
  186. ApproverIds = IpqcInspectionFlowConst.RoleInspectorCode,
  187. ApproverNames = "过程检验员",
  188. MultiApproveMode = "Any",
  189. },
  190. Text = new FlowTextItem { X = 300, Y = 154, Value = "检验员检验" },
  191. },
  192. new()
  193. {
  194. Id = IpqcInspectionFlowConst.NodeSupervisor,
  195. Type = "bpmn:userTask",
  196. X = 500,
  197. Y = 120,
  198. Properties = new FlowProperties
  199. {
  200. NodeName = "检验主管审核",
  201. ApproverType = nameof(ApproverTypeEnum.Role),
  202. ApproverIds = IpqcInspectionFlowConst.RoleSupervisorCode,
  203. ApproverNames = "过程检验主管",
  204. MultiApproveMode = "Any",
  205. },
  206. Text = new FlowTextItem { X = 500, Y = 154, Value = "检验主管审核" },
  207. },
  208. new()
  209. {
  210. Id = IpqcInspectionFlowConst.GatewayResult,
  211. Type = "bpmn:exclusiveGateway",
  212. X = 660,
  213. Y = 120,
  214. Properties = new FlowProperties
  215. {
  216. NodeName = "整批判定分流",
  217. Conditions = new List<GatewayCondition>
  218. {
  219. new()
  220. {
  221. Expression = "disposition_required == 1",
  222. TargetNodeId = IpqcInspectionFlowConst.NodeDisposition,
  223. IsDefault = false,
  224. Label = "整批不合格转处置",
  225. },
  226. new()
  227. {
  228. TargetNodeId = "end",
  229. IsDefault = true,
  230. Label = "整批合格完成",
  231. },
  232. },
  233. },
  234. Text = new FlowTextItem { X = 660, Y = 154, Value = "整批判定分流" },
  235. },
  236. new()
  237. {
  238. Id = IpqcInspectionFlowConst.NodeDisposition,
  239. Type = "bpmn:userTask",
  240. X = 820,
  241. Y = 120,
  242. Properties = new FlowProperties
  243. {
  244. NodeName = "质量处置",
  245. ApproverType = nameof(ApproverTypeEnum.Role),
  246. ApproverIds = IpqcInspectionFlowConst.RoleQualityEngineerCode,
  247. ApproverNames = "过程检验质量工程师",
  248. MultiApproveMode = "Any",
  249. },
  250. Text = new FlowTextItem { X = 820, Y = 154, Value = "质量处置" },
  251. },
  252. new()
  253. {
  254. Id = "end",
  255. Type = "bpmn:endEvent",
  256. X = 980,
  257. Y = 120,
  258. Properties = new FlowProperties { NodeName = "结束" },
  259. Text = new FlowTextItem { X = 980, Y = 154, Value = "结束" },
  260. },
  261. },
  262. Edges = new List<ApprovalFlowEdgeItem>
  263. {
  264. BuildEdge("edge-start-n1", "start", IpqcInspectionFlowConst.NodeInspect, 140, 120, 260, 120),
  265. BuildEdge("edge-n1-n2", IpqcInspectionFlowConst.NodeInspect, IpqcInspectionFlowConst.NodeSupervisor, 340, 120, 460, 120),
  266. BuildEdge("edge-n2-gw", IpqcInspectionFlowConst.NodeSupervisor, IpqcInspectionFlowConst.GatewayResult, 540, 120, 640, 120),
  267. BuildEdge("edge-gw-n3", IpqcInspectionFlowConst.GatewayResult, IpqcInspectionFlowConst.NodeDisposition, 680, 120, 780, 120),
  268. BuildEdge("edge-gw-end", IpqcInspectionFlowConst.GatewayResult, "end", 680, 120, 960, 120),
  269. BuildEdge("edge-n3-end", IpqcInspectionFlowConst.NodeDisposition, "end", 860, 120, 960, 120),
  270. }
  271. };
  272. return JsonSerializer.Serialize(item);
  273. }
  274. private static ApprovalFlowEdgeItem BuildEdge(string id, string source, string target, float sx, float sy, float ex, float ey)
  275. {
  276. return new ApprovalFlowEdgeItem
  277. {
  278. Id = id,
  279. Type = "bpmn:sequenceFlow",
  280. SourceNodeId = source,
  281. TargetNodeId = target,
  282. StartPoint = new FlowEdgePointItem { X = sx, Y = sy },
  283. EndPoint = new FlowEdgePointItem { X = ex, Y = ey },
  284. Properties = new FlowProperties(),
  285. PointsList = new List<FlowEdgePointItem>
  286. {
  287. new() { X = sx, Y = sy },
  288. new() { X = ex, Y = ey },
  289. },
  290. };
  291. }
  292. }