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