namespace SimpleAdmin.Application; /// /// 实现基于入职模板创建任务及冻结配置快照的业务能力。 /// public class OnboardingTaskService : IOnboardingTaskService { private readonly SqlSugarScope _db; private readonly IConfigService _configService; private readonly IOnboardingContractSnapshotService _snapshotService; public OnboardingTaskService( IConfigService configService, IOnboardingContractSnapshotService snapshotService) { _db = DbContext.DB; _configService = configService; _snapshotService = snapshotService; } /// /// 校验公司、岗位和入职模板后创建任务,并冻结合同、补充字段及材料要求。 /// public async Task Create(OnboardingTaskCreateInput input) { var companyEnabled = await _db.Queryable() .AnyAsync(item => item.Id == input.CompanyId && item.Status == "enable"); if (!companyEnabled) throw Oops.Bah("公司不存在或未启用"); var positionEnabled = await _db.Queryable() .AnyAsync(item => item.Id == input.PositionId && item.CompanyId == input.CompanyId && item.Status == "enable"); if (!positionEnabled) throw Oops.Bah("岗位不存在、未启用或不属于该公司"); var onboardingTemplate = await _db.Queryable() .FirstAsync(item => item.Id == input.OnboardingTemplateId && item.CompanyId == input.CompanyId && item.Status == "enable") ?? throw Oops.Bah("入职模板不存在、未启用或不属于该公司"); var templateSupportsPosition = await _db.Queryable() .AnyAsync(item => item.OnboardingTemplateId == onboardingTemplate.Id && item.PositionId == input.PositionId); if (!templateSupportsPosition) throw Oops.Bah("所选入职模板不适用于该岗位"); var contractConfigs = await _db.Queryable() .Where(item => item.OnboardingTemplateId == onboardingTemplate.Id) .OrderBy(item => item.SortCode) .ToListAsync(); if (contractConfigs.Count == 0) throw Oops.Bah("入职模板尚未配置合同文件"); var fieldConfigs = await _db.Queryable() .Where(item => item.OnboardingTemplateId == onboardingTemplate.Id) .OrderBy(item => item.SortCode) .ToListAsync(); var materialConfigs = await _db.Queryable() .Where(item => item.OnboardingTemplateId == onboardingTemplate.Id) .OrderBy(item => item.SortCode) .ToListAsync(); var templateIds = contractConfigs.Select(item => item.ContractTemplateId).Distinct().ToList(); var contractTemplates = await _db.Queryable() .Where(item => templateIds.Contains(item.Id)) .ToListAsync(); var versionIds = contractConfigs.Select(item => item.ContractTemplateVersionId).Distinct().ToList(); var enabledVersionCount = await _db.Queryable() .CountAsync(item => versionIds.Contains(item.Id) && item.VersionStatus == ContractTemplateVersionStatus.Enabled); if (enabledVersionCount != versionIds.Count) throw Oops.Bah("入职模板包含未启用的合同版本,请先调整模板配置"); var fieldIds = fieldConfigs.Select(item => item.FieldId).Distinct().ToList(); var fields = fieldIds.Count == 0 ? new List() : await _db.Queryable().Where(item => fieldIds.Contains(item.Id)).ToListAsync(); var materialIds = materialConfigs.Select(item => item.MaterialTypeId).Distinct().ToList(); var materialTypes = materialIds.Count == 0 ? new List() : await _db.Queryable().Where(item => materialIds.Contains(item.Id)).ToListAsync(); var expiryConfig = await _configService.GetByConfigKey( CateGoryConst.CONFIG_BIZ_DEFINE, "onboard.link.expireHours"); if (expiryConfig == null || !int.TryParse(expiryConfig.ConfigValue, out var expiryHours) || expiryHours <= 0) { throw Oops.Bah("请在系统配置中设置 onboard.link.expireHours(正整数小时)"); } var taskId = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(); var expiresAt = DateTime.Now.AddHours(expiryHours); var token = Guid.NewGuid().ToString("N"); var task = new ObOnboardingTask { Id = taskId, TaskNo = $"OB{DateTime.Now:yyyyMMddHHmmss}{taskId.ToString()[^4..]}", CompanyId = input.CompanyId, PositionId = input.PositionId, OnboardingTemplateId = onboardingTemplate.Id, AssignedReviewerUserId = input.AssignedReviewerUserId, LinkExpireAt = expiresAt, TaskStatus = OnboardingTaskStatus.PendingFill, Remark = input.Remark }; await _db.Ado.BeginTranAsync(); try { await _db.Insertable(task).ExecuteCommandAsync(); await _db.Insertable(new ObTaskLink { Id = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(), TaskId = taskId, TokenHash = Convert.ToHexString(global::System.Security.Cryptography.SHA256.HashData( global::System.Text.Encoding.UTF8.GetBytes(token))), ExpiresAt = expiresAt }).ExecuteCommandAsync(); var contracts = contractConfigs.Select(config => { var template = contractTemplates.First(item => item.Id == config.ContractTemplateId); return new ObTaskContract { Id = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(), TaskId = taskId, TemplateId = config.ContractTemplateId, TemplateVersionId = config.ContractTemplateVersionId, ContractName = template.TemplateName, ContractType = template.ContractType, Required = config.Required, PrintCopies = config.PrintCopies }; }).ToList(); await _db.Insertable(contracts).ExecuteCommandAsync(); if (fieldConfigs.Count > 0) { var fieldSnapshots = fieldConfigs.Select(config => { var field = fields.First(item => item.Id == config.FieldId); return new ObTaskFormRequirement { Id = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(), TaskId = taskId, FieldId = field.Id, FieldCode = field.FieldCode, FieldName = field.FieldName, DataType = field.DataType, OptionsJson = field.OptionsJson, Required = config.Required, SortCode = config.SortCode }; }).ToList(); await _db.Insertable(fieldSnapshots).ExecuteCommandAsync(); } if (materialConfigs.Count > 0) { var materialSnapshots = materialConfigs.Select(config => { var material = materialTypes.First(item => item.Id == config.MaterialTypeId); return new ObTaskMaterialRequirement { Id = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(), TaskId = taskId, MaterialTypeId = material.Id, MaterialCode = material.MaterialCode, MaterialName = material.MaterialName, AllowedExtensions = material.AllowedExtensions, MaxFileCount = material.MaxFileCount, MaxFileSizeMb = material.MaxFileSizeMb, Required = config.Required, SortCode = config.SortCode }; }).ToList(); await _db.Insertable(materialSnapshots).ExecuteCommandAsync(); } await _snapshotService.Build(taskId); await ApplyInitialContractVariables(input.ContractVariables, contracts); await _db.Ado.CommitTranAsync(); } catch { await _db.Ado.RollbackTranAsync(); throw; } return new OnboardingTaskCreatedOutput { TaskId = taskId, TaskNo = task.TaskNo, LinkToken = token, ExpiresAt = expiresAt }; } /// /// 校验并保存任务创建时填写的人事合同变量。 /// private async Task ApplyInitialContractVariables( List inputs, List contracts) { inputs ??= new List(); var versionIds = contracts.Select(item => item.TemplateVersionId).Distinct().ToList(); var definitions = await _db.Queryable() .Where(item => versionIds.Contains(item.TemplateVersionId) && item.HrEditable && (item.VariableType == ContractVariableType.Contract || item.VariableType == ContractVariableType.Company)) .ToListAsync(); var duplicateInput = inputs .GroupBy(item => new { item.ContractTemplateVersionId, item.VariableCode }) .FirstOrDefault(group => group.Count() > 1); if (duplicateInput != null) throw Oops.Bah($"合同变量“{duplicateInput.Key.VariableCode}”重复提交"); foreach (var input in inputs) { var definitionExists = definitions.Any(item => item.TemplateVersionId == input.ContractTemplateVersionId && item.VariableCode == input.VariableCode); if (!definitionExists) throw Oops.Bah($"合同变量“{input.VariableCode}”不存在或不允许人事填写"); } var missingRequired = definitions.FirstOrDefault(definition => { if (!definition.Required) return false; var input = inputs.FirstOrDefault(item => item.ContractTemplateVersionId == definition.TemplateVersionId && item.VariableCode == definition.VariableCode); return string.IsNullOrWhiteSpace(input?.VariableValue ?? definition.DefaultValue); }); if (missingRequired != null) throw Oops.Bah($"请填写合同变量“{missingRequired.VariableName}”"); if (inputs.Count == 0) return; var contractByVersion = contracts.ToDictionary(item => item.TemplateVersionId); var contractIds = contracts.Select(item => item.Id).ToList(); var rows = await _db.Queryable() .Where(item => contractIds.Contains(item.TaskContractId)) .ToListAsync(); foreach (var input in inputs) { var contract = contractByVersion[input.ContractTemplateVersionId]; var row = rows.FirstOrDefault(item => item.TaskContractId == contract.Id && item.VariableCode == input.VariableCode) ?? throw Oops.Bah($"合同变量快照“{input.VariableCode}”不存在"); row.VariableValue = input.VariableValue; row.SourceType = ContractVariableSourceType.HrInput; row.IsManualOverride = true; } var changedRows = rows.Where(item => item.IsManualOverride).ToList(); if (changedRows.Count > 0) await _db.Updateable(changedRows).ExecuteCommandAsync(); } }