AidopMenuLinkSync.cs 53 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246
  1. using Admin.NET.Core;
  2. using Admin.NET.Plugin.AiDOP.Entity;
  3. using Microsoft.Extensions.DependencyInjection;
  4. using SqlSugar;
  5. using System.Diagnostics;
  6. namespace Admin.NET.Plugin.AiDOP.Infrastructure;
  7. /// <summary>
  8. /// 将 Ai-DOP 种子菜单 Id 补写到 <c>sys_tenant_menu</c>。
  9. /// 解决:<c>SysTenantMenuSeedData</c> 带 IgnoreUpdateSeed,库已存在时新增菜单不会自动进入租户可用范围;多租户下仅补默认租户会导致其它租户菜单管理不可见。
  10. /// 角色菜单以角色管理保存结果为准;普通启动不得根据缺失项补写 <c>sys_role_menu</c>。
  11. /// </summary>
  12. public static class AidopMenuLinkSync
  13. {
  14. private const long LegacyMaterialSubstitutionMenuId = 1329003000005L;
  15. private const long DeprecatedMaterialSubstitutionMenuId = 1329002000004L;
  16. private const long DeprecatedS8DashboardChildMenuId = 1329008000001L;
  17. private const long S8DirMenuId = 1321000009000L;
  18. public static void EnsureLinked(IServiceProvider services)
  19. {
  20. using var scope = services.CreateScope();
  21. var db = scope.ServiceProvider.GetRequiredService<ISqlSugarClient>();
  22. // Database.json 常关闭 EnableInitSeed/EnableIncreSeed,新增种子菜单不会入库;先补插 S2 三级结构再挂租户/角色。
  23. EnsureS2ManufacturingCollaborationSeedMenus(db);
  24. EnsureS4ProcurementExecutionSeedMenus(db);
  25. EnsureDataPlatformSeedMenus(db);
  26. NormalizeMaterialSubstitutionMenu(db);
  27. NormalizeS1OrderWorkOrderParents(db);
  28. NormalizeS2ManufacturingParents(db);
  29. NormalizeS2ScheduleExceptionMenu(db);
  30. NormalizeS2OperationPlanLeafMenus(db);
  31. NormalizeS2WorkOrderProgressKanbanMenu(db);
  32. NormalizeS3SupplyMenus(db);
  33. NormalizeS4DeliveryMenus(db);
  34. NormalizeS4ReturnMenus(db);
  35. NormalizeS4TopLevelMenus(db);
  36. EnsureLinkagePlanMenu(db);
  37. EnsureImprovementLedgerMenu(db);
  38. RemoveDeprecatedS8DashboardChildMenu(db);
  39. NormalizeS8MenuParents(db);
  40. EnsureS7FqcSpecRuleSeedMenu(db);
  41. var seedMenus = new global::Admin.NET.Plugin.AiDOP.SysMenuSeedData().HasData().ToList();
  42. var seedMenuIds = seedMenus.Select(m => m.Id).ToHashSet();
  43. var existingMenuIds = db.Queryable<SysMenu>()
  44. .Where(m => seedMenuIds.Contains(m.Id))
  45. .Select(m => m.Id)
  46. .ToList()
  47. .ToHashSet();
  48. if (existingMenuIds.Count == 0)
  49. return;
  50. var tenantIds = db.Queryable<SysTenant>().Select(t => t.Id).ToList();
  51. if (tenantIds.Count == 0)
  52. return;
  53. // 租户级抑制清单:登记「某菜单全局存在、但该租户不应看到」的 (TenantId, MenuId) 对。
  54. // 不读它就会把运维刻意取消的租户授权原样补回(主键是 (tid, menuId) 的确定性哈希,连行都一模一样)。
  55. var suppressedPairs = LoadTenantMenuSuppressions(db);
  56. var tenantMenuPairs = db.Queryable<SysTenantMenu>()
  57. .Where(tm => tenantIds.Contains(tm.TenantId) && seedMenuIds.Contains(tm.MenuId))
  58. .Select(tm => new { tm.TenantId, tm.MenuId })
  59. .ToList()
  60. .Select(x => (x.TenantId, x.MenuId))
  61. .ToHashSet();
  62. var tenantRows = new List<SysTenantMenu>();
  63. foreach (var tid in tenantIds)
  64. {
  65. foreach (var menu in seedMenus)
  66. {
  67. if (!existingMenuIds.Contains(menu.Id))
  68. continue;
  69. if (tenantMenuPairs.Contains((tid, menu.Id)))
  70. continue;
  71. if (suppressedPairs.Contains((tid, menu.Id)))
  72. continue;
  73. tenantRows.Add(new SysTenantMenu
  74. {
  75. Id = CommonUtil.GetFixedHashCode("" + tid + menu.Id, 1300000000000),
  76. TenantId = tid,
  77. MenuId = menu.Id
  78. });
  79. }
  80. }
  81. if (tenantRows.Count > 0)
  82. db.Insertable(tenantRows).ExecuteCommand();
  83. // 抑制清单登记之前就已存在的授权行,在这里清掉:让本方法从「覆盖运维意图」变成「强制执行运维意图」,重启即自愈。
  84. EnforceTenantMenuSuppressions(db, suppressedPairs);
  85. }
  86. /// <summary>
  87. /// 读取租户级菜单抑制清单 <c>ado_tenant_menu_suppression</c>。
  88. /// 表可能尚未创建(全新库、版本脚本尚未执行)——此时按「无抑制」处理,不阻断菜单同步。
  89. /// </summary>
  90. private static HashSet<(long TenantId, long MenuId)> LoadTenantMenuSuppressions(ISqlSugarClient db)
  91. {
  92. try
  93. {
  94. if (!db.DbMaintenance.IsAnyTable("ado_tenant_menu_suppression", false))
  95. return new HashSet<(long, long)>();
  96. return db.Queryable<AdoTenantMenuSuppression>()
  97. .Select(x => new { x.TenantId, x.MenuId })
  98. .ToList()
  99. .Select(x => (x.TenantId, x.MenuId))
  100. .ToHashSet();
  101. }
  102. catch (Exception ex)
  103. {
  104. Trace.TraceWarning("Ai-DOP AidopMenuLinkSync 读取租户菜单抑制清单失败,本次按无抑制处理:" + ex);
  105. return new HashSet<(long, long)>();
  106. }
  107. }
  108. /// <summary>
  109. /// 按抑制清单删除对应租户的 <c>sys_tenant_menu</c> 授权行。
  110. /// 只删清单里逐对登记的 (TenantId, MenuId),不动 <c>SysMenu</c> 全局定义、不动 <c>SysRoleMenu</c>
  111. /// (保留角色授权,撤销抑制后可精确还原原可见性)。
  112. /// </summary>
  113. private static void EnforceTenantMenuSuppressions(ISqlSugarClient db, HashSet<(long TenantId, long MenuId)> suppressedPairs)
  114. {
  115. if (suppressedPairs.Count == 0)
  116. return;
  117. foreach (var group in suppressedPairs.GroupBy(p => p.TenantId))
  118. {
  119. var tenantId = group.Key;
  120. var menuIds = group.Select(p => p.MenuId).Distinct().ToList();
  121. db.Deleteable<SysTenantMenu>()
  122. .Where(tm => tm.TenantId == tenantId && menuIds.Contains(tm.MenuId))
  123. .ExecuteCommand();
  124. }
  125. }
  126. /// <summary>
  127. /// S4「采购执行」:数据库关闭种子时菜单不入库,侧栏只能看到历史遗留项(常见仅“供应商交货管理”)。
  128. /// 从 <see cref="SysMenuSeedData.HasData"/> 取完整行按父链补插,确保交货/退货/看板目录与子菜单齐全。
  129. /// </summary>
  130. private static void EnsureS4ProcurementExecutionSeedMenus(ISqlSugarClient db)
  131. {
  132. const long aidopRootId = 1320990000101L;
  133. var fullSeed = new global::Admin.NET.Plugin.AiDOP.SysMenuSeedData().HasData().ToList();
  134. var byId = fullSeed.GroupBy(m => m.Id).ToDictionary(g => g.Key, g => g.First());
  135. var orderedIds = new long[]
  136. {
  137. aidopRootId,
  138. 1321000005000L, // S4 一级目录
  139. 1322000000012L, // 交货管理(Dir)
  140. 1322000000013L, // 退货管理(Dir)
  141. 1322000000014L, // 采购执行看板(Dir)
  142. 1329004100001L, // 供应商交货管理
  143. 1329004100002L, // 供应商发货单
  144. 1329004100003L, // 发货单表单(隐藏)
  145. 1329004200001L, // 采购退货单
  146. 1329004200002L, // 采购退货单表单(隐藏)
  147. 1329004200003L, // IQC 退货查询
  148. 1329004300001L, // 采购执行看板主页
  149. 1329004300002L, // 供应商欠料看板
  150. };
  151. var existingMenus = db.Queryable<SysMenu>().Where(m => orderedIds.Contains(m.Id)).ToList();
  152. var existing = existingMenus.Select(m => m.Id).ToHashSet();
  153. var cumulativeIds = new HashSet<long>(existing);
  154. var toInsert = new List<SysMenu>();
  155. foreach (var id in orderedIds)
  156. {
  157. if (cumulativeIds.Contains(id))
  158. continue;
  159. if (!byId.TryGetValue(id, out var row))
  160. continue;
  161. if (row.Pid != 0 && !cumulativeIds.Contains(row.Pid))
  162. continue;
  163. toInsert.Add(row);
  164. cumulativeIds.Add(id);
  165. }
  166. if (toInsert.Count > 0)
  167. db.Insertable(toInsert).ExecuteCommand();
  168. // 已插入过的演示菜单也需要随种子纠偏;例如「数据源管理」改为「数据地图」时,旧动态路由不更新会导致 404。
  169. foreach (var menu in existingMenus)
  170. {
  171. if (!byId.TryGetValue(menu.Id, out var seed))
  172. continue;
  173. var needFix = menu.Pid != seed.Pid
  174. || menu.Title != seed.Title
  175. || menu.Path != seed.Path
  176. || menu.Name != seed.Name
  177. || menu.Component != seed.Component
  178. || menu.Icon != seed.Icon
  179. || menu.Type != seed.Type
  180. || menu.OrderNo != seed.OrderNo
  181. || menu.Redirect != seed.Redirect
  182. || menu.IsHide != seed.IsHide
  183. || menu.Remark != seed.Remark;
  184. if (!needFix)
  185. continue;
  186. menu.Pid = seed.Pid;
  187. menu.Title = seed.Title;
  188. menu.Path = seed.Path;
  189. menu.Name = seed.Name;
  190. menu.Component = seed.Component;
  191. menu.Icon = seed.Icon;
  192. menu.Type = seed.Type;
  193. menu.OrderNo = seed.OrderNo;
  194. menu.Redirect = seed.Redirect;
  195. menu.IsHide = seed.IsHide;
  196. menu.Remark = seed.Remark;
  197. db.Updateable(menu)
  198. .UpdateColumns(m => new { m.Pid, m.Title, m.Path, m.Name, m.Component, m.Icon, m.Type, m.OrderNo, m.Redirect, m.IsHide, m.Remark })
  199. .ExecuteCommand();
  200. }
  201. }
  202. /// <summary>
  203. /// S7 成品检规判定维护菜单(Phase 1B):种子关闭的共享库不会自动入库新增叶子菜单,
  204. /// 从 <see cref="SysMenuSeedData.HasData"/> 按父链补插「成品检规判定维护」菜单及其 2 个按钮权限点
  205. /// (成品质量管理目录已存在,仅补 3 个新行)。补插后由主授权路径授予租户/角色。
  206. /// </summary>
  207. private static void EnsureS7FqcSpecRuleSeedMenu(ISqlSugarClient db)
  208. {
  209. var fullSeed = new global::Admin.NET.Plugin.AiDOP.SysMenuSeedData().HasData().ToList();
  210. var byId = fullSeed.GroupBy(m => m.Id).ToDictionary(g => g.Key, g => g.First());
  211. var orderedIds = new long[]
  212. {
  213. 1322000000023L, // 成品质量管理(Dir,已存在)
  214. 1329017010003L, // 成品检规判定维护(Menu)
  215. 1329017010004L, // 保存判定条件(Btn 权限点 S7FqcSpecRule:save-rule)
  216. 1329017010005L, // 清空判定条件(Btn 权限点 S7FqcSpecRule:clear-rule)
  217. 1329017010006L, // 成品报检(Menu,Phase 1C)
  218. 1329017010007L, // 检验单录入(隐藏路由,Phase 1E)
  219. };
  220. var existing = db.Queryable<SysMenu>().Where(m => orderedIds.Contains(m.Id)).Select(m => m.Id).ToList().ToHashSet();
  221. var cumulativeIds = new HashSet<long>(existing);
  222. var toInsert = new List<SysMenu>();
  223. foreach (var id in orderedIds)
  224. {
  225. if (cumulativeIds.Contains(id)) continue;
  226. if (!byId.TryGetValue(id, out var row)) continue;
  227. if (row.Pid != 0 && !cumulativeIds.Contains(row.Pid)) continue;
  228. toInsert.Add(row);
  229. cumulativeIds.Add(id);
  230. }
  231. if (toInsert.Count > 0)
  232. db.Insertable(toInsert).ExecuteCommand();
  233. }
  234. /// <summary>
  235. /// 数据中台管理:数据库关闭种子时,补插 Ai-DOP 下的数据中台统一入口和演示子页。
  236. /// </summary>
  237. private static void EnsureDataPlatformSeedMenus(ISqlSugarClient db)
  238. {
  239. const long aidopRootId = 1320990000101L;
  240. var fullSeed = new global::Admin.NET.Plugin.AiDOP.SysMenuSeedData().HasData().ToList();
  241. var byId = fullSeed.GroupBy(m => m.Id).ToDictionary(g => g.Key, g => g.First());
  242. var orderedIds = new long[]
  243. {
  244. aidopRootId,
  245. 1320990000400L,
  246. 1320990000401L,
  247. 1320990000402L,
  248. 1320990000403L,
  249. 1320990000404L,
  250. 1320990000405L,
  251. };
  252. var existing = db.Queryable<SysMenu>().Where(m => orderedIds.Contains(m.Id)).Select(m => m.Id).ToList().ToHashSet();
  253. var cumulativeIds = new HashSet<long>(existing);
  254. var toInsert = new List<SysMenu>();
  255. foreach (var id in orderedIds)
  256. {
  257. if (cumulativeIds.Contains(id))
  258. continue;
  259. if (!byId.TryGetValue(id, out var row))
  260. continue;
  261. if (row.Pid != 0 && !cumulativeIds.Contains(row.Pid))
  262. continue;
  263. toInsert.Add(row);
  264. cumulativeIds.Add(id);
  265. }
  266. if (toInsert.Count > 0)
  267. db.Insertable(toInsert).ExecuteCommand();
  268. }
  269. /// <summary>
  270. /// S2「制造协同」二级目录与三级子菜单:<c>Database.json</c> 关闭种子时不会入库,导致侧栏无「工单工序排程」等项。
  271. /// 从 <see cref="SysMenuSeedData.HasData"/> 取完整行一次性补插(含目录 Redirect)。
  272. /// </summary>
  273. private static void EnsureS2ManufacturingCollaborationSeedMenus(ISqlSugarClient db)
  274. {
  275. const long aidopRootId = 1320990000101L;
  276. var fullSeed = new global::Admin.NET.Plugin.AiDOP.SysMenuSeedData().HasData().ToList();
  277. var byId = fullSeed.GroupBy(m => m.Id).ToDictionary(g => g.Key, g => g.First());
  278. // 父链:Ai-DOP 根 → S2 一级目录 → 三个二级目录 → 三级菜单(同批插入时父行尚未落库,用 cumulativeIds 判定)
  279. var orderedIds = new long[]
  280. {
  281. aidopRootId,
  282. 1321000003000L,
  283. 1322000000006L,
  284. 1322000000007L,
  285. 1322000000008L,
  286. 1322000000108L,
  287. 1329002100001L,
  288. 1329002100011L,
  289. 1329002100012L,
  290. 1329002100013L,
  291. 1329002100014L,
  292. 1329002100015L,
  293. 1329002100021L,
  294. };
  295. var existing = db.Queryable<SysMenu>().Where(m => orderedIds.Contains(m.Id)).Select(m => m.Id).ToList().ToHashSet();
  296. var cumulativeIds = new HashSet<long>(existing);
  297. var toInsert = new List<SysMenu>();
  298. foreach (var id in orderedIds)
  299. {
  300. if (cumulativeIds.Contains(id))
  301. continue;
  302. if (!byId.TryGetValue(id, out var row))
  303. continue;
  304. if (row.Pid != 0 && !cumulativeIds.Contains(row.Pid))
  305. continue;
  306. toInsert.Add(row);
  307. cumulativeIds.Add(id);
  308. }
  309. if (toInsert.Count > 0)
  310. db.Insertable(toInsert).ExecuteCommand();
  311. }
  312. /// <summary>
  313. /// 计划联动看板(Id=1322000000107):<c>Database.json</c> 常关闭 <c>EnableIncreSeed</c>,种子不会入库,故在启动时补插/纠偏。
  314. /// 父级为产销协同看板目录 1322000000005。
  315. /// </summary>
  316. private static void EnsureImprovementLedgerMenu(ISqlSugarClient db)
  317. {
  318. const long menuId = 1320990000314L;
  319. const long smartOpsDirId = 1320990000200L;
  320. if (!db.Queryable<SysMenu>().Any(m => m.Id == smartOpsDirId))
  321. return;
  322. if (db.Queryable<SysMenu>().Any(m => m.Id == menuId))
  323. return;
  324. db.Insertable(new SysMenu
  325. {
  326. Id = menuId,
  327. Pid = smartOpsDirId,
  328. Title = "改善计划台账",
  329. Path = "/aidop/smart-ops/improvement-plans",
  330. Name = "aidopSmartOpsImprovementPlans",
  331. Component = "/aidop/improvement/index",
  332. Icon = "ele-Document",
  333. Type = MenuTypeEnum.Menu,
  334. CreateTime = DateTime.Now,
  335. OrderNo = 195,
  336. Status = StatusEnum.Enable,
  337. Remark = "智慧诊断改善计划台账",
  338. }).ExecuteCommand();
  339. }
  340. private static void EnsureLinkagePlanMenu(ISqlSugarClient db)
  341. {
  342. const long menuId = 1322000000107L;
  343. const long salesKanBanDirId = 1322000000005L;
  344. if (!db.Queryable<SysMenu>().Any(m => m.Id == salesKanBanDirId))
  345. return;
  346. var ct = DateTime.Parse("2022-02-10 00:00:00");
  347. if (!db.Queryable<SysMenu>().Any(m => m.Id == menuId))
  348. {
  349. db.Insertable(new SysMenu
  350. {
  351. Id = menuId,
  352. Pid = salesKanBanDirId,
  353. Title = "计划联动看板",
  354. Path = "/aidop/s1/SalesKanBan/linkage-plan",
  355. Name = "aidopS1LinkagePlan",
  356. Component = "/aidop/business/linkagePlanList",
  357. Icon = "ele-DataBoard",
  358. Type = MenuTypeEnum.Menu,
  359. CreateTime = ct,
  360. OrderNo = 30,
  361. Status = StatusEnum.Enable,
  362. Remark = "S1 计划联动看板(LinkagePlan)"
  363. }).ExecuteCommand();
  364. return;
  365. }
  366. var row = db.Queryable<SysMenu>().First(m => m.Id == menuId);
  367. if (row.Pid != salesKanBanDirId
  368. || row.Path != "/aidop/s1/SalesKanBan/linkage-plan"
  369. || row.Component != "/aidop/business/linkagePlanList")
  370. {
  371. row.Pid = salesKanBanDirId;
  372. row.Path = "/aidop/s1/SalesKanBan/linkage-plan";
  373. row.Name = "aidopS1LinkagePlan";
  374. row.Component = "/aidop/business/linkagePlanList";
  375. row.Title = "计划联动看板";
  376. row.Icon = "ele-DataBoard";
  377. row.OrderNo = 30;
  378. row.Type = MenuTypeEnum.Menu;
  379. db.Updateable(row)
  380. .UpdateColumns(m => new { m.Pid, m.Path, m.Name, m.Component, m.Title, m.Icon, m.OrderNo, m.Type })
  381. .ExecuteCommand();
  382. }
  383. }
  384. private static void NormalizeMaterialSubstitutionMenu(ISqlSugarClient db)
  385. {
  386. var deprecatedMenuId = DeprecatedMaterialSubstitutionMenuId;
  387. var legacyMenuId = LegacyMaterialSubstitutionMenuId;
  388. var legacyMenu = db.Queryable<SysMenu>()
  389. .First(m => m.Id == legacyMenuId);
  390. if (legacyMenu != null)
  391. {
  392. legacyMenu.Title = "物料替代";
  393. legacyMenu.Path = "/aidop/s0/manufacturing/material-substitution";
  394. legacyMenu.Name = "aidopS0MfgMaterialSubstitution";
  395. legacyMenu.Component = "/aidop/s0/manufacturing/MaterialSubstitutionList";
  396. legacyMenu.Remark = "S0 物料替代";
  397. db.Updateable(legacyMenu)
  398. .UpdateColumns(m => new { m.Title, m.Path, m.Name, m.Component, m.Remark })
  399. .ExecuteCommand();
  400. }
  401. // Id 1329002000004 历史上是旧「物料替代」菜单,已迁移到 1329003000005;但该 Id 现被
  402. // SysMenuSeedData「合同评审周期」(/aidop/s0/sales/contract-review-cycle) 合法复用。
  403. // 旧逻辑仅凭 Id 无条件删除,会在每次启动抹掉合同评审周期菜单及其租户/角色授权。
  404. // 改为语义判别:是合同评审周期则保护,明确是旧物料替代才清理,语义不明保守跳过。
  405. // 用原生 SQL 读取该行,绕过 SqlSugar 查询缓存/全局过滤:直连 DB 写入的合同评审周期行
  406. // 在 ORM 缓存路径下可能被读成迁移前的旧「物料替代」陈旧行,导致误判删除(启动期偶发)。
  407. var deprecated = db.Ado
  408. .SqlQuery<SysMenu>("SELECT * FROM SysMenu WHERE Id = @id", new SugarParameter("@id", deprecatedMenuId))
  409. .FirstOrDefault();
  410. if (deprecated == null)
  411. return;
  412. if (IsContractReviewCycleMenu(deprecated))
  413. {
  414. Trace.TraceInformation($"AidopMenuLinkSync: Id={deprecatedMenuId} 现为「合同评审周期」菜单,跳过旧物料替代清理(保护)。");
  415. return;
  416. }
  417. if (!IsLegacyMaterialSubstitutionMenu(deprecated))
  418. {
  419. Trace.TraceWarning($"AidopMenuLinkSync: Id={deprecatedMenuId} 语义不明(Title={deprecated.Title}, Path={deprecated.Path}),保守跳过,不删除。");
  420. return;
  421. }
  422. // 仅当确认为旧「物料替代」菜单时,才执行历史清理。
  423. db.Deleteable<SysUserMenu>().Where(x => x.MenuId == deprecatedMenuId).ExecuteCommand();
  424. db.Deleteable<SysRoleMenu>().Where(x => x.MenuId == deprecatedMenuId).ExecuteCommand();
  425. db.Deleteable<SysTenantMenu>().Where(x => x.MenuId == deprecatedMenuId).ExecuteCommand();
  426. db.Deleteable<SysMenu>().Where(x => x.Id == deprecatedMenuId).ExecuteCommand();
  427. }
  428. /// <summary>
  429. /// 判断给定菜单是否为 S0「合同评审周期」菜单(Id 1329002000004 的当前合法占用者)。
  430. /// </summary>
  431. private static bool IsContractReviewCycleMenu(SysMenu m) =>
  432. m.Title == "合同评审周期"
  433. || m.Path == "/aidop/s0/sales/contract-review-cycle"
  434. || m.Name == "aidopS0SalesContractReviewCycle"
  435. || m.Component == "/aidop/s0/sales/ContractReviewCycleList";
  436. /// <summary>
  437. /// 判断给定菜单是否为旧「物料替代」菜单(迁移前曾占用 Id 1329002000004)。
  438. /// 依据 Phase 0 已举证的旧字段语义:Title 含「物料替代」,或 Path/Name/Component 含 material-substitution / MaterialSubstitution。
  439. /// </summary>
  440. private static bool IsLegacyMaterialSubstitutionMenu(SysMenu m) =>
  441. (m.Title?.Contains("物料替代") ?? false)
  442. || (m.Path?.Contains("material-substitution") ?? false)
  443. || (m.Name?.Contains("MaterialSubstitution") ?? false)
  444. || (m.Component?.Contains("MaterialSubstitution") ?? false);
  445. private static void RemoveDeprecatedS8DashboardChildMenu(ISqlSugarClient db)
  446. {
  447. const long deprecatedMenuId = DeprecatedS8DashboardChildMenuId;
  448. var deprecatedExists = db.Queryable<SysMenu>().Any(m => m.Id == deprecatedMenuId);
  449. if (!deprecatedExists)
  450. return;
  451. db.Deleteable<SysUserMenu>().Where(x => x.MenuId == deprecatedMenuId).ExecuteCommand();
  452. db.Deleteable<SysRoleMenu>().Where(x => x.MenuId == deprecatedMenuId).ExecuteCommand();
  453. db.Deleteable<SysTenantMenu>().Where(x => x.MenuId == deprecatedMenuId).ExecuteCommand();
  454. db.Deleteable<SysMenu>().Where(x => x.Id == deprecatedMenuId).ExecuteCommand();
  455. }
  456. private static void NormalizeS8MenuParents(ISqlSugarClient db)
  457. {
  458. var s8MenuIds = new long[]
  459. {
  460. 1329008000002L,
  461. 1329008000003L,
  462. 1329008000004L,
  463. 1329008000010L,
  464. 1329008000011L,
  465. 1329008000012L,
  466. 1329008000013L,
  467. 1329008000014L,
  468. 1329008000015L,
  469. 1329008000016L,
  470. 1329008000020L
  471. };
  472. db.Updateable<SysMenu>()
  473. .SetColumns(x => x.Pid == S8DirMenuId)
  474. .Where(x => s8MenuIds.Contains(x.Id) && x.Pid != S8DirMenuId)
  475. .ExecuteCommand();
  476. }
  477. /// <summary>
  478. /// 将 S1「订单管理」「工单管理」升级为目录并修正 path/name;
  479. /// 「产销协同看板」单独菜单路径为 /aidop/s1/SalesKanBan;
  480. /// 「工单下达」父级须为工单管理目录 1322000000004(历史错误曾挂到 0005)。
  481. /// 「工单工序排产」1322000000108 已迁至 S2「生产排程」下,见 <see cref="NormalizeS2ManufacturingParents"/>。
  482. /// </summary>
  483. private static void NormalizeS1OrderWorkOrderParents(ISqlSugarClient db)
  484. {
  485. const long orderMgmtId = 1322000000003L;
  486. const long workOrderMgmtId = 1322000000004L;
  487. const long salesKanBanMenuId = 1322000000005L;
  488. const long workOrderDispatchMenuId = 1322000000106L;
  489. var orderMgmt = db.Queryable<SysMenu>().First(m => m.Id == orderMgmtId);
  490. if (orderMgmt != null)
  491. {
  492. orderMgmt.Type = MenuTypeEnum.Dir;
  493. orderMgmt.Component = "Layout";
  494. orderMgmt.Path = "/aidop/s1/order-mgmt";
  495. orderMgmt.Name = "aidopS1OrderMgmt";
  496. orderMgmt.Icon = "ele-Folder";
  497. db.Updateable(orderMgmt)
  498. .UpdateColumns(m => new { m.Type, m.Component, m.Path, m.Name, m.Icon })
  499. .ExecuteCommand();
  500. }
  501. var workOrderMgmt = db.Queryable<SysMenu>().First(m => m.Id == workOrderMgmtId);
  502. if (workOrderMgmt != null)
  503. {
  504. workOrderMgmt.Type = MenuTypeEnum.Dir;
  505. workOrderMgmt.Component = "Layout";
  506. workOrderMgmt.Path = "/aidop/s1/workorder-mgmt";
  507. workOrderMgmt.Name = "aidopS1WorkOrderMgmt";
  508. workOrderMgmt.Icon = "ele-Folder";
  509. db.Updateable(workOrderMgmt)
  510. .UpdateColumns(m => new { m.Type, m.Component, m.Path, m.Name, m.Icon })
  511. .ExecuteCommand();
  512. }
  513. var salesKanBan = db.Queryable<SysMenu>().First(m => m.Id == salesKanBanMenuId);
  514. if (salesKanBan != null)
  515. {
  516. salesKanBan.Type = MenuTypeEnum.Menu;
  517. salesKanBan.Component = "/aidop/kanban/s1";
  518. salesKanBan.Path = "/aidop/s1/SalesKanBan";
  519. salesKanBan.Name = "aidopS1003";
  520. salesKanBan.Icon = "ele-DataAnalysis";
  521. db.Updateable(salesKanBan)
  522. .UpdateColumns(m => new { m.Type, m.Component, m.Path, m.Name, m.Icon })
  523. .ExecuteCommand();
  524. }
  525. var dispatch = db.Queryable<SysMenu>().First(m => m.Id == workOrderDispatchMenuId);
  526. if (dispatch != null)
  527. {
  528. var needFix = dispatch.Pid != workOrderMgmtId
  529. || dispatch.Path != "/aidop/s1/workorder-mgmt/dispatch";
  530. if (needFix)
  531. {
  532. dispatch.Pid = workOrderMgmtId;
  533. dispatch.Path = "/aidop/s1/workorder-mgmt/dispatch";
  534. db.Updateable(dispatch)
  535. .UpdateColumns(m => new { m.Pid, m.Path })
  536. .ExecuteCommand();
  537. }
  538. }
  539. }
  540. /// <summary>
  541. /// S2「生产排程 / 作业计划 / 制造协同看板」升级为目录;「工单工序排产」父级为生产排程 1322000000006,path 为 S2 路由。
  542. /// </summary>
  543. private static void NormalizeS2ManufacturingParents(ISqlSugarClient db)
  544. {
  545. const long productionSchedulingId = 1322000000006L;
  546. const long operationPlanId = 1322000000007L;
  547. const long collaborationKanbanId = 1322000000008L;
  548. const long workOrderSchedulingMenuId = 1322000000108L;
  549. void EnsureDir(long id, string path, string name)
  550. {
  551. var m = db.Queryable<SysMenu>().First(x => x.Id == id);
  552. if (m == null)
  553. return;
  554. m.Type = MenuTypeEnum.Dir;
  555. m.Component = "Layout";
  556. m.Path = path;
  557. m.Name = name;
  558. m.Icon = "ele-Folder";
  559. db.Updateable(m)
  560. .UpdateColumns(x => new { x.Type, x.Component, x.Path, x.Name, x.Icon })
  561. .ExecuteCommand();
  562. }
  563. EnsureDir(productionSchedulingId, "/aidop/s2/production-scheduling", "aidopS2ProductionScheduling");
  564. EnsureDir(operationPlanId, "/aidop/s2/operation-plan", "aidopS2OperationPlan");
  565. EnsureDir(collaborationKanbanId, "/aidop/s2/collaboration-kanban", "aidopS2CollaborationKanban");
  566. var scheduling = db.Queryable<SysMenu>().First(m => m.Id == workOrderSchedulingMenuId);
  567. if (scheduling == null)
  568. return;
  569. const string s2SchedulingPath = "/aidop/s2/production-scheduling/work-order-scheduling";
  570. var needFix = scheduling.Pid != productionSchedulingId
  571. || scheduling.Path != s2SchedulingPath
  572. || scheduling.Name != "aidopS2WorkOrderScheduling";
  573. if (!needFix)
  574. return;
  575. scheduling.Pid = productionSchedulingId;
  576. scheduling.Path = s2SchedulingPath;
  577. scheduling.Name = "aidopS2WorkOrderScheduling";
  578. db.Updateable(scheduling)
  579. .UpdateColumns(m => new { m.Pid, m.Path, m.Name })
  580. .ExecuteCommand();
  581. }
  582. /// <summary>
  583. /// S2「排产异常记录」由占位页 <c>/aidop/planning/index</c> 改为真实列表页 <c>/aidop/production/scheduleExceptionList</c>。
  584. /// </summary>
  585. private static void NormalizeS2ScheduleExceptionMenu(ISqlSugarClient db)
  586. {
  587. const long menuId = 1329002100001L;
  588. const string component = "/aidop/production/scheduleExceptionList";
  589. var m = db.Queryable<SysMenu>().First(x => x.Id == menuId);
  590. if (m == null)
  591. return;
  592. if (m.Component == component)
  593. return;
  594. m.Component = component;
  595. m.Remark = "S2 排产异常记录";
  596. db.Updateable(m)
  597. .UpdateColumns(x => new { x.Component, x.Remark })
  598. .ExecuteCommand();
  599. }
  600. /// <summary>
  601. /// S2「作业计划」下三页由占位改为真实组件(库中已落旧 <c>/aidop/planning/index</c> 时纠偏)。
  602. /// </summary>
  603. private static void NormalizeS2OperationPlanLeafMenus(ISqlSugarClient db)
  604. {
  605. (long Id, string Component, string Title)[] map =
  606. {
  607. (1329002100011L, "/aidop/production/executableDailyPlanList", "可执行日计划"),
  608. (1329002100012L, "/aidop/production/shopCalendarWorkCtrList", "产线工作日历管理"),
  609. (1329002100013L, "/aidop/production/qualityLineRestDetailList", "产线休息时间管理"),
  610. (1329002100014L, "/aidop/production/holidayMasterList", "产线节假日管理"),
  611. (1329002100015L, "/aidop/production/lineOvertimeList", "产线加班管理"),
  612. };
  613. foreach (var (id, component, title) in map)
  614. {
  615. var m = db.Queryable<SysMenu>().First(x => x.Id == id);
  616. if (m == null)
  617. continue;
  618. if (m.Component == component && m.Title == title)
  619. continue;
  620. m.Component = component;
  621. m.Title = title;
  622. db.Updateable(m)
  623. .UpdateColumns(x => new { x.Component, x.Title })
  624. .ExecuteCommand();
  625. }
  626. }
  627. /// <summary>
  628. /// S2「工单执行进度看板」由占位 <c>/aidop/kanban/s2</c> 改为列表页 <c>/aidop/production/workOrderProgressDashboardList</c>。
  629. /// </summary>
  630. private static void NormalizeS2WorkOrderProgressKanbanMenu(ISqlSugarClient db)
  631. {
  632. const long menuId = 1329002100021L;
  633. const string component = "/aidop/production/workOrderProgressDashboardList";
  634. var m = db.Queryable<SysMenu>().First(x => x.Id == menuId);
  635. if (m == null)
  636. return;
  637. if (m.Component == component)
  638. return;
  639. m.Component = component;
  640. m.Remark = "S2 工单执行进度看板(列表)";
  641. db.Updateable(m)
  642. .UpdateColumns(x => new { x.Component, x.Remark })
  643. .ExecuteCommand();
  644. }
  645. /// <summary>
  646. /// S3「物料计划」下三项菜单:种子关闭时补插,已存在时纠偏到真实组件页。
  647. /// </summary>
  648. private static void NormalizeS3SupplyMenus(ISqlSugarClient db)
  649. {
  650. const long s3RootId = 1321000004000L;
  651. const long parentId = 1322000000009L;
  652. const long procurementDirId = 1322000000010L;
  653. if (!db.Queryable<SysMenu>().Any(m => m.Id == parentId) || !db.Queryable<SysMenu>().Any(m => m.Id == procurementDirId))
  654. return;
  655. var procurementDir = db.Queryable<SysMenu>().First(m => m.Id == procurementDirId);
  656. if (procurementDir != null)
  657. {
  658. var procurementNeedFix = procurementDir.Pid != s3RootId
  659. || procurementDir.Title != "采购管理"
  660. || procurementDir.Path != "/aidop/s3/procurement"
  661. || procurementDir.Name != "aidopS3Procurement"
  662. || procurementDir.Component != "Layout"
  663. || procurementDir.Type != MenuTypeEnum.Dir
  664. || procurementDir.Icon != "ele-Folder"
  665. || procurementDir.Redirect != "/aidop/s3/procurement/purchase-request"
  666. || procurementDir.Remark != "S3 采购管理";
  667. if (procurementNeedFix)
  668. {
  669. procurementDir.Pid = s3RootId;
  670. procurementDir.Title = "采购管理";
  671. procurementDir.Path = "/aidop/s3/procurement";
  672. procurementDir.Name = "aidopS3Procurement";
  673. procurementDir.Component = "Layout";
  674. procurementDir.Type = MenuTypeEnum.Dir;
  675. procurementDir.Icon = "ele-Folder";
  676. procurementDir.Redirect = "/aidop/s3/procurement/purchase-request";
  677. procurementDir.Remark = "S3 采购管理";
  678. db.Updateable(procurementDir)
  679. .UpdateColumns(m => new { m.Pid, m.Title, m.Path, m.Name, m.Component, m.Type, m.Icon, m.Redirect, m.Remark })
  680. .ExecuteCommand();
  681. }
  682. }
  683. var ct = DateTime.Parse("2022-02-10 00:00:00");
  684. var defs = new (long Id, string Title, string Path, string Name, string Component, string Icon, int OrderNo, string Remark)[]
  685. {
  686. (
  687. 1329003100001L,
  688. "物料需求计划",
  689. "/aidop/s3/material-plan/demand-schedule",
  690. "aidopS3DemandSchedule",
  691. "/aidop/s3/supply/demandScheduleList",
  692. "ele-Calendar",
  693. 10,
  694. "S3 物料需求计划(ic_demandschedule)"
  695. ),
  696. (
  697. 1329003100002L,
  698. "物料交货计划",
  699. "/aidop/s3/material-plan/delivery-schedule",
  700. "aidopS3DeliverySchedule",
  701. "/aidop/s3/supply/deliveryScheduleList",
  702. "ele-Document",
  703. 20,
  704. "S3 物料交货计划(srm_polist_ds)"
  705. ),
  706. (
  707. 1329003100003L,
  708. "交货单异常记录",
  709. "/aidop/s3/material-plan/delivery-exception",
  710. "aidopS3DeliveryException",
  711. "/aidop/s3/supply/deliveryExceptionList",
  712. "ele-Warning",
  713. 30,
  714. "S3 交货单异常记录(DeliveryExceptionMaster)"
  715. ),
  716. };
  717. foreach (var def in defs)
  718. {
  719. var row = db.Queryable<SysMenu>().First(m => m.Id == def.Id);
  720. if (row == null)
  721. {
  722. db.Insertable(new SysMenu
  723. {
  724. Id = def.Id,
  725. Pid = parentId,
  726. Title = def.Title,
  727. Path = def.Path,
  728. Name = def.Name,
  729. Component = def.Component,
  730. Icon = def.Icon,
  731. Type = MenuTypeEnum.Menu,
  732. CreateTime = ct,
  733. OrderNo = def.OrderNo,
  734. Status = StatusEnum.Enable,
  735. Remark = def.Remark
  736. }).ExecuteCommand();
  737. continue;
  738. }
  739. var needFix = row.Pid != parentId
  740. || row.Path != def.Path
  741. || row.Name != def.Name
  742. || row.Component != def.Component
  743. || row.Title != def.Title
  744. || row.Icon != def.Icon
  745. || row.OrderNo != def.OrderNo
  746. || row.Type != MenuTypeEnum.Menu
  747. || row.Remark != def.Remark;
  748. if (!needFix)
  749. continue;
  750. row.Pid = parentId;
  751. row.Path = def.Path;
  752. row.Name = def.Name;
  753. row.Component = def.Component;
  754. row.Title = def.Title;
  755. row.Icon = def.Icon;
  756. row.OrderNo = def.OrderNo;
  757. row.Type = MenuTypeEnum.Menu;
  758. row.Remark = def.Remark;
  759. db.Updateable(row)
  760. .UpdateColumns(m => new { m.Pid, m.Path, m.Name, m.Component, m.Title, m.Icon, m.OrderNo, m.Type, m.Remark })
  761. .ExecuteCommand();
  762. }
  763. EnsureS3LeafMenu(
  764. db, ct, 1329003100011L, procurementDirId, "物料采购申请",
  765. "/aidop/s3/procurement/purchase-request", "aidopS3PurchaseRequest",
  766. "/aidop/s3/supply/purchaseRequestList", "ele-Document", 10, "S3 物料采购申请(srm_pr_main)");
  767. EnsureS3LeafMenu(
  768. db, ct, 1329003100012L, procurementDirId, "物料采购订单",
  769. "/aidop/s3/procurement/purchase-order", "aidopS3PurchaseOrder",
  770. "/aidop/s3/supply/purchaseOrderList", "ele-Tickets", 20, "S3 物料采购订单(vscm_jhjh)");
  771. EnsureS3LeafMenu(
  772. db, ct, 1329003100013L, procurementDirId, "委外加工订单",
  773. "/aidop/s3/procurement/outsource-order", "aidopS3OutsourceOrder",
  774. "/aidop/s3/supply/outsourceOrderList", "ele-List", 30, "S3 委外加工订单(PurOrdMaster/PW)");
  775. EnsureS3LeafMenu(
  776. db, ct, 1329003100014L, procurementDirId, "工序外协订单",
  777. "/aidop/s3/procurement/process-outsource-order", "aidopS3ProcessOutsourceOrder",
  778. "/aidop/s3/supply/processOutsourceOrderList", "ele-List", 40, "S3 工序外协订单(PurOrdMaster/PW)");
  779. EnsureS3LeafMenu(
  780. db, ct, 1329003100018L, procurementDirId, "要货令",
  781. "/aidop/s3/procurement/demand-order", "aidopS3DemandOrder",
  782. "/aidop/s3/supply/demandOrderList", "ele-ShoppingCart", 15, "S3 要货令(PurOrdMaster/PO,ReqBy=DO)");
  783. EnsureS3DirMenu(
  784. db, ct, 1329003100010L, s3RootId, "供应协同看板",
  785. "/aidop/s3/supply-kanban", "aidopS3SupplyKanbanDir",
  786. "/aidop/s3/supply-kanban/dashboard", "S3 供应协同看板目录");
  787. EnsureS3LeafMenu(
  788. db, ct, 1329003100015L, 1329003100010L, "供应协同看板",
  789. "/aidop/s3/supply-kanban/dashboard", "aidopS3SupplyKanban",
  790. "/aidop/kanban/s3", "ele-DataBoard", 10, "S3 供应协同看板");
  791. EnsureS3LeafMenu(
  792. db, ct, 1329003100016L, 1329003100010L, "工单物料齐套上线看板",
  793. "/aidop/s3/supply-kanban/work-order-material-readiness", "aidopS3WorkOrderMaterialReadiness",
  794. "/aidop/s3/supply/workOrderMaterialReadinessKanban", "ele-List", 20, "S3 工单物料齐套上线看板");
  795. EnsureS3LeafMenu(
  796. db, ct, 1329003100017L, 1329003100010L, "MDP运行监控",
  797. "/aidop/s3/supply-kanban/mdp-monitor", "aidopS3MdpMonitor",
  798. "/aidop/s3/supply/mdpMonitorList", "ele-Monitor", 30, "S3 MDP 同步与转换运行监控");
  799. }
  800. /// <summary>
  801. /// S4「交货管理」目录与子菜单纠偏/补齐。
  802. /// </summary>
  803. private static void NormalizeS4DeliveryMenus(ISqlSugarClient db)
  804. {
  805. const long s4RootId = 1321000005000L;
  806. const long deliveryDirId = 1322000000012L;
  807. if (!db.Queryable<SysMenu>().Any(m => m.Id == deliveryDirId))
  808. return;
  809. var dir = db.Queryable<SysMenu>().First(m => m.Id == deliveryDirId);
  810. if (dir != null)
  811. {
  812. var needFix = dir.Pid != s4RootId
  813. || dir.Title != "交货管理"
  814. || dir.Path != "/aidop/s4/delivery"
  815. || dir.Name != "aidopS4Delivery"
  816. || dir.Component != "Layout"
  817. || dir.Type != MenuTypeEnum.Dir
  818. || dir.Icon != "ele-Folder"
  819. || dir.Redirect != "/aidop/s4/delivery/supplier-delivery-management";
  820. if (needFix)
  821. {
  822. dir.Pid = s4RootId;
  823. dir.Title = "交货管理";
  824. dir.Path = "/aidop/s4/delivery";
  825. dir.Name = "aidopS4Delivery";
  826. dir.Component = "Layout";
  827. dir.Type = MenuTypeEnum.Dir;
  828. dir.Icon = "ele-Folder";
  829. dir.Redirect = "/aidop/s4/delivery/supplier-delivery-management";
  830. db.Updateable(dir)
  831. .UpdateColumns(m => new { m.Pid, m.Title, m.Path, m.Name, m.Component, m.Type, m.Icon, m.Redirect })
  832. .ExecuteCommand();
  833. }
  834. }
  835. var ct = DateTime.Parse("2022-02-10 00:00:00");
  836. EnsureS3LeafMenu(
  837. db, ct, 1329004100001L, deliveryDirId, "供应商交货管理",
  838. "/aidop/s4/delivery/supplier-delivery-management", "aidopS4SupplierDeliveryManagement",
  839. "/aidop/s4/delivery/supplierDeliveryManagementList", "ele-Document", 10, "S4 供应商交货管理");
  840. EnsureS3LeafMenu(
  841. db, ct, 1329004100002L, deliveryDirId, "供应商发货单",
  842. "/aidop/s4/delivery/supplier-shipment", "aidopS4SupplierShipment",
  843. "/aidop/s4/delivery/supplierShipmentList", "ele-Tickets", 20, "S4 供应商发货单");
  844. var formMenu = db.Queryable<SysMenu>().First(m => m.Id == 1329004100003L);
  845. if (formMenu == null)
  846. {
  847. db.Insertable(new SysMenu
  848. {
  849. Id = 1329004100003L,
  850. Pid = deliveryDirId,
  851. Title = "发货单表单",
  852. Path = "/aidop/s4/delivery/supplier-shipment-form",
  853. Name = "aidopS4SupplierShipmentForm",
  854. Component = "/aidop/s4/delivery/supplierShipmentForm",
  855. Icon = "ele-Document",
  856. Type = MenuTypeEnum.Menu,
  857. IsHide = true,
  858. CreateTime = ct,
  859. OrderNo = 90,
  860. Status = StatusEnum.Enable,
  861. Remark = "S4 发货单新增/编辑/查看表单"
  862. }).ExecuteCommand();
  863. return;
  864. }
  865. var formNeedFix = formMenu.Pid != deliveryDirId
  866. || formMenu.Path != "/aidop/s4/delivery/supplier-shipment-form"
  867. || formMenu.Name != "aidopS4SupplierShipmentForm"
  868. || formMenu.Component != "/aidop/s4/delivery/supplierShipmentForm"
  869. || formMenu.Title != "发货单表单"
  870. || formMenu.Icon != "ele-Document"
  871. || formMenu.Type != MenuTypeEnum.Menu
  872. || formMenu.OrderNo != 90
  873. || formMenu.IsHide != true
  874. || formMenu.Remark != "S4 发货单新增/编辑/查看表单";
  875. if (!formNeedFix)
  876. return;
  877. formMenu.Pid = deliveryDirId;
  878. formMenu.Path = "/aidop/s4/delivery/supplier-shipment-form";
  879. formMenu.Name = "aidopS4SupplierShipmentForm";
  880. formMenu.Component = "/aidop/s4/delivery/supplierShipmentForm";
  881. formMenu.Title = "发货单表单";
  882. formMenu.Icon = "ele-Document";
  883. formMenu.Type = MenuTypeEnum.Menu;
  884. formMenu.OrderNo = 90;
  885. formMenu.IsHide = true;
  886. formMenu.Remark = "S4 发货单新增/编辑/查看表单";
  887. db.Updateable(formMenu)
  888. .UpdateColumns(m => new { m.Pid, m.Path, m.Name, m.Component, m.Title, m.Icon, m.Type, m.OrderNo, m.IsHide, m.Remark })
  889. .ExecuteCommand();
  890. }
  891. private static void NormalizeS4ReturnMenus(ISqlSugarClient db)
  892. {
  893. const long s4RootId = 1321000005000L;
  894. const long returnDirId = 1322000000013L;
  895. if (!db.Queryable<SysMenu>().Any(m => m.Id == returnDirId))
  896. return;
  897. var dir = db.Queryable<SysMenu>().First(m => m.Id == returnDirId);
  898. if (dir != null)
  899. {
  900. var needFix = dir.Pid != s4RootId
  901. || dir.Title != "退货管理"
  902. || dir.Path != "/aidop/s4/return-mgmt"
  903. || dir.Name != "aidopS4ReturnMgmt"
  904. || dir.Component != "Layout"
  905. || dir.Type != MenuTypeEnum.Dir
  906. || dir.Icon != "ele-Folder"
  907. || dir.Redirect != "/aidop/s4/return-mgmt/purchase-return-order";
  908. if (needFix)
  909. {
  910. dir.Pid = s4RootId;
  911. dir.Title = "退货管理";
  912. dir.Path = "/aidop/s4/return-mgmt";
  913. dir.Name = "aidopS4ReturnMgmt";
  914. dir.Component = "Layout";
  915. dir.Type = MenuTypeEnum.Dir;
  916. dir.Icon = "ele-Folder";
  917. dir.Redirect = "/aidop/s4/return-mgmt/purchase-return-order";
  918. dir.Remark = "S4 退货管理";
  919. db.Updateable(dir)
  920. .UpdateColumns(m => new { m.Pid, m.Title, m.Path, m.Name, m.Component, m.Type, m.Icon, m.Redirect, m.Remark })
  921. .ExecuteCommand();
  922. }
  923. }
  924. var ct = DateTime.Parse("2022-02-10 00:00:00");
  925. EnsureS3LeafMenu(
  926. db, ct, 1329004200001L, returnDirId, "采购退货单",
  927. "/aidop/s4/return-mgmt/purchase-return-order", "aidopS4PurchaseReturnOrder",
  928. "/aidop/s4/return/purchaseReturnOrderList", "ele-Document", 10, "S4 采购退货单");
  929. EnsureS3LeafMenu(
  930. db, ct, 1329004200003L, returnDirId, "IQC 退货查询",
  931. "/aidop/s4/return-mgmt/iqc-return-query", "aidopS4IqcReturnQuery",
  932. "/aidop/s4/return/iqcReturnQueryList", "ele-Search", 20, "S4 IQC 退货查询(只读列表)");
  933. var formMenu = db.Queryable<SysMenu>().First(m => m.Id == 1329004200002L);
  934. if (formMenu == null)
  935. {
  936. db.Insertable(new SysMenu
  937. {
  938. Id = 1329004200002L,
  939. Pid = returnDirId,
  940. Title = "采购退货单表单",
  941. Path = "/aidop/s4/return-mgmt/purchase-return-order-form",
  942. Name = "aidopS4PurchaseReturnOrderForm",
  943. Component = "/aidop/s4/return/purchaseReturnOrderForm",
  944. Icon = "ele-Document",
  945. Type = MenuTypeEnum.Menu,
  946. IsHide = true,
  947. CreateTime = ct,
  948. OrderNo = 90,
  949. Status = StatusEnum.Enable,
  950. Remark = "S4 采购退货单新增/编辑/查看表单"
  951. }).ExecuteCommand();
  952. }
  953. else
  954. {
  955. var formNeedFix = formMenu.Pid != returnDirId
  956. || formMenu.Path != "/aidop/s4/return-mgmt/purchase-return-order-form"
  957. || formMenu.Name != "aidopS4PurchaseReturnOrderForm"
  958. || formMenu.Component != "/aidop/s4/return/purchaseReturnOrderForm"
  959. || formMenu.Title != "采购退货单表单"
  960. || formMenu.Icon != "ele-Document"
  961. || formMenu.Type != MenuTypeEnum.Menu
  962. || formMenu.OrderNo != 90
  963. || formMenu.IsHide != true;
  964. if (formNeedFix)
  965. {
  966. formMenu.Pid = returnDirId;
  967. formMenu.Path = "/aidop/s4/return-mgmt/purchase-return-order-form";
  968. formMenu.Name = "aidopS4PurchaseReturnOrderForm";
  969. formMenu.Component = "/aidop/s4/return/purchaseReturnOrderForm";
  970. formMenu.Title = "采购退货单表单";
  971. formMenu.Icon = "ele-Document";
  972. formMenu.Type = MenuTypeEnum.Menu;
  973. formMenu.OrderNo = 90;
  974. formMenu.IsHide = true;
  975. formMenu.Remark = "S4 采购退货单新增/编辑/查看表单";
  976. db.Updateable(formMenu)
  977. .UpdateColumns(m => new { m.Pid, m.Path, m.Name, m.Component, m.Title, m.Icon, m.Type, m.OrderNo, m.IsHide, m.Remark })
  978. .ExecuteCommand();
  979. }
  980. }
  981. }
  982. /// <summary>S4:隐藏规划占位「采购管理」;纠正「采购执行看板」路径与组件。</summary>
  983. private static void NormalizeS4TopLevelMenus(ISqlSugarClient db)
  984. {
  985. const long purchaseMgmtPlaceholderId = 1322000000011L;
  986. const long executionKanbanId = 1322000000014L;
  987. if (db.Queryable<SysMenu>().Any(m => m.Id == purchaseMgmtPlaceholderId))
  988. {
  989. var proc = db.Queryable<SysMenu>().First(m => m.Id == purchaseMgmtPlaceholderId);
  990. if (proc != null && proc.IsHide != true)
  991. {
  992. proc.IsHide = true;
  993. db.Updateable(proc).UpdateColumns(m => new { m.IsHide }).ExecuteCommand();
  994. }
  995. }
  996. if (!db.Queryable<SysMenu>().Any(m => m.Id == executionKanbanId))
  997. return;
  998. var kb = db.Queryable<SysMenu>().First(m => m.Id == executionKanbanId);
  999. var needFix = kb.Path != "/aidop/s4/execution-kanban"
  1000. || kb.Name != "aidopS4ExecutionKanban"
  1001. || kb.Component != "Layout"
  1002. || kb.Type != MenuTypeEnum.Dir
  1003. || kb.Icon != "ele-Folder"
  1004. || kb.Redirect != "/aidop/s4/execution-kanban/dashboard";
  1005. if (needFix)
  1006. {
  1007. kb.Path = "/aidop/s4/execution-kanban";
  1008. kb.Name = "aidopS4ExecutionKanban";
  1009. kb.Component = "Layout";
  1010. kb.Type = MenuTypeEnum.Dir;
  1011. kb.Icon = "ele-Folder";
  1012. kb.Redirect = "/aidop/s4/execution-kanban/dashboard";
  1013. kb.Remark = "S4 采购执行看板目录(含主页 + 供应商欠料看板)";
  1014. db.Updateable(kb)
  1015. .UpdateColumns(m => new { m.Path, m.Name, m.Component, m.Type, m.Icon, m.Redirect, m.Remark })
  1016. .ExecuteCommand();
  1017. }
  1018. var ct = DateTime.Parse("2022-02-10 00:00:00");
  1019. EnsureS3LeafMenu(
  1020. db, ct, 1329004300001L, executionKanbanId, "采购执行看板主页",
  1021. "/aidop/s4/execution-kanban/dashboard", "aidopS4ExecutionKanbanDashboard",
  1022. "/aidop/kanban/s4", "ele-DataBoard", 10, "S4 采购执行看板主页(/aidop/kanban/s4)");
  1023. EnsureS3LeafMenu(
  1024. db, ct, 1329004300002L, executionKanbanId, "供应商欠料看板",
  1025. "/aidop/s4/execution-kanban/supplier-shortage-kanban", "aidopS4SupplierShortageKanban",
  1026. "/aidop/s4/execution-kanban/supplierShortageKanbanList", "ele-List", 20, "S4 供应商欠料看板(WorkOrdDetailTotalKB)");
  1027. }
  1028. private static void EnsureS3LeafMenu(
  1029. ISqlSugarClient db,
  1030. DateTime ct,
  1031. long id,
  1032. long pid,
  1033. string title,
  1034. string path,
  1035. string name,
  1036. string component,
  1037. string icon,
  1038. int orderNo,
  1039. string remark)
  1040. {
  1041. var row = db.Queryable<SysMenu>().First(m => m.Id == id);
  1042. if (row == null)
  1043. {
  1044. db.Insertable(new SysMenu
  1045. {
  1046. Id = id,
  1047. Pid = pid,
  1048. Title = title,
  1049. Path = path,
  1050. Name = name,
  1051. Component = component,
  1052. Icon = icon,
  1053. Type = MenuTypeEnum.Menu,
  1054. CreateTime = ct,
  1055. OrderNo = orderNo,
  1056. Status = StatusEnum.Enable,
  1057. Remark = remark
  1058. }).ExecuteCommand();
  1059. return;
  1060. }
  1061. var needFix = row.Pid != pid
  1062. || row.Title != title
  1063. || row.Path != path
  1064. || row.Name != name
  1065. || row.Component != component
  1066. || row.Icon != icon
  1067. || row.Type != MenuTypeEnum.Menu
  1068. || row.OrderNo != orderNo
  1069. || row.Remark != remark;
  1070. if (!needFix)
  1071. return;
  1072. row.Pid = pid;
  1073. row.Title = title;
  1074. row.Path = path;
  1075. row.Name = name;
  1076. row.Component = component;
  1077. row.Icon = icon;
  1078. row.Type = MenuTypeEnum.Menu;
  1079. row.OrderNo = orderNo;
  1080. row.Remark = remark;
  1081. db.Updateable(row)
  1082. .UpdateColumns(m => new { m.Pid, m.Title, m.Path, m.Name, m.Component, m.Icon, m.Type, m.OrderNo, m.Remark })
  1083. .ExecuteCommand();
  1084. }
  1085. private static void EnsureS3DirMenu(
  1086. ISqlSugarClient db,
  1087. DateTime ct,
  1088. long id,
  1089. long pid,
  1090. string title,
  1091. string path,
  1092. string name,
  1093. string redirect,
  1094. string remark)
  1095. {
  1096. var row = db.Queryable<SysMenu>().First(m => m.Id == id);
  1097. if (row == null)
  1098. {
  1099. db.Insertable(new SysMenu
  1100. {
  1101. Id = id,
  1102. Pid = pid,
  1103. Title = title,
  1104. Path = path,
  1105. Name = name,
  1106. Component = "Layout",
  1107. Icon = "ele-Folder",
  1108. Type = MenuTypeEnum.Dir,
  1109. Redirect = redirect,
  1110. CreateTime = ct,
  1111. OrderNo = 102,
  1112. Status = StatusEnum.Enable,
  1113. Remark = remark
  1114. }).ExecuteCommand();
  1115. return;
  1116. }
  1117. var needFix = row.Pid != pid
  1118. || row.Title != title
  1119. || row.Path != path
  1120. || row.Name != name
  1121. || row.Component != "Layout"
  1122. || row.Icon != "ele-Folder"
  1123. || row.Type != MenuTypeEnum.Dir
  1124. || row.Redirect != redirect
  1125. || row.OrderNo != 102
  1126. || row.Remark != remark;
  1127. if (!needFix)
  1128. return;
  1129. row.Pid = pid;
  1130. row.Title = title;
  1131. row.Path = path;
  1132. row.Name = name;
  1133. row.Component = "Layout";
  1134. row.Icon = "ele-Folder";
  1135. row.Type = MenuTypeEnum.Dir;
  1136. row.Redirect = redirect;
  1137. row.OrderNo = 102;
  1138. row.Remark = remark;
  1139. db.Updateable(row)
  1140. .UpdateColumns(m => new { m.Pid, m.Title, m.Path, m.Name, m.Component, m.Icon, m.Type, m.Redirect, m.OrderNo, m.Remark })
  1141. .ExecuteCommand();
  1142. }
  1143. }