Files
OnboardXAdmin/SimpleAdmin.Application/Services/Onboarding/TaskQuery/OnboardingTaskQueryService.cs
T
2026-08-26 15:30:31 +08:00

233 lines
9.2 KiB
C#

namespace SimpleAdmin.Application;
/// <summary>
/// 实现入职任务查询业务能力。
/// </summary>
public class OnboardingTaskQueryService : IOnboardingTaskQueryService
{
private readonly SqlSugarScope _db;
private readonly IConfigService _configService;
public OnboardingTaskQueryService(IConfigService configService)
{
_db = DbContext.DB;
_configService = configService;
}
/// <summary>
/// 分页查询后台入职任务。
/// </summary>
public async Task<SqlSugarPagedList<ObOnboardingTask>> Page(OnboardingTaskPageInput input)
{
var list = await _db.Queryable<ObOnboardingTask>()
.WhereIF(!string.IsNullOrWhiteSpace(input.TaskNo), item => item.TaskNo.Contains(input.TaskNo))
.WhereIF(!string.IsNullOrWhiteSpace(input.TaskStatus), item => item.TaskStatus == input.TaskStatus)
.WhereIF(input.CompanyId.HasValue, item => item.CompanyId == input.CompanyId.Value)
.OrderByDescending(item => item.CreateTime)
.ToPagedListAsync(input.PageNum, input.PageSize);
if (list.List != null && list.List.Any())
{
await FillTaskMetadata(list.List);
}
return list;
}
/// <summary>
/// 查询任务基础信息、人员资料、合同、材料、表单值、链接和审核记录。
/// </summary>
public async Task<OnboardingTaskDetailOutput> Detail(BaseIdInput input)
{
var task = await _db.Queryable<ObOnboardingTask>().InSingleAsync(input.Id)
?? throw Oops.Bah("入职任务不存在");
await FillTaskMetadata(new List<ObOnboardingTask> { task });
var employeeInfo = await _db.Queryable<ObTaskEmployeeInfo>()
.FirstAsync(item => item.TaskId == task.Id);
var contracts = await _db.Queryable<ObTaskContract>()
.Where(item => item.TaskId == task.Id)
.ToListAsync();
var materials = await _db.Queryable<ObTaskMaterial>()
.Where(item => item.TaskId == task.Id)
.ToListAsync();
var materialFileIds = materials
.Select(item => item.FileId)
.Distinct()
.ToList();
var materialFiles = await _db.Queryable<SysFile>()
.Where(item => materialFileIds.Contains(item.Id))
.ToListAsync();
var materialFileMap = materialFiles.ToDictionary(item => item.Id);
var materialOutputs = materials.Select(material =>
{
var output = material.Adapt<OnboardingTaskMaterialDetailOutput>();
if (materialFileMap.TryGetValue(material.FileId, out var file))
{
output.FileName = file.Name;
output.FileSuffix = file.Suffix;
}
return output;
}).ToList();
var link = await _db.Queryable<ObTaskLink>()
.Where(item => item.TaskId == task.Id && item.LinkStatus == "active")
.OrderByDescending(item => item.CreateTime)
.FirstAsync();
var audits = await _db.Queryable<ObTaskAudit>()
.Where(item => item.TaskId == task.Id)
.OrderByDescending(item => item.AuditedAt)
.ToListAsync();
return new OnboardingTaskDetailOutput
{
Task = task,
EmployeeInfo = employeeInfo,
Contracts = contracts,
Materials = materialOutputs,
Link = link,
Audits = audits
};
}
/// <summary>
/// 获取或重新生成任务有效填写链接。
/// </summary>
public async Task<OnboardingTaskCreatedOutput> GetTaskLink(BaseIdInput input)
{
var task = await _db.Queryable<ObOnboardingTask>().InSingleAsync(input.Id)
?? throw Oops.Bah("入职任务不存在");
var canShareLink = task.TaskStatus == OnboardingTaskStatus.PendingFill
|| task.TaskStatus == OnboardingTaskStatus.Rejected;
if (!canShareLink)
{
throw Oops.Bah("仅待填写或已退回的入职任务可以获取填写链接");
}
var activeLink = await _db.Queryable<ObTaskLink>()
.Where(item => item.TaskId == task.Id
&& item.LinkStatus == OnboardingLinkStatus.Active
&& item.ExpiresAt > DateTime.Now
&& !string.IsNullOrWhiteSpace(item.PlainToken))
.OrderByDescending(item => item.CreateTime)
.FirstAsync();
if (activeLink != null)
{
return new OnboardingTaskCreatedOutput
{
TaskId = task.Id,
TaskNo = task.TaskNo,
LinkToken = activeLink.PlainToken,
ExpiresAt = activeLink.ExpiresAt
};
}
var expiryHours = await GetLinkExpiryHours();
var currentTime = DateTime.Now;
var token = Guid.NewGuid().ToString("N");
var expiresAt = currentTime.AddHours(expiryHours);
var newLink = new ObTaskLink
{
Id = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(),
TaskId = task.Id,
PlainToken = token,
TokenHash = Convert.ToHexString(global::System.Security.Cryptography.SHA256.HashData(
global::System.Text.Encoding.UTF8.GetBytes(token))),
ExpiresAt = expiresAt,
LinkStatus = OnboardingLinkStatus.Active
};
await _db.Ado.BeginTranAsync();
try
{
// 历史链接虽然已超过有效期,但状态可能仍为 active,续期前统一修正为已过期。
await _db.Updateable<ObTaskLink>()
.SetColumns(item => new ObTaskLink
{
LinkStatus = OnboardingLinkStatus.Expired
})
.Where(item => item.TaskId == task.Id
&& item.LinkStatus == OnboardingLinkStatus.Active
&& item.ExpiresAt <= currentTime)
.ExecuteCommandAsync();
// 旧数据可能只有 TokenHash、没有可返回的 PlainToken,需要撤销后重新签发。
await _db.Updateable<ObTaskLink>()
.SetColumns(item => new ObTaskLink
{
LinkStatus = OnboardingLinkStatus.Revoked,
RevokedAt = currentTime
})
.Where(item => item.TaskId == task.Id
&& item.LinkStatus == OnboardingLinkStatus.Active)
.ExecuteCommandAsync();
await _db.Insertable(newLink).ExecuteCommandAsync();
await _db.Updateable<ObOnboardingTask>()
.SetColumns(item => item.LinkExpireAt == expiresAt)
.Where(item => item.Id == task.Id)
.ExecuteCommandAsync();
await _db.Ado.CommitTranAsync();
}
catch
{
await _db.Ado.RollbackTranAsync();
throw;
}
return new OnboardingTaskCreatedOutput
{
TaskId = task.Id,
TaskNo = task.TaskNo,
LinkToken = token,
ExpiresAt = expiresAt
};
}
private async Task FillTaskMetadata(IEnumerable<ObOnboardingTask> tasks)
{
var taskList = tasks.ToList();
var companyIds = taskList.Select(t => t.CompanyId).Distinct().ToList();
var positionIds = taskList.Select(t => t.PositionId).Distinct().ToList();
var templateIds = taskList.Select(t => t.OnboardingTemplateId).Distinct().ToList();
var companies = await _db.Queryable<ObCompany>().Where(c => companyIds.Contains(c.Id)).ToListAsync();
var positions = await _db.Queryable<ObPosition>().Where(p => positionIds.Contains(p.Id)).ToListAsync();
var templates = await _db.Queryable<ObOnboardingTemplate>().Where(t => templateIds.Contains(t.Id)).ToListAsync();
var companyMap = companies.ToDictionary(c => c.Id, c => c.CompanyName);
var positionMap = positions.ToDictionary(p => p.Id, p => p.PositionName);
var templateMap = templates.ToDictionary(t => t.Id, t => t.TemplateName);
foreach (var task in taskList)
{
if (companyMap.TryGetValue(task.CompanyId, out var cName)) task.CompanyName = cName;
if (positionMap.TryGetValue(task.PositionId, out var pName)) task.PositionName = pName;
if (templateMap.TryGetValue(task.OnboardingTemplateId, out var tName)) task.OnboardingTemplateName = tName;
}
}
/// <summary>
/// 从系统配置读取员工填写链接有效期,创建任务与续期保持同一规则。
/// </summary>
private async Task<int> GetLinkExpiryHours()
{
var expiryConfig = await _configService.GetByConfigKey(
CateGoryConst.CONFIG_BIZ_DEFINE,
"onboard.link.expireHours");
var expiryHours = 0;
var configInvalid = expiryConfig == null
|| !int.TryParse(expiryConfig.ConfigValue, out expiryHours)
|| expiryHours <= 0;
if (configInvalid)
{
throw Oops.Bah("请在系统配置中设置 onboard.link.expireHours(正整数小时)");
}
return expiryHours;
}
}