AidopMenuLinkSync.cs 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. using Admin.NET.Core;
  2. using Microsoft.Extensions.DependencyInjection;
  3. using SqlSugar;
  4. namespace Admin.NET.Plugin.AiDOP.Infrastructure;
  5. /// <summary>
  6. /// 将 Ai-DOP 种子菜单 Id 补写到 <c>sys_tenant_menu</c> / <c>sys_role_menu</c>。
  7. /// 解决:<c>SysTenantMenuSeedData</c> 带 IgnoreUpdateSeed,库已存在时新增菜单不会自动进租户/角色;多租户下仅补默认租户会导致其它租户侧栏与菜单管理不可见。
  8. /// </summary>
  9. public static class AidopMenuLinkSync
  10. {
  11. /// <summary>与框架种子中首条「系统管理员」角色 Id 一致。</summary>
  12. private const long SysAdminRoleId = 1300000000101L;
  13. private const long LegacyMaterialSubstitutionMenuId = 1329003000005L;
  14. private const long DeprecatedMaterialSubstitutionMenuId = 1329002000004L;
  15. public static void EnsureLinked(IServiceProvider services)
  16. {
  17. using var scope = services.CreateScope();
  18. var db = scope.ServiceProvider.GetRequiredService<ISqlSugarClient>();
  19. NormalizeMaterialSubstitutionMenu(db);
  20. NormalizeS1OrderWorkOrderParents(db);
  21. var seedMenus = new global::Admin.NET.Plugin.AiDOP.SysMenuSeedData().HasData().ToList();
  22. var seedMenuIds = seedMenus.Select(m => m.Id).ToHashSet();
  23. var existingMenuIds = db.Queryable<SysMenu>()
  24. .Where(m => seedMenuIds.Contains(m.Id))
  25. .Select(m => m.Id)
  26. .ToList()
  27. .ToHashSet();
  28. if (existingMenuIds.Count == 0)
  29. return;
  30. var tenantIds = db.Queryable<SysTenant>().Select(t => t.Id).ToList();
  31. if (tenantIds.Count == 0)
  32. return;
  33. var tenantMenuPairs = db.Queryable<SysTenantMenu>()
  34. .Where(tm => tenantIds.Contains(tm.TenantId) && seedMenuIds.Contains(tm.MenuId))
  35. .Select(tm => new { tm.TenantId, tm.MenuId })
  36. .ToList()
  37. .Select(x => (x.TenantId, x.MenuId))
  38. .ToHashSet();
  39. var tenantRows = new List<SysTenantMenu>();
  40. foreach (var tid in tenantIds)
  41. {
  42. foreach (var menu in seedMenus)
  43. {
  44. if (!existingMenuIds.Contains(menu.Id))
  45. continue;
  46. if (tenantMenuPairs.Contains((tid, menu.Id)))
  47. continue;
  48. tenantRows.Add(new SysTenantMenu
  49. {
  50. Id = CommonUtil.GetFixedHashCode("" + tid + menu.Id, 1300000000000),
  51. TenantId = tid,
  52. MenuId = menu.Id
  53. });
  54. }
  55. }
  56. if (tenantRows.Count > 0)
  57. db.Insertable(tenantRows).ExecuteCommand();
  58. // 已为任一 Ai-DOP 种子菜单授权的角色,补全新增子菜单;并始终包含默认租户系统管理员角色。
  59. var roleIdsWithAnyAidop = db.Queryable<SysRoleMenu>()
  60. .Where(rm => seedMenuIds.Contains(rm.MenuId))
  61. .Select(rm => rm.RoleId)
  62. .ToList()
  63. .Distinct()
  64. .ToHashSet();
  65. roleIdsWithAnyAidop.Add(SysAdminRoleId);
  66. var roleMenuPairs = db.Queryable<SysRoleMenu>()
  67. .Where(rm => roleIdsWithAnyAidop.Contains(rm.RoleId) && seedMenuIds.Contains(rm.MenuId))
  68. .Select(rm => new { rm.RoleId, rm.MenuId })
  69. .ToList()
  70. .Select(x => (x.RoleId, x.MenuId))
  71. .ToHashSet();
  72. var roleRows = new List<SysRoleMenu>();
  73. foreach (var roleId in roleIdsWithAnyAidop)
  74. {
  75. foreach (var menu in seedMenus)
  76. {
  77. if (!existingMenuIds.Contains(menu.Id))
  78. continue;
  79. if (roleMenuPairs.Contains((roleId, menu.Id)))
  80. continue;
  81. roleRows.Add(new SysRoleMenu
  82. {
  83. Id = menu.Id + (roleId % 1300000000000),
  84. RoleId = roleId,
  85. MenuId = menu.Id
  86. });
  87. }
  88. }
  89. if (roleRows.Count > 0)
  90. db.Insertable(roleRows).ExecuteCommand();
  91. }
  92. private static void NormalizeMaterialSubstitutionMenu(ISqlSugarClient db)
  93. {
  94. var deprecatedMenuId = DeprecatedMaterialSubstitutionMenuId;
  95. var legacyMenuId = LegacyMaterialSubstitutionMenuId;
  96. var legacyMenu = db.Queryable<SysMenu>()
  97. .First(m => m.Id == legacyMenuId);
  98. if (legacyMenu != null)
  99. {
  100. legacyMenu.Title = "物料替代";
  101. legacyMenu.Path = "/aidop/s0/manufacturing/material-substitution";
  102. legacyMenu.Name = "aidopS0MfgMaterialSubstitution";
  103. legacyMenu.Component = "/aidop/s0/manufacturing/MaterialSubstitutionList";
  104. legacyMenu.Remark = "S0 物料替代";
  105. db.Updateable(legacyMenu)
  106. .UpdateColumns(m => new { m.Title, m.Path, m.Name, m.Component, m.Remark })
  107. .ExecuteCommand();
  108. }
  109. var deprecatedExists = db.Queryable<SysMenu>().Any(m => m.Id == deprecatedMenuId);
  110. if (!deprecatedExists)
  111. return;
  112. db.Deleteable<SysUserMenu>().Where(x => x.MenuId == deprecatedMenuId).ExecuteCommand();
  113. db.Deleteable<SysRoleMenu>().Where(x => x.MenuId == deprecatedMenuId).ExecuteCommand();
  114. db.Deleteable<SysTenantMenu>().Where(x => x.MenuId == deprecatedMenuId).ExecuteCommand();
  115. db.Deleteable<SysMenu>().Where(x => x.Id == deprecatedMenuId).ExecuteCommand();
  116. }
  117. /// <summary>
  118. /// 将 S1「订单管理」「工单管理」升级为目录并修正 path/name;
  119. /// 「产销协同看板」单独菜单路径为 /aidop/s1/SalesKanBan;
  120. /// 「工单下达」父级须为工单管理目录 1322000000004(历史错误曾挂到 0005)。
  121. /// </summary>
  122. private static void NormalizeS1OrderWorkOrderParents(ISqlSugarClient db)
  123. {
  124. const long orderMgmtId = 1322000000003L;
  125. const long workOrderMgmtId = 1322000000004L;
  126. const long salesKanBanMenuId = 1322000000005L;
  127. const long workOrderDispatchMenuId = 1322000000106L;
  128. var orderMgmt = db.Queryable<SysMenu>().First(m => m.Id == orderMgmtId);
  129. if (orderMgmt != null)
  130. {
  131. orderMgmt.Type = MenuTypeEnum.Dir;
  132. orderMgmt.Component = "Layout";
  133. orderMgmt.Path = "/aidop/s1/order-mgmt";
  134. orderMgmt.Name = "aidopS1OrderMgmt";
  135. orderMgmt.Icon = "ele-Folder";
  136. db.Updateable(orderMgmt)
  137. .UpdateColumns(m => new { m.Type, m.Component, m.Path, m.Name, m.Icon })
  138. .ExecuteCommand();
  139. }
  140. var workOrderMgmt = db.Queryable<SysMenu>().First(m => m.Id == workOrderMgmtId);
  141. if (workOrderMgmt != null)
  142. {
  143. workOrderMgmt.Type = MenuTypeEnum.Dir;
  144. workOrderMgmt.Component = "Layout";
  145. workOrderMgmt.Path = "/aidop/s1/workorder-mgmt";
  146. workOrderMgmt.Name = "aidopS1WorkOrderMgmt";
  147. workOrderMgmt.Icon = "ele-Folder";
  148. db.Updateable(workOrderMgmt)
  149. .UpdateColumns(m => new { m.Type, m.Component, m.Path, m.Name, m.Icon })
  150. .ExecuteCommand();
  151. }
  152. var salesKanBan = db.Queryable<SysMenu>().First(m => m.Id == salesKanBanMenuId);
  153. if (salesKanBan != null)
  154. {
  155. salesKanBan.Type = MenuTypeEnum.Menu;
  156. salesKanBan.Component = "/aidop/kanban/s1";
  157. salesKanBan.Path = "/aidop/s1/SalesKanBan";
  158. salesKanBan.Name = "aidopS1003";
  159. salesKanBan.Icon = "ele-DataAnalysis";
  160. db.Updateable(salesKanBan)
  161. .UpdateColumns(m => new { m.Type, m.Component, m.Path, m.Name, m.Icon })
  162. .ExecuteCommand();
  163. }
  164. var dispatch = db.Queryable<SysMenu>().First(m => m.Id == workOrderDispatchMenuId);
  165. if (dispatch != null)
  166. {
  167. var needFix = dispatch.Pid != workOrderMgmtId
  168. || dispatch.Path != "/aidop/s1/workorder-mgmt/dispatch";
  169. if (needFix)
  170. {
  171. dispatch.Pid = workOrderMgmtId;
  172. dispatch.Path = "/aidop/s1/workorder-mgmt/dispatch";
  173. db.Updateable(dispatch)
  174. .UpdateColumns(m => new { m.Pid, m.Path })
  175. .ExecuteCommand();
  176. }
  177. }
  178. }
  179. }