逻辑调整

This commit is contained in:
fuenmao
2026-08-25 10:20:53 +08:00
parent 4679bd6c13
commit 085bf5e2ab
36 changed files with 2770 additions and 1209 deletions
@@ -1,22 +1,25 @@
namespace SimpleAdmin.Application;
/// <summary>
/// 员工手机端入口;仅凭一次性任务链接访问,绝不创建正式员工。
/// 员工手机端公开入口,仅凭一次性任务链接访问入职资料。
/// </summary>
public class OnboardingPublicTaskService : IOnboardingPublicTaskService
{
private readonly SqlSugarScope _db;
private readonly IOnboardingContractSnapshotService _snapshotService;
private readonly IOnboardingContractDocumentService _contractDocumentService;
public OnboardingPublicTaskService(
IOnboardingContractSnapshotService snapshotService)
IOnboardingContractSnapshotService snapshotService,
IOnboardingContractDocumentService contractDocumentService)
{
_db = DbContext.DB;
_snapshotService = snapshotService;
_contractDocumentService = contractDocumentService;
}
/// <summary>
/// 通过有效 Token 获取员工端任务、字段、材料和合同信息。
/// 通过有效 Token 获取任务创建时冻结的字段、材料和合同信息。
/// </summary>
public async Task<OnboardingTaskPublicDetailOutput> Detail(OnboardingTaskPublicAccessInput input)
{
@@ -28,70 +31,65 @@ public class OnboardingPublicTaskService : IOnboardingPublicTaskService
link.LastAccessedAt = DateTime.Now;
await _db.Updateable(link).ExecuteCommandAsync();
var formRequirements = await GetEffectiveFormRequirements(task);
var materialRequirements = await GetEffectiveMaterialRequirements(task);
var fields = await _db.Queryable<ObFormField>()
.Where(x => formRequirements.Select(r => r.FieldId).Contains(x.Id) && x.Status == "enable")
var formRequirements = await _db.Queryable<ObTaskFormRequirement>()
.Where(item => item.TaskId == task.Id)
.OrderBy(item => item.SortCode)
.ToListAsync();
var materialTypes = await _db.Queryable<ObMaterialType>()
.Where(x => materialRequirements.Select(r => r.MaterialTypeId).Contains(x.Id) && x.Status == "enable")
var materialRequirements = await _db.Queryable<ObTaskMaterialRequirement>()
.Where(item => item.TaskId == task.Id)
.OrderBy(item => item.SortCode)
.ToListAsync();
return new OnboardingTaskPublicDetailOutput
{
Task = task,
EmployeeInfo = await _db.Queryable<ObTaskEmployeeInfo>().FirstAsync(it => it.TaskId == task.Id),
EmployeeInfo = await _db.Queryable<ObTaskEmployeeInfo>()
.FirstAsync(item => item.TaskId == task.Id),
Contracts = await _db.Queryable<ObTaskContract>()
.Where(it => it.TaskId == task.Id)
.Where(item => item.TaskId == task.Id)
.ToListAsync(),
DynamicValues = await _db.Queryable<ObTaskFormValue>()
.Where(x => x.TaskId == task.Id)
.Where(item => item.TaskId == task.Id)
.ToListAsync(),
Materials = await _db.Queryable<ObTaskMaterial>()
.Where(x => x.TaskId == task.Id)
.Where(item => item.TaskId == task.Id)
.ToListAsync(),
FormFields = formRequirements
.OrderBy(x => x.SortCode)
.Select(requirement => fields.FirstOrDefault(field => field.Id == requirement.FieldId))
.Where(field => field != null)
.Select(field => new OnboardingPublicFormFieldOutput
{
FieldId = field.Id,
FieldCode = field.FieldCode,
FieldName = field.FieldName,
DataType = field.DataType,
OptionsJson = field.OptionsJson,
Required = formRequirements.First(requirement => requirement.FieldId == field.Id).Required
})
.ToList(),
MaterialRequirements = materialRequirements
.OrderBy(x => x.SortCode)
.Select(requirement => materialTypes.FirstOrDefault(material => material.Id == requirement.MaterialTypeId))
.Where(material => material != null)
.Select(material => new OnboardingPublicMaterialOutput
{
MaterialTypeId = material.Id,
MaterialName = material.MaterialName,
AllowedExtensions = material.AllowedExtensions,
MaxFileCount = material.MaxFileCount,
Required = materialRequirements.First(requirement => requirement.MaterialTypeId == material.Id).Required
})
.ToList()
FormFields = formRequirements.Select(requirement => new OnboardingPublicFormFieldOutput
{
FieldId = requirement.FieldId,
FieldCode = requirement.FieldCode,
FieldName = requirement.FieldName,
DataType = requirement.DataType,
OptionsJson = requirement.OptionsJson,
Required = requirement.Required
}).ToList(),
MaterialRequirements = materialRequirements.Select(requirement => new OnboardingPublicMaterialOutput
{
MaterialTypeId = requirement.MaterialTypeId,
MaterialName = requirement.MaterialName,
AllowedExtensions = requirement.AllowedExtensions,
MaxFileCount = requirement.MaxFileCount,
Required = requirement.Required
}).ToList()
};
}
/// <summary>
/// 保存候选人固定基础信息并刷新合同变量快照。
/// 保存候选人的固定基础信息并刷新尚未确认的合同变量快照。
/// </summary>
public async Task SaveEmployeeInfo(OnboardingTaskPublicSaveInput input)
{
var link = await GetActiveLink(input.Token);
var task = await _db.Queryable<ObOnboardingTask>().InSingleAsync(link.TaskId)
?? throw Oops.Bah("入职任务不存在");
if (task.TaskStatus != OnboardingTaskStatus.PendingFill && task.TaskStatus != OnboardingTaskStatus.Rejected)
if (task.TaskStatus != OnboardingTaskStatus.PendingFill
&& task.TaskStatus != OnboardingTaskStatus.Rejected)
{
throw Oops.Bah("当前任务不可编辑");
}
var old = await _db.Queryable<ObTaskEmployeeInfo>().FirstAsync(it => it.TaskId == task.Id);
var old = await _db.Queryable<ObTaskEmployeeInfo>()
.FirstAsync(item => item.TaskId == task.Id);
var entity = input.Adapt<ObTaskEmployeeInfo>();
entity.TaskId = task.Id;
@@ -106,56 +104,41 @@ public class OnboardingPublicTaskService : IOnboardingPublicTaskService
await _db.Updateable(entity).ExecuteCommandAsync();
}
await _snapshotService.Build(task.Id, entity);
await _snapshotService.Build(task.Id, entity, invalidateGeneratedPreview: true);
}
/// <summary>
/// 校验任务归属后返回合同模板源文件 Id。
/// 根据员工当前已填写信息生成合同预览,并返回已替换变量的 DOCX 文件 Id。
/// </summary>
public async Task<long> GetContractSourceFileId(OnboardingPublicContractSourceInput input)
{
var link = await GetActiveLink(input.Token);
var contract = await _db.Queryable<ObTaskContract>()
.FirstAsync(x => x.Id == input.TaskContractId && x.TaskId == link.TaskId)
.FirstAsync(item => item.Id == input.TaskContractId && item.TaskId == link.TaskId)
?? throw Oops.Bah("合同不存在");
var version = await _db.Queryable<ObContractTemplateVersion>().InSingleAsync(contract.TemplateVersionId)
?? throw Oops.Bah("合同模板版本不存在");
return version.SourceDocxFileId;
}
await _snapshotService.Build(link.TaskId);
var fileId = await _contractDocumentService.Generate(new OnboardingGenerateContractInput
{
TaskContractId = contract.Id
});
private async Task<List<ObFormRequirement>> GetEffectiveFormRequirements(ObOnboardingTask task)
{
var requirements = await _db.Queryable<ObFormRequirement>()
.Where(x => x.CompanyId == task.CompanyId && (x.PositionId == null || x.PositionId == task.PositionId))
.ToListAsync();
// 同一字段同时存在公司级和岗位级配置时,岗位配置覆盖公司默认配置。
return requirements
.GroupBy(x => x.FieldId)
.Select(group => group.FirstOrDefault(x => x.PositionId == task.PositionId) ?? group.First())
.ToList();
}
private async Task<List<ObMaterialRequirement>> GetEffectiveMaterialRequirements(ObOnboardingTask task)
{
var requirements = await _db.Queryable<ObMaterialRequirement>()
.Where(x => x.CompanyId == task.CompanyId && (x.PositionId == null || x.PositionId == task.PositionId))
.ToListAsync();
// 同一材料同时存在公司级和岗位级配置时,岗位配置覆盖公司默认配置。
return requirements
.GroupBy(x => x.MaterialTypeId)
.Select(group => group.FirstOrDefault(x => x.PositionId == task.PositionId) ?? group.First())
.ToList();
contract.ReadAt ??= DateTime.Now;
if (contract.ContractStatus == TaskContractStatus.PendingGenerate)
contract.ContractStatus = TaskContractStatus.PendingConfirm;
await _db.Updateable(contract).ExecuteCommandAsync();
return fileId;
}
private async Task<ObTaskLink> GetActiveLink(string token)
{
if (string.IsNullOrWhiteSpace(token))
throw Oops.Bah("入职链接 Token 不能为空");
var hash = Convert.ToHexString(
global::System.Security.Cryptography.SHA256.HashData(
global::System.Text.Encoding.UTF8.GetBytes(token)));
var link = await _db.Queryable<ObTaskLink>().FirstAsync(it => it.TokenHash == hash)
var link = await _db.Queryable<ObTaskLink>().FirstAsync(item => item.TokenHash == hash)
?? throw Oops.Bah("链接不存在");
if (link.LinkStatus != OnboardingLinkStatus.Active || link.ExpiresAt <= DateTime.Now)
@@ -163,4 +146,4 @@ public class OnboardingPublicTaskService : IOnboardingPublicTaskService
return link;
}
}
}