namespace SimpleAdmin.Application; /// /// 员工手机端入口;仅凭一次性任务链接访问,绝不创建正式员工。 /// public class OnboardingPublicTaskService : IOnboardingPublicTaskService { private readonly SqlSugarScope _db; private readonly IOnboardingContractSnapshotService _snapshotService; public OnboardingPublicTaskService( IOnboardingContractSnapshotService snapshotService) { _db = DbContext.DB; _snapshotService = snapshotService; } /// /// 通过有效 Token 获取员工端任务、字段、材料和合同信息。 /// public async Task Detail(OnboardingTaskPublicAccessInput input) { var link = await GetActiveLink(input.Token); var task = await _db.Queryable().InSingleAsync(link.TaskId) ?? throw Oops.Bah("入职任务不存在"); link.AccessCount++; link.LastAccessedAt = DateTime.Now; await _db.Updateable(link).ExecuteCommandAsync(); var formRequirements = await GetEffectiveFormRequirements(task); var materialRequirements = await GetEffectiveMaterialRequirements(task); var fields = await _db.Queryable() .Where(x => formRequirements.Select(r => r.FieldId).Contains(x.Id) && x.Status == "enable") .ToListAsync(); var materialTypes = await _db.Queryable() .Where(x => materialRequirements.Select(r => r.MaterialTypeId).Contains(x.Id) && x.Status == "enable") .ToListAsync(); return new OnboardingTaskPublicDetailOutput { Task = task, EmployeeInfo = await _db.Queryable().FirstAsync(it => it.TaskId == task.Id), Contracts = await _db.Queryable() .Where(it => it.TaskId == task.Id) .ToListAsync(), DynamicValues = await _db.Queryable() .Where(x => x.TaskId == task.Id) .ToListAsync(), Materials = await _db.Queryable() .Where(x => x.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() }; } /// /// 保存候选人固定基础信息并刷新合同变量快照。 /// public async Task SaveEmployeeInfo(OnboardingTaskPublicSaveInput input) { var link = await GetActiveLink(input.Token); var task = await _db.Queryable().InSingleAsync(link.TaskId) ?? throw Oops.Bah("入职任务不存在"); if (task.TaskStatus != OnboardingTaskStatus.PendingFill && task.TaskStatus != OnboardingTaskStatus.Rejected) throw Oops.Bah("当前任务不可编辑"); var old = await _db.Queryable().FirstAsync(it => it.TaskId == task.Id); var entity = input.Adapt(); entity.TaskId = task.Id; if (old == null) { entity.Id = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(); await _db.Insertable(entity).ExecuteCommandAsync(); } else { entity.Id = old.Id; await _db.Updateable(entity).ExecuteCommandAsync(); } await _snapshotService.Build(task.Id, entity); } /// /// 校验任务归属后返回合同模板源文件 Id。 /// public async Task GetContractSourceFileId(OnboardingPublicContractSourceInput input) { var link = await GetActiveLink(input.Token); var contract = await _db.Queryable() .FirstAsync(x => x.Id == input.TaskContractId && x.TaskId == link.TaskId) ?? throw Oops.Bah("合同不存在"); var version = await _db.Queryable().InSingleAsync(contract.TemplateVersionId) ?? throw Oops.Bah("合同模板版本不存在"); return version.SourceDocxFileId; } private async Task> GetEffectiveFormRequirements(ObOnboardingTask task) { var requirements = await _db.Queryable() .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> GetEffectiveMaterialRequirements(ObOnboardingTask task) { var requirements = await _db.Queryable() .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(); } private async Task GetActiveLink(string token) { var hash = Convert.ToHexString( global::System.Security.Cryptography.SHA256.HashData( global::System.Text.Encoding.UTF8.GetBytes(token))); var link = await _db.Queryable().FirstAsync(it => it.TokenHash == hash) ?? throw Oops.Bah("链接不存在"); if (link.LinkStatus != OnboardingLinkStatus.Active || link.ExpiresAt <= DateTime.Now) throw Oops.Bah("链接已失效"); return link; } }