FqcInspBillFlowSeed.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. using System.Diagnostics;
  2. using System.Text.Json;
  3. using Admin.NET.Plugin.AiDOP.Entity;
  4. using Admin.NET.Plugin.AiDOP.FinishedWarehouse;
  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. /// S7 FQC 成品检验单 检验员+主管流程种子(幂等)。
  12. ///
  13. /// 顺序:① 角色 ROLE_S7_FQC_INSPECTOR / ROLE_S7_FQC_SUPERVISOR ② UAT 成员绑定(superAdmin.NET 同绑两角色)
  14. /// ③ ApprovalBizType(S7_FQC_INSPBILL) ④ ApprovalFlow 已发布定义(start→N1_INSPECT→N2_SUP_REVIEW→end,节点绑角色,线性无网关)。
  15. /// 全部幂等:存在则跳过,不覆盖、不删除既有角色/流程。superAdmin.NET 不存在时仅告警不崩溃。
  16. /// </summary>
  17. public static class FqcInspBillFlowSeed
  18. {
  19. public static void EnsureSeed(ISqlSugarClient db)
  20. {
  21. try
  22. {
  23. db.CodeFirst.InitTables(
  24. typeof(ApprovalBizType),
  25. typeof(ApprovalFlowEntity),
  26. typeof(ApprovalFlowVersion));
  27. // QE 处置表(CodeFirst 建表,无 SQL)
  28. db.CodeFirst.InitTables(typeof(AdoS7FqcQeDisposition));
  29. // S7-FQC-TENANT-AUTHORITY-1:角色供给改为**租户本地化**(原 EnsureRole 用全局
  30. // ClearFilter().Any(Code) 查重,默认租户一有该 Code 就再不为其它租户建 → 全库单例)。
  31. // 默认租户沿用既有固定 Id,其余租户各自一份;既有跨租户绑定 COPY 保留、不删。
  32. S7TenantAuthoritySeed.EnsureTenantAuthority(db);
  33. EnsureUatMembers(db);
  34. EnsureBizType(db);
  35. EnsurePublishedFlow(db);
  36. }
  37. catch (Exception ex)
  38. {
  39. Trace.TraceWarning("S7 FQC inspbill flow seed: " + ex);
  40. }
  41. }
  42. private static void EnsureUatMembers(ISqlSugarClient db)
  43. {
  44. var user = db.Queryable<SysUser>().ClearFilter()
  45. .Where(x => x.Account == FqcInspBillFlowConst.UatAccount)
  46. .First();
  47. if (user == null)
  48. {
  49. Trace.TraceWarning($"S7 FQC flow seed: UAT 账号 {FqcInspBillFlowConst.UatAccount} 不存在,跳过成员绑定(阶段2 冒烟前须指定成员)");
  50. return;
  51. }
  52. EnsureUserRole(db, user.Id, FqcInspBillFlowConst.RoleInspectorId);
  53. EnsureUserRole(db, user.Id, FqcInspBillFlowConst.RoleSupervisorId);
  54. EnsureUserRole(db, user.Id, FqcInspBillFlowConst.RoleQeId);
  55. }
  56. private static void EnsureUserRole(ISqlSugarClient db, long userId, long roleId)
  57. {
  58. var exists = db.Queryable<SysUserRole>().ClearFilter().Any(x => x.UserId == userId && x.RoleId == roleId);
  59. if (exists) return;
  60. db.Insertable(new SysUserRole
  61. {
  62. Id = YitIdHelper.NextId(),
  63. UserId = userId,
  64. RoleId = roleId,
  65. }).ExecuteCommand();
  66. }
  67. private static void EnsureBizType(ISqlSugarClient db)
  68. {
  69. var exists = db.Queryable<ApprovalBizType>().Any(x => x.Code == FqcInspBillFlowConst.BizType);
  70. if (exists) return;
  71. db.Insertable(new ApprovalBizType
  72. {
  73. Id = YitIdHelper.NextId(),
  74. Code = FqcInspBillFlowConst.BizType,
  75. Name = "成品检验单审批",
  76. Remark = "S7 成品检验单 检验员→主管 审批流",
  77. IsEnabled = true,
  78. CreateTime = DateTime.Now,
  79. UpdateTime = DateTime.Now,
  80. }).ExecuteCommand();
  81. }
  82. /// <summary>
  83. /// S7-FQC-TENANT-AUTHORITY-1:定义播种维度由 **SysOrg** 改为 **可激活租户**。
  84. ///
  85. /// 原实现按 SysOrg 逐个播种(<c>Code = $"S7FQC_{orgId}"</c>、<c>TenantId = ResolveTenantId(db, orgId)</c>),
  86. /// 造成 57 条定义且多条定义租户与业务租户错配(审计实测:5 条 Running + 4 条终态实例的
  87. /// 定义租户是 788012541595717,而业务租户是 797 / 838)。审批权威的维度是**租户**,不是机构。
  88. ///
  89. /// 幂等判据改为「已发布定义的 FlowJson 是否已含 RoleCode」:含则跳过,否则补发新版本。
  90. /// **不再下架历史定义**(原 v2→v3 升级会 SetColumns(IsPublished=false))——历史事实保留,
  91. /// 新版本靠 StartFlowCore 的「本租户优先 + Version 降序」自然胜出即可。
  92. /// 历史 Running/Approved 实例各用自己的 FlowJsonSnapshot,本方法对其零影响。
  93. /// </summary>
  94. private static void EnsurePublishedFlow(ISqlSugarClient db)
  95. {
  96. var now = DateTime.Now;
  97. var flowJson = BuildFlowJson();
  98. foreach (var tenantId in S7TenantAuthoritySeed.GetActivatableTenantIds(db))
  99. {
  100. var existing = db.Queryable<ApprovalFlowEntity>()
  101. .ClearFilter()
  102. .Where(x => x.BizType == FqcInspBillFlowConst.BizType && x.TenantId == tenantId && !x.IsDelete)
  103. .OrderByDescending(x => x.Version)
  104. .First();
  105. // 已有 RoleCode 版则跳过(幂等);仍是纯数字 RoleId 的需要补发。
  106. if (existing != null && (existing.FlowJson ?? "").Contains(FqcInspBillFlowConst.RoleSupervisorCode))
  107. continue;
  108. var flow = new ApprovalFlowEntity
  109. {
  110. Id = YitIdHelper.NextId(),
  111. Code = $"S7FQC_T{tenantId}",
  112. Name = "成品检验单审批",
  113. BizType = FqcInspBillFlowConst.BizType,
  114. FormJson = "{}",
  115. FlowJson = flowJson,
  116. Status = 1,
  117. Remark = "租户级流程:开始-检验员检验(发起人本人/认领人)-检验主管审核-网关(pd=1)-QE处置-结束(审批权威=RoleCode)",
  118. Version = (existing?.Version ?? 0) + 1,
  119. IsPublished = true,
  120. OrgId = ResolveTenantOrgId(db, tenantId),
  121. TenantId = tenantId,
  122. CreateTime = now,
  123. UpdateTime = now,
  124. };
  125. db.Insertable(flow).ExecuteCommand();
  126. db.Insertable(new ApprovalFlowVersion
  127. {
  128. Id = YitIdHelper.NextId(),
  129. FlowId = flow.Id,
  130. Version = flow.Version,
  131. FlowJson = flow.FlowJson,
  132. FormJson = flow.FormJson,
  133. PublishTime = now,
  134. PublisherName = "系统初始化",
  135. CreateTime = now,
  136. UpdateTime = now,
  137. }).ExecuteCommand();
  138. }
  139. }
  140. /// <summary>取租户主机构 Id 落 OrgId;OrgId 不参与 Runtime 选取,仅保持字段有意义。</summary>
  141. private static long ResolveTenantOrgId(ISqlSugarClient db, long tenantId)
  142. {
  143. var orgId = db.Queryable<SysTenant>()
  144. .ClearFilter<ITenantIdFilter>()
  145. .Where(x => x.Id == tenantId)
  146. .Select(x => x.OrgId)
  147. .First();
  148. return orgId > 0 ? orgId : 0;
  149. }
  150. private static string BuildFlowJson()
  151. {
  152. var item = new ApprovalFlowItem
  153. {
  154. Nodes = new List<ApprovalFlowNodeItem>
  155. {
  156. new()
  157. {
  158. Id = "start",
  159. Type = "bpmn:startEvent",
  160. X = 100,
  161. Y = 120,
  162. Properties = new FlowProperties { NodeName = "开始" },
  163. Text = new FlowTextItem { X = 100, Y = 154, Value = "开始" },
  164. },
  165. new()
  166. {
  167. Id = FqcInspBillFlowConst.NodeInspect,
  168. Type = "bpmn:userTask",
  169. X = 300,
  170. Y = 120,
  171. Properties = new FlowProperties
  172. {
  173. NodeName = "检验员检验",
  174. // v3:N1 待办归「发起人本人」= 生成检验单的认领人(ApproverType=Initiator,引擎按 InitiatorId 解析)。
  175. // ApproverIds 置空(Initiator 不需要角色/用户 id)。角色约束后置,本节点不再绑成品检验员角色。
  176. ApproverType = nameof(ApproverTypeEnum.Initiator),
  177. ApproverIds = "",
  178. ApproverNames = "认领人(发起人)",
  179. MultiApproveMode = "Any",
  180. },
  181. Text = new FlowTextItem { X = 300, Y = 154, Value = "检验员检验" },
  182. },
  183. new()
  184. {
  185. Id = FqcInspBillFlowConst.NodeSupervisor,
  186. Type = "bpmn:userTask",
  187. X = 500,
  188. Y = 120,
  189. Properties = new FlowProperties
  190. {
  191. NodeName = "检验主管审核",
  192. // S7-FQC-TENANT-AUTHORITY-1:权威改为 **RoleCode**,由引擎在目标租户内解析成
  193. // 该租户自己的角色。定义是模板、RoleId 是租户本地身份,模板里不能存某个具体租户的
  194. // 物理 RoleId(原写 RoleSupervisorId=1329915020002,属默认租户)。
  195. // 即使此处能查到本租户 RoleId 也不写入——那会让定义重新变成租户绑定的。
  196. ApproverType = nameof(ApproverTypeEnum.Role),
  197. ApproverIds = FqcInspBillFlowConst.RoleSupervisorCode,
  198. ApproverNames = "成品检验主管",
  199. MultiApproveMode = "Any",
  200. },
  201. Text = new FlowTextItem { X = 500, Y = 154, Value = "检验主管审核" },
  202. },
  203. new()
  204. {
  205. Id = FqcInspBillFlowConst.GatewayResult,
  206. Type = "bpmn:exclusiveGateway",
  207. X = 660,
  208. Y = 120,
  209. Properties = new FlowProperties
  210. {
  211. NodeName = "判定分流",
  212. Conditions = new List<GatewayCondition>
  213. {
  214. new()
  215. {
  216. Expression = "disposition_required == 1",
  217. TargetNodeId = FqcInspBillFlowConst.NodeDisposition,
  218. IsDefault = false,
  219. Label = "不合格转QE",
  220. },
  221. new()
  222. {
  223. TargetNodeId = "end",
  224. IsDefault = true,
  225. Label = "合格完成",
  226. },
  227. },
  228. },
  229. Text = new FlowTextItem { X = 660, Y = 154, Value = "判定分流" },
  230. },
  231. new()
  232. {
  233. Id = FqcInspBillFlowConst.NodeDisposition,
  234. Type = "bpmn:userTask",
  235. X = 820,
  236. Y = 120,
  237. Properties = new FlowProperties
  238. {
  239. NodeName = "QE处置",
  240. // 同 N2:权威用 RoleCode,不写默认租户物理 RoleId(原 RoleQeId=1329915020003)。
  241. ApproverType = nameof(ApproverTypeEnum.Role),
  242. ApproverIds = FqcInspBillFlowConst.RoleQeCode,
  243. ApproverNames = "成品检验QE",
  244. MultiApproveMode = "Any",
  245. },
  246. Text = new FlowTextItem { X = 820, Y = 154, Value = "QE处置" },
  247. },
  248. new()
  249. {
  250. Id = "end",
  251. Type = "bpmn:endEvent",
  252. X = 980,
  253. Y = 120,
  254. Properties = new FlowProperties { NodeName = "结束" },
  255. Text = new FlowTextItem { X = 980, Y = 154, Value = "结束" },
  256. },
  257. },
  258. Edges = new List<ApprovalFlowEdgeItem>
  259. {
  260. BuildEdge("edge-start-n1", "start", FqcInspBillFlowConst.NodeInspect, 140, 120, 260, 120),
  261. BuildEdge("edge-n1-n2", FqcInspBillFlowConst.NodeInspect, FqcInspBillFlowConst.NodeSupervisor, 340, 120, 460, 120),
  262. BuildEdge("edge-n2-gw", FqcInspBillFlowConst.NodeSupervisor, FqcInspBillFlowConst.GatewayResult, 540, 120, 640, 120),
  263. BuildEdge("edge-gw-n3", FqcInspBillFlowConst.GatewayResult, FqcInspBillFlowConst.NodeDisposition, 680, 120, 780, 120),
  264. BuildEdge("edge-gw-end", FqcInspBillFlowConst.GatewayResult, "end", 680, 120, 960, 120),
  265. BuildEdge("edge-n3-end", FqcInspBillFlowConst.NodeDisposition, "end", 860, 120, 960, 120),
  266. }
  267. };
  268. return JsonSerializer.Serialize(item);
  269. }
  270. private static ApprovalFlowEdgeItem BuildEdge(string id, string source, string target, float sx, float sy, float ex, float ey)
  271. {
  272. return new ApprovalFlowEdgeItem
  273. {
  274. Id = id,
  275. Type = "bpmn:sequenceFlow",
  276. SourceNodeId = source,
  277. TargetNodeId = target,
  278. StartPoint = new FlowEdgePointItem { X = sx, Y = sy },
  279. EndPoint = new FlowEdgePointItem { X = ex, Y = ey },
  280. Properties = new FlowProperties(),
  281. PointsList = new List<FlowEdgePointItem>
  282. {
  283. new() { X = sx, Y = sy },
  284. new() { X = ex, Y = ey },
  285. },
  286. };
  287. }
  288. }