IqcInspBillEditGate.cs 4.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. using Admin.NET.Plugin.ApprovalFlow;
  2. namespace Admin.NET.Plugin.AiDOP.MaterialWarehouse;
  3. /// <summary>
  4. /// S5 IQC 检验单「项目级结果是否可编辑」的唯一判据(Batch 3)。
  5. ///
  6. /// 单独成类而非散落在 Service 内,目的是让编辑授权只有一个真源:
  7. /// Detail 的 canEdit、save-detail 的门禁都必须走这里,不允许各处自行判 currentNode。
  8. ///
  9. /// 【判据】
  10. /// ① bill 必须存在且属当前租户(带租户谓词,见 tenant 边界说明)
  11. /// ② 取该 bill 最新流程实例(BizType + BizId)
  12. /// · 实例不存在(尚未起流) → 具备检验员角色即可编辑
  13. /// · 实例非 Running(终态/拒绝/撤销)→ 只读
  14. /// · Running 且当前用户有 N1_INSPECT 待办 → 可编辑
  15. /// · 其余(N2 / GW_PD / N3 / end;或在 N1 但待办属他人)→ 只读
  16. ///
  17. /// 【为什么必须有「实例不存在」分支】
  18. /// GenerateInspBill **不起流**,流程是 submit-result 惰性 StartFlow
  19. /// (IqcInspBillFlowService.cs:95-107)。新生成的检验单没有实例、也没有 N1 待办,
  20. /// 若只认「有实例且在 N1」,检验员将永远无法在提交前录入结果,本能力直接失效。
  21. /// 该分支与既有 IqcInspBillFlowService.BuildStateAsync 对 CanSubmitResult 的判法
  22. /// (实例为空时 = HasRole(RoleInspectorId))完全一致 —— 保证「能存」与「能提交」
  23. /// 权限口径同一,不会出现可存不可提交、或可提交不可存的矛盾。
  24. ///
  25. /// 【为什么不叠加 jyfzr == 当前用户】
  26. /// jyfzr 在报检分录上、由认领/调配写入;ApprovalFlow 的 N1 受让人由角色成员解析,
  27. /// 两者是不同人群。同时要求会制造与 S7 FQC 同类的审批人死结。
  28. /// N1 待办本身已是更强的授权凭据,故刻意不加该条件。
  29. ///
  30. /// 【租户边界的结构性限制(实测)】
  31. /// ApprovalFlowInstance / ApprovalFlowTask **没有 TenantId 列**(只有 OrgId / CreateUserId),
  32. /// 因此流程查询无法直接加租户谓词。本类的做法是:先用带 tenant_id 谓词的 bill 查询确认
  33. /// billId 属当前租户,再以该 billId 作 BizId 查流程 —— 租户约束经 bill 传递。
  34. /// 这是该框架表结构下可达的最强隔离。
  35. /// </summary>
  36. public class IqcInspBillEditGate : ITransient
  37. {
  38. private readonly ISqlSugarClient _db;
  39. private readonly UserManager _userManager;
  40. public IqcInspBillEditGate(ISqlSugarClient db, UserManager userManager)
  41. {
  42. _db = db;
  43. _userManager = userManager;
  44. }
  45. /// <summary>
  46. /// 该检验单当前是否允许由**当前用户**编辑项目级结果。
  47. /// billId 不属当前租户时返回 false(不区分“不存在”与“属他人租户”,避免存在性泄漏)。
  48. /// </summary>
  49. public async Task<bool> CanEditAsync(long billId, long tenantId)
  50. {
  51. if (billId <= 0 || tenantId <= 0) return false;
  52. // ① 租户归属(唯一的租户谓词落点;后续流程查询经 billId 传递该约束)
  53. var owns = await _db.Ado.GetIntAsync(
  54. "SELECT COUNT(1) FROM qms_qcp_inspbill WHERE id=@id AND tenant_id=@TenantId",
  55. new SugarParameter("@id", billId),
  56. new SugarParameter("@TenantId", tenantId));
  57. if (owns == 0) return false;
  58. // ② 最新流程实例
  59. var inst = await _db.Queryable<ApprovalFlowInstance>()
  60. .ClearFilter()
  61. .Where(x => x.BizType == IqcInspBillFlowConst.BizType && x.BizId == billId)
  62. .OrderByDescending(x => x.Id)
  63. .FirstAsync();
  64. // 尚未起流:检验员可先录入,提交时再惰性起流
  65. if (inst == null)
  66. return await HasInspectorRoleAsync();
  67. // 已结束(Approved / Rejected / Cancelled / Terminated)→ 只读
  68. if (inst.Status != FlowInstanceStatusEnum.Running) return false;
  69. // Running:必须当前用户在 N1_INSPECT 有待办
  70. var userId = _userManager.UserId;
  71. var myTask = await _db.Queryable<ApprovalFlowTask>()
  72. .ClearFilter()
  73. .Where(x => x.InstanceId == inst.Id
  74. && x.AssigneeId == userId
  75. && x.Status == FlowTaskStatusEnum.Pending)
  76. .FirstAsync();
  77. return myTask != null && myTask.NodeId == IqcInspBillFlowConst.NodeInspect;
  78. }
  79. /// <summary>当前用户是否具备 S5 IQC 检验员角色(跨数据范围,与 Flow 服务同口径)。</summary>
  80. private async Task<bool> HasInspectorRoleAsync()
  81. {
  82. var userId = _userManager.UserId;
  83. return await _db.Queryable<SysUserRole>()
  84. .ClearFilter()
  85. .AnyAsync(x => x.UserId == userId && x.RoleId == IqcInspBillFlowConst.RoleInspectorId);
  86. }
  87. }