namespace Admin.NET.Plugin.ApprovalFlow.Service; /// /// 审批任务服务 /// [ApiDescriptionSettings(ApprovalFlowConst.GroupName, Order = 90)] public class FlowTaskService : IDynamicApiController, ITransient { private readonly FlowEngineService _engine; private readonly SqlSugarRepository _taskRep; private readonly SqlSugarRepository _instanceRep; private readonly UserManager _userManager; public FlowTaskService( FlowEngineService engine, SqlSugarRepository taskRep, SqlSugarRepository instanceRep, UserManager userManager) { _engine = engine; _taskRep = taskRep; _instanceRep = instanceRep; _userManager = userManager; } // ── 查询 ── /// /// 我的待办(分页) /// [HttpPost] [ApiDescriptionSettings(Name = "MyPendingPage")] [DisplayName("我的待办")] public async Task> MyPendingPage(TaskPageInput input) { var userId = _userManager.UserId; return await _taskRep.AsQueryable() .InnerJoin((t, i) => t.InstanceId == i.Id) .Where((t, i) => t.AssigneeId == userId && t.Status == FlowTaskStatusEnum.Pending) .WhereIF(!string.IsNullOrWhiteSpace(input.BizType), (t, i) => i.BizType == input.BizType) .OrderByDescending((t, i) => t.CreateTime) .Select((t, i) => new TaskPageOutput { Id = t.Id, InstanceId = t.InstanceId, NodeId = t.NodeId, NodeName = t.NodeName, Status = t.Status, Title = i.Title, BizType = i.BizType, BizId = i.BizId, BizNo = i.BizNo, InitiatorName = i.InitiatorName, StartTime = i.StartTime, CreateTime = t.CreateTime, IsDelegate = t.IsDelegate, DelegateForUserName = t.DelegateForUserName, }) .ToPagedListAsync(input.Page, input.PageSize); } /// /// 我的已办(分页) /// [HttpPost] [ApiDescriptionSettings(Name = "MyDonePage")] [DisplayName("我的已办")] public async Task> MyDonePage(TaskPageInput input) { var userId = _userManager.UserId; return await _taskRep.AsQueryable() .InnerJoin((t, i) => t.InstanceId == i.Id) .Where((t, i) => t.AssigneeId == userId && t.Status != FlowTaskStatusEnum.Pending) .WhereIF(!string.IsNullOrWhiteSpace(input.BizType), (t, i) => i.BizType == input.BizType) .OrderByDescending((t, i) => t.ActionTime) .Select((t, i) => new TaskPageOutput { Id = t.Id, InstanceId = t.InstanceId, NodeId = t.NodeId, NodeName = t.NodeName, Status = t.Status, Comment = t.Comment, ActionTime = t.ActionTime, Title = i.Title, BizType = i.BizType, BizId = i.BizId, BizNo = i.BizNo, InitiatorName = i.InitiatorName, StartTime = i.StartTime, CreateTime = t.CreateTime, IsDelegate = t.IsDelegate, DelegateForUserName = t.DelegateForUserName, }) .ToPagedListAsync(input.Page, input.PageSize); } /// /// 我发起的(分页) /// [HttpPost] [ApiDescriptionSettings(Name = "MyInitiatedPage")] [DisplayName("我发起的")] public async Task> MyInitiatedPage(InstancePageInput input) { var userId = _userManager.UserId; var page = await _instanceRep.AsQueryable() .Where(i => i.InitiatorId == userId) .WhereIF(!string.IsNullOrWhiteSpace(input.BizType), i => i.BizType == input.BizType) .WhereIF(input.Status.HasValue, i => i.Status == input.Status!.Value) .OrderByDescending(i => i.CreateTime) .Select(i => new InstancePageOutput { Id = i.Id, BizType = i.BizType, BizId = i.BizId, BizNo = i.BizNo, Title = i.Title, Status = i.Status, CurrentNodeId = i.CurrentNodeId, StartTime = i.StartTime, EndTime = i.EndTime, InitiatorName = i.InitiatorName, }) .ToPagedListAsync(input.Page, input.PageSize); var runningIds = page.Items.Where(x => x.Status == FlowInstanceStatusEnum.Running).Select(x => x.Id).ToList(); if (runningIds.Count > 0) { var pendingAssignees = await _taskRep.AsQueryable() .Where(t => runningIds.Contains(t.InstanceId) && t.Status == FlowTaskStatusEnum.Pending) .Select(t => new { t.InstanceId, t.AssigneeName }) .ToListAsync(); var grouped = pendingAssignees.GroupBy(x => x.InstanceId) .ToDictionary(g => g.Key, g => string.Join(", ", g.Select(x => x.AssigneeName).Distinct())); foreach (var item in page.Items) { if (grouped.TryGetValue(item.Id, out var names)) item.CurrentAssigneeName = names; } } return page; } /// /// 待办数量 /// [HttpGet] [ApiDescriptionSettings(Name = "MyPendingCount")] [DisplayName("待办数量")] public async Task MyPendingCount() { var userId = _userManager.UserId; return await _taskRep.AsQueryable() .Where(t => t.AssigneeId == userId && t.Status == FlowTaskStatusEnum.Pending) .CountAsync(); } // ── 操作 ── /// /// 同意 /// [HttpPost] [ApiDescriptionSettings(Name = "Approve")] [DisplayName("同意")] public async Task Approve(TaskActionInput input) { await _engine.Approve(input.TaskId, input.Comment); } /// /// 拒绝 /// [HttpPost] [ApiDescriptionSettings(Name = "Reject")] [DisplayName("拒绝")] public async Task Reject(TaskActionInput input) { await _engine.Reject(input.TaskId, input.Comment); } /// /// 转办 /// [HttpPost] [ApiDescriptionSettings(Name = "Transfer")] [DisplayName("转办")] public async Task Transfer(TaskTransferInput input) { await _engine.Transfer(input.TaskId, input.TargetUserId, input.Comment); } /// /// 撤回 /// [HttpPost] [ApiDescriptionSettings(Name = "Withdraw")] [DisplayName("撤回")] public async Task Withdraw(TaskWithdrawInput input) { await _engine.Withdraw(input.InstanceId); } /// /// 退回上一步 /// [HttpPost] [ApiDescriptionSettings(Name = "ReturnToPrev")] [DisplayName("退回上一步")] public async Task ReturnToPrev(TaskActionInput input) { await _engine.ReturnToPrev(input.TaskId, input.Comment); } /// /// 加签 /// [HttpPost] [ApiDescriptionSettings(Name = "AddSign")] [DisplayName("加签")] public async Task AddSign(TaskAddSignInput input) { await _engine.AddSign(input.TaskId, input.TargetUserId, input.Comment); } /// /// 升级 /// [HttpPost] [ApiDescriptionSettings(Name = "Escalate")] [DisplayName("升级")] public async Task Escalate(TaskActionInput input) { await _engine.Escalate(input.TaskId, input.Comment); } /// /// 获取指定任务所属节点的升级配置 /// [HttpGet] [ApiDescriptionSettings(Name = "GetEscalationConfig")] [DisplayName("获取升级配置")] public async Task GetEscalationConfig([FromQuery] long taskId) { var task = await _taskRep.GetByIdAsync(taskId); if (task == null) return new EscalationConfigOutput { Enabled = false }; var instance = await _instanceRep.GetByIdAsync(task.InstanceId); if (instance == null) return new EscalationConfigOutput { Enabled = false }; var flowData = System.Text.Json.JsonSerializer.Deserialize(instance.FlowJsonSnapshot ?? "{}"); var node = flowData?.Nodes?.FirstOrDefault(n => n.Id == task.NodeId); var props = node?.Properties; return new EscalationConfigOutput { Enabled = props?.EnableManualEscalation == true && !string.IsNullOrWhiteSpace(props?.EscalationApproverType) && !string.IsNullOrWhiteSpace(props?.EscalationApproverIds), EscalationApproverNames = props?.EscalationApproverNames, }; } /// /// 催办 /// [HttpPost] [ApiDescriptionSettings(Name = "Urge")] [DisplayName("催办")] public async Task Urge(TaskUrgeInput input) { await _engine.Urge(input.InstanceId); } /// /// 立即触发一次超时任务扫描(等价于 FlowTimeoutJob 的单次执行) /// 仅 SuperAdmin / SysAdmin 可调用,用于运维排障与 E2E 自动化验收。 /// [HttpPost] [ApiDescriptionSettings(Name = "TriggerTimeoutScan")] [DisplayName("立即扫描超时任务")] public async Task TriggerTimeoutScan() { if (!_userManager.SuperAdmin && !_userManager.SysAdmin) throw Oops.Oh("仅超级管理员/系统管理员可触发该操作"); return await _engine.ScanTimeoutTasks(); } /// /// 批量同意 /// [HttpPost] [ApiDescriptionSettings(Name = "BatchApprove")] [DisplayName("批量同意")] public async Task BatchApprove(BatchActionInput input) { var result = new BatchActionResult(); foreach (var taskId in input.TaskIds) { try { await _engine.Approve(taskId, input.Comment); result.SuccessCount++; } catch (Exception ex) { result.FailCount++; result.FailItems.Add(new BatchActionFailItem { TaskId = taskId, Error = ex.Message }); } } return result; } /// /// 批量拒绝 /// [HttpPost] [ApiDescriptionSettings(Name = "BatchReject")] [DisplayName("批量拒绝")] public async Task BatchReject(BatchActionInput input) { var result = new BatchActionResult(); foreach (var taskId in input.TaskIds) { try { await _engine.Reject(taskId, input.Comment); result.SuccessCount++; } catch (Exception ex) { result.FailCount++; result.FailItems.Add(new BatchActionFailItem { TaskId = taskId, Error = ex.Message }); } } return result; } }