using System.Diagnostics; using System.Text.Json; using Admin.NET.Plugin.AiDOP.Entity; using Admin.NET.Plugin.AiDOP.Manufacturing; using Admin.NET.Plugin.ApprovalFlow; using Admin.NET.Plugin.ApprovalFlow.Service; using Yitter.IdGenerator; using ApprovalFlowEntity = Admin.NET.Plugin.ApprovalFlow.ApprovalFlow; namespace Admin.NET.Plugin.AiDOP.Infrastructure; /// /// S6 Phase 1D 过程检验单(Phase 1C bill)审核流程种子(幂等)。 /// /// 桥接:让 ado_s6_process_inspection_bill 接入 ApprovalFlow(BizType=S6_PROCESS_INSPECTION)。 /// 复用 IpqcInspectionFlowSeed 已建的角色(ROLE_S6_IPQC_SUPERVISOR/QUALITY_ENGINEER)与处置表 ado_s6_ipqc_quality_disposition, /// 本种子只:① 确保引擎/处置表存在(CodeFirst 幂等)② 注册 BizType ③ 发布流程定义 /// (start→N1_SUBMIT(Initiator 发起人)→N2_SUP_REVIEW(主管)→GW_RESULT(disposition_required==1→N3 / 否则→end)→N3_QE_DISPOSITION(QE)→end)。 /// N1=发起人本人(提交审核人),无需检验员角色成员;N2/N3 复用 IPQC 主管/QE 角色。已发布则跳过(幂等)。 /// public static class S6ProcessInspectionFlowSeed { public static void EnsureSeed(ISqlSugarClient db) { try { db.CodeFirst.InitTables( typeof(ApprovalBizType), typeof(ApprovalFlowEntity), typeof(ApprovalFlowVersion)); db.CodeFirst.InitTables(typeof(AdoS6IpqcQualityDisposition)); // S6-TENANT-AUTHORITY-1:与 IPQC 链共用同一套租户本地角色。幂等,重复调用无副作用; // 显式调用而不依赖 Startup 的种子顺序,避免顺序被调整后本链拿不到本地角色。 S6TenantAuthoritySeed.EnsureTenantAuthority(db); EnsureBizType(db); EnsurePublishedFlow(db); } catch (Exception ex) { Trace.TraceWarning("S6 process inspection review flow seed: " + ex); } } private static void EnsureBizType(ISqlSugarClient db) { var exists = db.Queryable().Any(x => x.Code == S6ProcessInspectionFlowConst.BizType); if (exists) return; db.Insertable(new ApprovalBizType { Id = YitIdHelper.NextId(), Code = S6ProcessInspectionFlowConst.BizType, Name = "过程检验单(Phase1C)审核", Remark = "S6 过程检验单(ado_s6_process_inspection_bill) 提交审核→主管→质量处置 审批流", IsEnabled = true, CreateTime = DateTime.Now, UpdateTime = DateTime.Now, }).ExecuteCommand(); } /// /// S6-TENANT-AUTHORITY-1:副本维度由 **Org 改为 Tenant**,并显式落 TenantId。 /// /// 原实现按 SysOrg 建 20 份且**从不设置 TenantId**(全部落 NULL),而 Runtime 只按 TenantId 选取 —— /// 20 份在「本租户优先」「版本号」两个排序键上全部并列,FirstAsync 选谁不确定。 /// 改为每租户一份且 TenantId 明确后,本租户定义在排序键① 上稳定胜出 legacy 的 NULL 定义。 /// /// 只为通过 Activation Gate 的租户发布;历史 20 份 NULL 定义**不下架、不删除**(纯增量), /// 未就绪的租户继续沿用原路径,等成员补齐后下次启动自动补发。 /// private static void EnsurePublishedFlow(ISqlSugarClient db) { var now = DateTime.Now; var flowJson = BuildFlowJson(); foreach (var tenantId in S6TenantAuthoritySeed.GetActivatableTenantIds(db)) { var existing = db.Queryable() .ClearFilter() .Where(x => x.BizType == S6ProcessInspectionFlowConst.BizType && x.TenantId == tenantId && !x.IsDelete) .OrderByDescending(x => x.Version) .First(); // 已有 RoleCode 版则跳过(幂等);仍是纯数字 RoleId 的需要补发。 if (existing != null && (existing.FlowJson ?? "").Contains(S6ProcessInspectionFlowConst.RoleSupervisorCode)) continue; var flow = new ApprovalFlowEntity { Id = YitIdHelper.NextId(), Code = $"S6PI_T{tenantId}", Name = "过程检验单(Phase1C)审核", BizType = S6ProcessInspectionFlowConst.BizType, FormJson = "{}", FlowJson = flowJson, Status = 1, Remark = "租户级流程:开始-提交审核(发起人)-检验主管审核-网关(质量结果)-质量处置-结束(审批权威=RoleCode)", Version = (existing?.Version ?? 0) + 1, IsPublished = true, OrgId = ResolveTenantOrgId(db, tenantId), TenantId = tenantId, CreateTime = now, UpdateTime = now, }; db.Insertable(flow).ExecuteCommand(); db.Insertable(new ApprovalFlowVersion { Id = YitIdHelper.NextId(), FlowId = flow.Id, Version = flow.Version, FlowJson = flow.FlowJson, FormJson = flow.FormJson, PublishTime = now, PublisherName = "系统初始化", CreateTime = now, UpdateTime = now, }).ExecuteCommand(); } } /// 取租户主机构 Id 落 OrgId;OrgId 不参与 Runtime 选取,仅保持字段有意义。 private static long ResolveTenantOrgId(ISqlSugarClient db, long tenantId) { var orgId = db.Queryable() .ClearFilter() .Where(x => x.Id == tenantId) .Select(x => x.OrgId) .First(); return orgId > 0 ? orgId : 0; } private static string BuildFlowJson() { var item = new ApprovalFlowItem { Nodes = new List { new() { Id = "start", Type = "bpmn:startEvent", X = 100, Y = 120, Properties = new FlowProperties { NodeName = "开始" }, Text = new FlowTextItem { X = 100, Y = 154, Value = "开始" }, }, new() { Id = S6ProcessInspectionFlowConst.NodeSubmit, Type = "bpmn:userTask", X = 300, Y = 120, Properties = new FlowProperties { NodeName = "提交审核", ApproverType = nameof(ApproverTypeEnum.Initiator), ApproverIds = "", ApproverNames = "发起人", MultiApproveMode = "Any", }, Text = new FlowTextItem { X = 300, Y = 154, Value = "提交审核" }, }, new() { Id = S6ProcessInspectionFlowConst.NodeSupervisor, Type = "bpmn:userTask", X = 500, Y = 120, Properties = new FlowProperties { NodeName = "检验主管审核", ApproverType = nameof(ApproverTypeEnum.Role), // S6-TENANT-AUTHORITY-1:审批权威用 RoleCode,由引擎在当前租户内重新解析本地 RoleId。 ApproverIds = S6ProcessInspectionFlowConst.RoleSupervisorCode, ApproverNames = "过程检验主管", MultiApproveMode = "Any", }, Text = new FlowTextItem { X = 500, Y = 154, Value = "检验主管审核" }, }, new() { Id = S6ProcessInspectionFlowConst.GatewayResult, Type = "bpmn:exclusiveGateway", X = 660, Y = 120, Properties = new FlowProperties { NodeName = "质量结果分流", Conditions = new List { new() { Expression = "disposition_required == 1", TargetNodeId = S6ProcessInspectionFlowConst.NodeDisposition, IsDefault = false, Label = "不合格转质量处置", }, new() { TargetNodeId = "end", IsDefault = true, Label = "合格完成", }, }, }, Text = new FlowTextItem { X = 660, Y = 154, Value = "质量结果分流" }, }, new() { Id = S6ProcessInspectionFlowConst.NodeDisposition, Type = "bpmn:userTask", X = 820, Y = 120, Properties = new FlowProperties { NodeName = "质量处置", ApproverType = nameof(ApproverTypeEnum.Role), ApproverIds = S6ProcessInspectionFlowConst.RoleQualityEngineerCode, ApproverNames = "过程检验质量工程师", MultiApproveMode = "Any", }, Text = new FlowTextItem { X = 820, Y = 154, Value = "质量处置" }, }, new() { Id = "end", Type = "bpmn:endEvent", X = 980, Y = 120, Properties = new FlowProperties { NodeName = "结束" }, Text = new FlowTextItem { X = 980, Y = 154, Value = "结束" }, }, }, Edges = new List { BuildEdge("edge-start-n1", "start", S6ProcessInspectionFlowConst.NodeSubmit, 140, 120, 260, 120), BuildEdge("edge-n1-n2", S6ProcessInspectionFlowConst.NodeSubmit, S6ProcessInspectionFlowConst.NodeSupervisor, 340, 120, 460, 120), BuildEdge("edge-n2-gw", S6ProcessInspectionFlowConst.NodeSupervisor, S6ProcessInspectionFlowConst.GatewayResult, 540, 120, 640, 120), BuildEdge("edge-gw-n3", S6ProcessInspectionFlowConst.GatewayResult, S6ProcessInspectionFlowConst.NodeDisposition, 680, 120, 780, 120), BuildEdge("edge-gw-end", S6ProcessInspectionFlowConst.GatewayResult, "end", 680, 120, 960, 120), BuildEdge("edge-n3-end", S6ProcessInspectionFlowConst.NodeDisposition, "end", 860, 120, 960, 120), } }; return JsonSerializer.Serialize(item); } private static ApprovalFlowEdgeItem BuildEdge(string id, string source, string target, float sx, float sy, float ex, float ey) { return new ApprovalFlowEdgeItem { Id = id, Type = "bpmn:sequenceFlow", SourceNodeId = source, TargetNodeId = target, StartPoint = new FlowEdgePointItem { X = sx, Y = sy }, EndPoint = new FlowEdgePointItem { X = ex, Y = ey }, Properties = new FlowProperties(), PointsList = new List { new() { X = sx, Y = sy }, new() { X = ex, Y = ey } }, }; } }