|
|
@@ -1,7 +1,9 @@
|
|
|
using Admin.NET.Plugin.AiDOP.Entity.S0.Warehouse;
|
|
|
using Admin.NET.Plugin.AiDOP.Entity.S8;
|
|
|
using Admin.NET.Plugin.AiDOP.Infrastructure.S8;
|
|
|
+using Admin.NET.Plugin.ApprovalFlow;
|
|
|
using Admin.NET.Plugin.ApprovalFlow.Service;
|
|
|
+using Microsoft.Extensions.Logging;
|
|
|
|
|
|
namespace Admin.NET.Plugin.AiDOP.Service.S8;
|
|
|
|
|
|
@@ -10,21 +12,30 @@ public class S8TaskFlowService : ITransient
|
|
|
private readonly SqlSugarRepository<AdoS8Exception> _rep;
|
|
|
private readonly SqlSugarRepository<AdoS8ExceptionTimeline> _timelineRep;
|
|
|
private readonly SqlSugarRepository<AdoS0EmployeeMaster> _employeeRep;
|
|
|
+ private readonly SqlSugarRepository<ApprovalFlowInstance> _flowInstanceRep;
|
|
|
+ private readonly SqlSugarRepository<ApprovalFlowTask> _flowTaskRep;
|
|
|
private readonly FlowEngineService _flowEngine;
|
|
|
private readonly UserManager _userManager;
|
|
|
+ private readonly ILogger<S8TaskFlowService> _logger;
|
|
|
|
|
|
public S8TaskFlowService(
|
|
|
SqlSugarRepository<AdoS8Exception> rep,
|
|
|
SqlSugarRepository<AdoS8ExceptionTimeline> timelineRep,
|
|
|
SqlSugarRepository<AdoS0EmployeeMaster> employeeRep,
|
|
|
+ SqlSugarRepository<ApprovalFlowInstance> flowInstanceRep,
|
|
|
+ SqlSugarRepository<ApprovalFlowTask> flowTaskRep,
|
|
|
FlowEngineService flowEngine,
|
|
|
- UserManager userManager)
|
|
|
+ UserManager userManager,
|
|
|
+ ILogger<S8TaskFlowService> logger)
|
|
|
{
|
|
|
_rep = rep;
|
|
|
_timelineRep = timelineRep;
|
|
|
_employeeRep = employeeRep;
|
|
|
+ _flowInstanceRep = flowInstanceRep;
|
|
|
+ _flowTaskRep = flowTaskRep;
|
|
|
_flowEngine = flowEngine;
|
|
|
_userManager = userManager;
|
|
|
+ _logger = logger;
|
|
|
}
|
|
|
|
|
|
public async Task<AdoS8Exception> ClaimAsync(long id, long tenantId, long factoryId, long assigneeId, string? remark)
|
|
|
@@ -46,6 +57,7 @@ public class S8TaskFlowService : ITransient
|
|
|
await InsertTimelineAsync(e.Id, "CLAIM", "认领", fromStatus, "ASSIGNED", assigneeId, null, remark);
|
|
|
}, ex => throw ex);
|
|
|
|
|
|
+ // 注:认领仅承接异常,不视为审批流完成。审批流的"通过"在"开始处理"那一步触发。
|
|
|
return e;
|
|
|
}
|
|
|
|
|
|
@@ -72,9 +84,54 @@ public class S8TaskFlowService : ITransient
|
|
|
await InsertTimelineAsync(e.Id, "TRANSFER", "转派", e.Status, e.Status, newAssigneeId, null, remark);
|
|
|
}, ex => throw ex);
|
|
|
|
|
|
+ // 双线合一:S8 转派 = TB001 任务转办给新处理人。
|
|
|
+ await TryTransferIntakeOnTransferAsync(e.Id, newAssigneeId, remark);
|
|
|
+
|
|
|
return e;
|
|
|
}
|
|
|
|
|
|
+ private async Task TryTransferIntakeOnTransferAsync(long exceptionId, long newAssigneeRecId, string? remark)
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ var instance = await _flowInstanceRep.AsQueryable()
|
|
|
+ .Where(x => x.BizType == "EXCEPTION_REPORT"
|
|
|
+ && x.BizId == exceptionId
|
|
|
+ && x.Status == FlowInstanceStatusEnum.Running)
|
|
|
+ .FirstAsync();
|
|
|
+ if (instance == null) return;
|
|
|
+
|
|
|
+ var currentUserId = _userManager.UserId;
|
|
|
+ var task = await _flowTaskRep.AsQueryable()
|
|
|
+ .Where(x => x.InstanceId == instance.Id
|
|
|
+ && x.AssigneeId == currentUserId
|
|
|
+ && x.Status == FlowTaskStatusEnum.Pending)
|
|
|
+ .FirstAsync();
|
|
|
+ if (task == null) return;
|
|
|
+
|
|
|
+ // newAssigneeRecId 是 EmployeeMaster.RecID;FlowEngine.Transfer 要 SysUser.UserId。
|
|
|
+ var targetSysUserId = await _employeeRep.AsQueryable().ClearFilter()
|
|
|
+ .Where(x => x.Id == newAssigneeRecId && x.SysUserId != null)
|
|
|
+ .Select(x => x.SysUserId)
|
|
|
+ .FirstAsync();
|
|
|
+ if (targetSysUserId == null || targetSysUserId == 0)
|
|
|
+ {
|
|
|
+ _logger.LogWarning(
|
|
|
+ "S8 转派联动审批流跳过:员工 {RecId} 未绑定 SysUser,TB001 任务保留原 assignee",
|
|
|
+ newAssigneeRecId);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ await _flowEngine.Transfer(task.Id, targetSysUserId.Value, remark ?? "S8 转派(双线合一自动转办)");
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ _logger.LogWarning(ex,
|
|
|
+ "S8 转派时自动转办 TB001 任务失败 exceptionId={Id} newAssigneeRecId={AssigneeId}",
|
|
|
+ exceptionId, newAssigneeRecId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
public async Task<AdoS8Exception> StartProgressAsync(long id, long tenantId, long factoryId, string? remark)
|
|
|
{
|
|
|
var currentUserId = GetCurrentUserId();
|
|
|
@@ -92,9 +149,41 @@ public class S8TaskFlowService : ITransient
|
|
|
await InsertTimelineAsync(e.Id, "START_PROGRESS", "开始处理", fromStatus, "IN_PROGRESS", currentUserId, null, remark);
|
|
|
}, ex => throw ex);
|
|
|
|
|
|
+ // 双线合一:开始处理 = TB001 异常提报审批通过。
|
|
|
+ // 当前用户必须是 TB001 task 的 AssigneeId,FlowEngine 强校验。
|
|
|
+ await TryApproveIntakeOnStartProgressAsync(e.Id, currentUserId);
|
|
|
+
|
|
|
return e;
|
|
|
}
|
|
|
|
|
|
+ private async Task TryApproveIntakeOnStartProgressAsync(long exceptionId, long currentUserId)
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ var instance = await _flowInstanceRep.AsQueryable()
|
|
|
+ .Where(x => x.BizType == "EXCEPTION_REPORT"
|
|
|
+ && x.BizId == exceptionId
|
|
|
+ && x.Status == FlowInstanceStatusEnum.Running)
|
|
|
+ .FirstAsync();
|
|
|
+ if (instance == null) return;
|
|
|
+
|
|
|
+ var task = await _flowTaskRep.AsQueryable()
|
|
|
+ .Where(x => x.InstanceId == instance.Id
|
|
|
+ && x.AssigneeId == currentUserId
|
|
|
+ && x.Status == FlowTaskStatusEnum.Pending)
|
|
|
+ .FirstAsync();
|
|
|
+ if (task == null) return;
|
|
|
+
|
|
|
+ await _flowEngine.Approve(task.Id, "S8 已开始处理(双线合一自动同意)");
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ _logger.LogWarning(ex,
|
|
|
+ "S8 开始处理时自动同意 TB001 任务失败 exceptionId={Id} userId={UserId}",
|
|
|
+ exceptionId, currentUserId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
public async Task<AdoS8Exception> UpgradeAsync(long id, long tenantId, long factoryId, string? remark)
|
|
|
{
|
|
|
var e = await LoadAsync(id, tenantId, factoryId) ?? throw new S8BizException("异常不存在");
|
|
|
@@ -139,33 +228,39 @@ public class S8TaskFlowService : ITransient
|
|
|
await InsertTimelineAsync(e.Id, "REJECT", "驳回", from, "REJECTED", null, null, remark);
|
|
|
}, ex => throw ex);
|
|
|
|
|
|
+ // 双线合一:S8 驳回 = TB001 流程整体拒绝(取消所有 pending 任务、Instance 终止)。
|
|
|
+ await TryRejectIntakeOnRejectAsync(e.Id, remark);
|
|
|
+
|
|
|
return e;
|
|
|
}
|
|
|
|
|
|
- public async Task<AdoS8Exception> CloseAsync(long id, long tenantId, long factoryId, string? remark)
|
|
|
+ private async Task TryRejectIntakeOnRejectAsync(long exceptionId, string? remark)
|
|
|
{
|
|
|
- var e = await LoadAsync(id, tenantId, factoryId) ?? throw new S8BizException("异常不存在");
|
|
|
-
|
|
|
- if (e.ActiveFlowInstanceId.HasValue)
|
|
|
- throw new S8BizException("该异常已有进行中的审批流程,请等待审批完成");
|
|
|
-
|
|
|
- if (!S8StatusRules.IsAllowedTransition(e.Status, "CLOSED"))
|
|
|
- throw new S8BizException($"状态 {e.Status} 不可关闭");
|
|
|
-
|
|
|
- await _flowEngine.StartFlow(new StartFlowInput
|
|
|
+ try
|
|
|
{
|
|
|
- BizType = "EXCEPTION_CLOSURE",
|
|
|
- BizId = e.Id,
|
|
|
- Title = $"异常关闭确认 - {e.ExceptionCode}",
|
|
|
- Comment = remark,
|
|
|
- BizData = new Dictionary<string, object>
|
|
|
- {
|
|
|
- ["sceneCode"] = e.SceneCode,
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- // 状态和时间线由 ExceptionClosureBizHandler.OnFlowStarted 回调更新
|
|
|
- return await LoadAsync(id, tenantId, factoryId) ?? e;
|
|
|
+ var instance = await _flowInstanceRep.AsQueryable()
|
|
|
+ .Where(x => x.BizType == "EXCEPTION_REPORT"
|
|
|
+ && x.BizId == exceptionId
|
|
|
+ && x.Status == FlowInstanceStatusEnum.Running)
|
|
|
+ .FirstAsync();
|
|
|
+ if (instance == null) return;
|
|
|
+
|
|
|
+ var currentUserId = _userManager.UserId;
|
|
|
+ var task = await _flowTaskRep.AsQueryable()
|
|
|
+ .Where(x => x.InstanceId == instance.Id
|
|
|
+ && x.AssigneeId == currentUserId
|
|
|
+ && x.Status == FlowTaskStatusEnum.Pending)
|
|
|
+ .FirstAsync();
|
|
|
+ if (task == null) return;
|
|
|
+
|
|
|
+ await _flowEngine.Reject(task.Id, remark ?? "S8 已驳回(双线合一自动拒绝)");
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ _logger.LogWarning(ex,
|
|
|
+ "S8 驳回时自动拒绝 TB001 流程失败 exceptionId={Id}",
|
|
|
+ exceptionId);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
public async Task<AdoS8Exception> SubmitVerificationAsync(
|
|
|
@@ -194,7 +289,37 @@ public class S8TaskFlowService : ITransient
|
|
|
currentUserId, null, remark);
|
|
|
}, ex => throw ex);
|
|
|
|
|
|
- return e;
|
|
|
+ // 双线合一:提交复检 = 启动 EXCEPTION_CLOSURE 流程,指派检验人。
|
|
|
+ // 该流程定义复用,作为复检/关闭确认通用审批载体;handler 只做 ActiveFlowInstanceId 维护。
|
|
|
+ await TryStartVerificationFlowAsync(e, verifierId, remark);
|
|
|
+
|
|
|
+ return await LoadAsync(id, tenantId, factoryId) ?? e;
|
|
|
+ }
|
|
|
+
|
|
|
+ private async Task TryStartVerificationFlowAsync(AdoS8Exception e, long verifierRecId, string? remark)
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ await _flowEngine.StartFlow(new StartFlowInput
|
|
|
+ {
|
|
|
+ BizType = "EXCEPTION_CLOSURE",
|
|
|
+ BizId = e.Id,
|
|
|
+ BizNo = e.ExceptionCode,
|
|
|
+ Title = $"异常复检 - {e.ExceptionCode}",
|
|
|
+ Comment = remark,
|
|
|
+ BizData = new Dictionary<string, object>
|
|
|
+ {
|
|
|
+ ["sceneCode"] = e.SceneCode ?? string.Empty,
|
|
|
+ ["verifierRecId"] = verifierRecId,
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ _logger.LogWarning(ex,
|
|
|
+ "S8 提交复检时启动 EXCEPTION_CLOSURE 流程失败 exceptionId={Id} verifierRecId={VerifierId}",
|
|
|
+ e.Id, verifierRecId);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
public async Task<AdoS8Exception> ApproveVerificationAsync(
|
|
|
@@ -205,26 +330,58 @@ public class S8TaskFlowService : ITransient
|
|
|
var e = await LoadAsync(id, tenantId, factoryId) ?? throw new S8BizException("异常不存在");
|
|
|
await EnsureCurrentUserIsOperatorAsync(e.VerifierId, currentUserId,
|
|
|
"只有指定检验人才能检验通过(或当前账号未绑定员工主数据)");
|
|
|
- if (!S8StatusRules.IsAllowedTransition(e.Status, "RESOLVED"))
|
|
|
+ if (!S8StatusRules.IsAllowedTransition(e.Status, "CLOSED"))
|
|
|
throw new S8BizException($"状态 {e.Status} 不可检验通过");
|
|
|
|
|
|
var from = e.Status;
|
|
|
- e.Status = "RESOLVED";
|
|
|
+ e.Status = "CLOSED";
|
|
|
e.VerifiedAt = DateTime.Now;
|
|
|
e.VerificationResult = "APPROVED";
|
|
|
e.VerificationRemark = remark;
|
|
|
+ e.ClosedAt = DateTime.Now;
|
|
|
e.UpdatedAt = DateTime.Now;
|
|
|
|
|
|
await _rep.AsTenant().UseTranAsync(async () =>
|
|
|
{
|
|
|
await _rep.UpdateAsync(e);
|
|
|
- await InsertTimelineAsync(e.Id, "VERIFY_APPROVED", "检验通过", from, "RESOLVED",
|
|
|
+ await InsertTimelineAsync(e.Id, "VERIFY_APPROVED", "检验通过", from, "CLOSED",
|
|
|
currentUserId, null, remark);
|
|
|
}, ex => throw ex);
|
|
|
|
|
|
+ // 双线合一:检验通过 = EXCEPTION_CLOSURE 复检流程审批通过。
|
|
|
+ await TryApproveVerificationFlowAsync(e.Id, currentUserId);
|
|
|
+
|
|
|
return e;
|
|
|
}
|
|
|
|
|
|
+ private async Task TryApproveVerificationFlowAsync(long exceptionId, long currentUserId)
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ var instance = await _flowInstanceRep.AsQueryable()
|
|
|
+ .Where(x => x.BizType == "EXCEPTION_CLOSURE"
|
|
|
+ && x.BizId == exceptionId
|
|
|
+ && x.Status == FlowInstanceStatusEnum.Running)
|
|
|
+ .FirstAsync();
|
|
|
+ if (instance == null) return;
|
|
|
+
|
|
|
+ var task = await _flowTaskRep.AsQueryable()
|
|
|
+ .Where(x => x.InstanceId == instance.Id
|
|
|
+ && x.AssigneeId == currentUserId
|
|
|
+ && x.Status == FlowTaskStatusEnum.Pending)
|
|
|
+ .FirstAsync();
|
|
|
+ if (task == null) return;
|
|
|
+
|
|
|
+ await _flowEngine.Approve(task.Id, "S8 检验通过(双线合一自动同意)");
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ _logger.LogWarning(ex,
|
|
|
+ "S8 检验通过时自动同意 EXCEPTION_CLOSURE 流程失败 exceptionId={Id} userId={UserId}",
|
|
|
+ exceptionId, currentUserId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
public async Task<AdoS8Exception> RejectVerificationAsync(
|
|
|
long id, long tenantId, long factoryId,
|
|
|
string remark)
|
|
|
@@ -252,9 +409,40 @@ public class S8TaskFlowService : ITransient
|
|
|
currentUserId, null, remark);
|
|
|
}, ex => throw ex);
|
|
|
|
|
|
+ // 双线合一:检验退回 = EXCEPTION_CLOSURE 复检流程整体拒绝。
|
|
|
+ await TryRejectVerificationFlowAsync(e.Id, currentUserId, remark);
|
|
|
+
|
|
|
return e;
|
|
|
}
|
|
|
|
|
|
+ private async Task TryRejectVerificationFlowAsync(long exceptionId, long currentUserId, string? remark)
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ var instance = await _flowInstanceRep.AsQueryable()
|
|
|
+ .Where(x => x.BizType == "EXCEPTION_CLOSURE"
|
|
|
+ && x.BizId == exceptionId
|
|
|
+ && x.Status == FlowInstanceStatusEnum.Running)
|
|
|
+ .FirstAsync();
|
|
|
+ if (instance == null) return;
|
|
|
+
|
|
|
+ var task = await _flowTaskRep.AsQueryable()
|
|
|
+ .Where(x => x.InstanceId == instance.Id
|
|
|
+ && x.AssigneeId == currentUserId
|
|
|
+ && x.Status == FlowTaskStatusEnum.Pending)
|
|
|
+ .FirstAsync();
|
|
|
+ if (task == null) return;
|
|
|
+
|
|
|
+ await _flowEngine.Reject(task.Id, remark ?? "S8 检验退回(双线合一自动拒绝)");
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ _logger.LogWarning(ex,
|
|
|
+ "S8 检验退回时自动拒绝 EXCEPTION_CLOSURE 流程失败 exceptionId={Id} userId={UserId}",
|
|
|
+ exceptionId, currentUserId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
public async Task CommentAsync(long id, string? remark)
|
|
|
{
|
|
|
var e = await _rep.GetFirstAsync(x => x.Id == id && !x.IsDeleted)
|