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;
}
}