namespace SimpleAdmin.Application; /// /// 实现入职模板聚合配置及公司岗位匹配能力。 /// public class OnboardingTemplateService : IOnboardingTemplateService { private readonly SqlSugarScope _db; public OnboardingTemplateService() { _db = DbContext.DB; } /// public async Task> Page(OnboardingTemplatePageInput input) { var query = _db.Queryable() .Where(item => !item.IsDelete) .WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), item => item.TemplateCode.Contains(input.Keyword) || item.TemplateName.Contains(input.Keyword)) .WhereIF(input.CompanyId.HasValue, item => item.CompanyId == input.CompanyId.Value) .WhereIF(!string.IsNullOrWhiteSpace(input.Status), item => item.Status == input.Status); if (input.PositionId.HasValue) { var templateIds = await _db.Queryable() .Where(item => item.PositionId == input.PositionId.Value) .Select(item => item.OnboardingTemplateId) .ToListAsync(); query = query.Where(item => templateIds.Contains(item.Id)); } var page = await query .OrderByDescending(item => item.CreateTime) .ToPagedListAsync(input.PageNum, input.PageSize); var rows = await BuildOutputs(page.List.ToList()); return new SqlSugarPagedList { PageNum = page.PageNum, PageSize = page.PageSize, Total = page.Total, Pages = page.Pages, HasPrevPages = page.HasPrevPages, HasNextPages = page.HasNextPages, List = rows }; } /// public async Task Detail(BaseIdInput input) { var template = await _db.Queryable().InSingleAsync(input.Id) ?? throw Oops.Bah("入职模板不存在"); var summary = (await BuildOutputs(new List { template })).Single(); return new OnboardingTemplateDetailOutput { Id = summary.Id, TemplateCode = summary.TemplateCode, TemplateName = summary.TemplateName, CompanyId = summary.CompanyId, CompanyName = summary.CompanyName, Description = summary.Description, Status = summary.Status, CreateTime = summary.CreateTime, UpdateTime = summary.UpdateTime, PositionIds = summary.PositionIds, PositionNames = summary.PositionNames, ContractCount = summary.ContractCount, FormFieldCount = summary.FormFieldCount, MaterialCount = summary.MaterialCount, Contracts = await _db.Queryable() .Where(item => item.OnboardingTemplateId == template.Id) .OrderBy(item => item.SortCode) .ToListAsync(), FormFields = await _db.Queryable() .Where(item => item.OnboardingTemplateId == template.Id) .OrderBy(item => item.SortCode) .ToListAsync(), Materials = await _db.Queryable() .Where(item => item.OnboardingTemplateId == template.Id) .OrderBy(item => item.SortCode) .ToListAsync() }; } /// public async Task Save(OnboardingTemplateSaveInput input) { NormalizeAndValidateInput(input); await ValidateReferences(input); var templateId = input.Id ?? global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(); var codeExists = await _db.Queryable() .AnyAsync(item => item.TemplateCode == input.TemplateCode && item.Id != templateId); if (codeExists) throw Oops.Bah("入职模板编码已存在"); var entity = new ObOnboardingTemplate { Id = templateId, TemplateCode = input.TemplateCode.Trim(), TemplateName = input.TemplateName.Trim(), CompanyId = input.CompanyId, Description = input.Description, Status = input.Status }; await _db.Ado.BeginTranAsync(); try { if (input.Id.HasValue) { var exists = await _db.Queryable().AnyAsync(item => item.Id == templateId); if (!exists) throw Oops.Bah("入职模板不存在"); await _db.Updateable(entity).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommandAsync(); await DeleteRelations(templateId); } else { await _db.Insertable(entity).ExecuteCommandAsync(); } await InsertRelations(templateId, input); await _db.Ado.CommitTranAsync(); return templateId; } catch { await _db.Ado.RollbackTranAsync(); throw; } } /// public async Task Delete(BaseIdListInput input) { if (input.Ids.Count == 0) return; var isUsed = await _db.Queryable() .AnyAsync(item => input.Ids.Contains(item.OnboardingTemplateId)); if (isUsed) throw Oops.Bah("入职模板已生成任务,不能删除,可以改为停用"); await _db.Ado.BeginTranAsync(); try { foreach (var id in input.Ids) await DeleteRelations(id); await _db.Deleteable() .Where(it => input.Ids.Contains(it.Id)) .ExecuteCommandAsync(); await _db.Updateable() .SetColumns(it => it.IsDelete == true) .Where(it => input.Ids.Contains(it.Id)) .ExecuteCommandAsync(); await _db.Ado.CommitTranAsync(); } catch { await _db.Ado.RollbackTranAsync(); throw; } } /// public async Task> ListEnabled(long companyId, long positionId) { var templateIds = await _db.Queryable() .Where(item => item.PositionId == positionId) .Select(item => item.OnboardingTemplateId) .ToListAsync(); var templates = await _db.Queryable() .Where(item => item.CompanyId == companyId && item.Status == "enable" && templateIds.Contains(item.Id)) .OrderBy(item => item.TemplateName) .ToListAsync(); return await BuildOutputs(templates); } private static void NormalizeAndValidateInput(OnboardingTemplateSaveInput input) { input.TemplateCode = input.TemplateCode?.Trim(); input.TemplateName = input.TemplateName?.Trim(); input.PositionIds = input.PositionIds?.Distinct().ToList() ?? new List(); input.Contracts = input.Contracts? .GroupBy(item => item.ContractTemplateVersionId) .Select(group => group.First()) .ToList() ?? new List(); input.FormFields = input.FormFields? .GroupBy(item => item.FieldId) .Select(group => group.First()) .ToList() ?? new List(); input.Materials = input.Materials? .GroupBy(item => item.MaterialTypeId) .Select(group => group.First()) .ToList() ?? new List(); if (string.IsNullOrWhiteSpace(input.TemplateCode) || string.IsNullOrWhiteSpace(input.TemplateName)) throw Oops.Bah("请填写入职模板编码和名称"); if (input.CompanyId <= 0) throw Oops.Bah("请选择所属公司"); if (input.PositionIds.Count == 0) throw Oops.Bah("请至少选择一个适用岗位"); if (input.Contracts.Count == 0) throw Oops.Bah("请至少选择一份合同文件"); if (input.Status != "enable" && input.Status != "disable") throw Oops.Bah("入职模板状态不正确"); if (input.Contracts.Any(item => item.PrintCopies <= 0)) throw Oops.Bah("合同打印份数必须大于 0"); } private async Task ValidateReferences(OnboardingTemplateSaveInput input) { var companyEnabled = await _db.Queryable() .AnyAsync(item => item.Id == input.CompanyId && item.Status == "enable"); if (!companyEnabled) throw Oops.Bah("所属公司不存在或未启用"); var positionCount = await _db.Queryable() .CountAsync(item => input.PositionIds.Contains(item.Id) && item.CompanyId == input.CompanyId && item.Status == "enable"); if (positionCount != input.PositionIds.Count) throw Oops.Bah("存在不属于该公司或未启用的岗位"); var versionIds = input.Contracts.Select(item => item.ContractTemplateVersionId).ToList(); var versions = await _db.Queryable() .Where(item => versionIds.Contains(item.Id)) .ToListAsync(); if (versions.Count != versionIds.Count || input.Contracts.Any(contract => versions.All(version => version.Id != contract.ContractTemplateVersionId || version.TemplateId != contract.ContractTemplateId))) { throw Oops.Bah("合同模板与版本不匹配"); } if (input.Status == "enable") { if (versions.Any(item => item.VersionStatus != ContractTemplateVersionStatus.Enabled)) throw Oops.Bah("启用入职模板前,请先启用全部合同版本"); var variables = await _db.Queryable() .Where(item => versionIds.Contains(item.TemplateVersionId)) .ToListAsync(); var validTypes = new[] { ContractVariableType.Employee, ContractVariableType.Company, ContractVariableType.Contract, ContractVariableType.System }; if (variables.Any(item => !validTypes.Contains(item.VariableType))) throw Oops.Bah("合同中仍有未确认来源的变量,请先完成变量映射"); } var fieldIds = input.FormFields.Select(item => item.FieldId).ToList(); var enabledFields = fieldIds.Count == 0 ? new List() : await _db.Queryable() .Where(item => fieldIds.Contains(item.Id) && item.Status == "enable") .ToListAsync(); if (enabledFields.Count != fieldIds.Count) throw Oops.Bah("存在未启用或不存在的动态补充字段"); if (input.Status == "enable") { var variables = await _db.Queryable() .Where(item => versionIds.Contains(item.TemplateVersionId) && item.VariableType == ContractVariableType.Employee) .ToListAsync(); var availableFields = new HashSet( typeof(ObTaskEmployeeInfo).GetProperties().Select(property => property.Name), StringComparer.OrdinalIgnoreCase); foreach (var fieldCode in enabledFields.Select(item => item.FieldCode)) availableFields.Add(fieldCode); var missingMapping = variables.FirstOrDefault(variable => !availableFields.Contains(ResolveEmployeeVariableField(variable))); if (missingMapping != null) { throw Oops.Bah( $"合同员工变量“{missingMapping.VariableCode}”没有对应的固定字段或模板补充字段,请先完成字段映射"); } } var materialIds = input.Materials.Select(item => item.MaterialTypeId).ToList(); if (materialIds.Count > 0) { var materialCount = await _db.Queryable() .CountAsync(item => materialIds.Contains(item.Id) && item.Status == "enable"); if (materialCount != materialIds.Count) throw Oops.Bah("存在未启用或不存在的上传材料类型"); } } private static string ResolveEmployeeVariableField(ObTemplateVariable variable) { var sourceField = variable.EmployeeField; if (string.IsNullOrWhiteSpace(sourceField)) { var separatorIndex = variable.VariableCode?.IndexOf('.') ?? -1; sourceField = separatorIndex >= 0 ? variable.VariableCode[(separatorIndex + 1)..] : variable.VariableCode; } return sourceField?.ToUpperInvariant() switch { "FULLNAME" or "FULL_NAME" or "NAME" => nameof(ObTaskEmployeeInfo.EmployeeName), "MOBILE" or "MOBILEPHONE" or "MOBILE_PHONE" => nameof(ObTaskEmployeeInfo.Phone), "IDNUMBER" or "ID_NUMBER" or "IDENTITYNUMBER" => nameof(ObTaskEmployeeInfo.IdCardNumber), _ => sourceField }; } private async Task InsertRelations(long templateId, OnboardingTemplateSaveInput input) { var positions = input.PositionIds.Select(positionId => new ObOnboardingTemplatePosition { Id = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(), OnboardingTemplateId = templateId, PositionId = positionId }).ToList(); await _db.Insertable(positions).ExecuteCommandAsync(); var contracts = input.Contracts.Select(item => new ObOnboardingTemplateContract { Id = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(), OnboardingTemplateId = templateId, ContractTemplateId = item.ContractTemplateId, ContractTemplateVersionId = item.ContractTemplateVersionId, Required = item.Required, PrintCopies = item.PrintCopies, SortCode = item.SortCode }).ToList(); await _db.Insertable(contracts).ExecuteCommandAsync(); if (input.FormFields.Count > 0) { var fields = input.FormFields.Select(item => new ObOnboardingTemplateFormField { Id = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(), OnboardingTemplateId = templateId, FieldId = item.FieldId, Required = item.Required, SortCode = item.SortCode }).ToList(); await _db.Insertable(fields).ExecuteCommandAsync(); } if (input.Materials.Count > 0) { var materials = input.Materials.Select(item => new ObOnboardingTemplateMaterial { Id = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(), OnboardingTemplateId = templateId, MaterialTypeId = item.MaterialTypeId, Required = item.Required, SortCode = item.SortCode }).ToList(); await _db.Insertable(materials).ExecuteCommandAsync(); } } private async Task DeleteRelations(long templateId) { await _db.Deleteable() .Where(item => item.OnboardingTemplateId == templateId) .ExecuteCommandAsync(); await _db.Deleteable() .Where(item => item.OnboardingTemplateId == templateId) .ExecuteCommandAsync(); await _db.Deleteable() .Where(item => item.OnboardingTemplateId == templateId) .ExecuteCommandAsync(); await _db.Deleteable() .Where(item => item.OnboardingTemplateId == templateId) .ExecuteCommandAsync(); } private async Task> BuildOutputs(List templates) { if (templates.Count == 0) return new List(); var templateIds = templates.Select(item => item.Id).ToList(); var companyIds = templates.Select(item => item.CompanyId).Distinct().ToList(); var companies = await _db.Queryable() .Where(item => companyIds.Contains(item.Id)) .ToListAsync(); var relations = await _db.Queryable() .Where(item => templateIds.Contains(item.OnboardingTemplateId)) .ToListAsync(); var positionIds = relations.Select(item => item.PositionId).Distinct().ToList(); var positions = await _db.Queryable() .Where(item => positionIds.Contains(item.Id)) .ToListAsync(); var contracts = await _db.Queryable() .Where(item => templateIds.Contains(item.OnboardingTemplateId)) .ToListAsync(); var fields = await _db.Queryable() .Where(item => templateIds.Contains(item.OnboardingTemplateId)) .ToListAsync(); var materials = await _db.Queryable() .Where(item => templateIds.Contains(item.OnboardingTemplateId)) .ToListAsync(); return templates.Select(template => { var templatePositions = relations.Where(item => item.OnboardingTemplateId == template.Id).ToList(); return new OnboardingTemplateOutput { Id = template.Id, TemplateCode = template.TemplateCode, TemplateName = template.TemplateName, CompanyId = template.CompanyId, CompanyName = companies.FirstOrDefault(item => item.Id == template.CompanyId)?.CompanyName, Description = template.Description, Status = template.Status, CreateTime = template.CreateTime, UpdateTime = template.UpdateTime, PositionIds = templatePositions.Select(item => item.PositionId).ToList(), PositionNames = templatePositions .Select(relation => positions.FirstOrDefault(position => position.Id == relation.PositionId)?.PositionName) .Where(name => !string.IsNullOrWhiteSpace(name)) .ToList(), ContractCount = contracts.Count(item => item.OnboardingTemplateId == template.Id), FormFieldCount = fields.Count(item => item.OnboardingTemplateId == template.Id), MaterialCount = materials.Count(item => item.OnboardingTemplateId == template.Id) }; }).ToList(); } }