namespace SimpleAdmin.Application; /// /// 实现入职模板聚合配置及公司岗位匹配能力。 /// public class OnboardingTemplateService : IOnboardingTemplateService { private readonly SqlSugarScope _db; private readonly IOnboardingDataPermissionService _dataPermission; public OnboardingTemplateService(IOnboardingDataPermissionService dataPermission) { _db = DbContext.DB; _dataPermission = dataPermission; } /// 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(input.DepartmentId.HasValue, item => item.DepartmentId == input.DepartmentId.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, CreateUserId = summary.CreateUserId, CreateUser = summary.CreateUser, TemplateCode = summary.TemplateCode, TemplateName = summary.TemplateName, CompanyId = summary.CompanyId, CompanyName = summary.CompanyName, DepartmentId = summary.DepartmentId, DepartmentName = summary.DepartmentName, 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 = new List(), 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 codeOwnerQuery = _db.Queryable() .ClearFilter() .Where(item => item.TemplateCode == input.TemplateCode); if (input.Id.HasValue) { var currentTemplateId = input.Id.Value; codeOwnerQuery = codeOwnerQuery.Where(item => item.Id != currentTemplateId); } var codeOwner = await codeOwnerQuery.FirstAsync(); if (codeOwner != null && !codeOwner.IsDelete) throw Oops.Bah("入职模板编码已存在"); if (input.Id.HasValue && codeOwner != null && codeOwner.IsDelete) throw Oops.Bah("该入职模板编码属于一条已删除记录,请通过新增模板重新启用该编码"); // 新增时如果编码命中历史软删除记录,复用原 Id 并恢复,避免唯一索引直接抛 500。 var isRestore = !input.Id.HasValue && codeOwner != null && codeOwner.IsDelete; var templateId = isRestore ? codeOwner.Id : input.Id ?? global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(); if (input.Id.HasValue || isRestore) { var existing = isRestore ? codeOwner : await _db.Queryable().InSingleAsync(templateId); if (existing == null) throw Oops.Bah("入职模板不存在"); await _dataPermission.EnsureCanManage( existing.CreateUserId, OnboardingPermissionCodes.EntryConfigManageAll, "入职模板"); } var entity = new ObOnboardingTemplate { Id = templateId, TemplateCode = input.TemplateCode.Trim(), TemplateName = input.TemplateName.Trim(), CompanyId = input.CompanyId, DepartmentId = input.DepartmentId, Description = input.Description, Status = input.Status, IsDelete = false }; await _db.Ado.BeginTranAsync(); try { if (input.Id.HasValue || isRestore) { var exists = isRestore || 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 templates = await _db.Queryable() .Where(item => input.Ids.Contains(item.Id)) .ToListAsync(); if (templates.Count != input.Ids.Distinct().Count()) throw Oops.Bah("入职模板不存在"); await _dataPermission.EnsureCanManage( templates.Select(item => item.CreateUserId), OnboardingPermissionCodes.EntryConfigManageAll, "入职模板"); 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.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 departmentId, 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.DepartmentId == departmentId && item.Status == "enable" && templateIds.Contains(item.Id)) .OrderBy(item => item.TemplateName) .ToListAsync(); return await BuildOutputs(templates); } private static void NormalizeAndValidateInput(OnboardingTemplateSaveInput input) { input.TemplateCode = OnboardingCodeValidator.Normalize( input.TemplateCode, "入职模板编码", 100); input.TemplateName = input.TemplateName?.Trim(); input.PositionIds = input.PositionIds?.Distinct().ToList() ?? new List(); input.Contracts ??= new List(); var duplicateContract = input.Contracts .GroupBy(item => item.ContractTemplateId) .FirstOrDefault(group => group.Count() > 1); if (duplicateContract != null) throw Oops.Bah("同一份合同模板在入职模板中只能选择一个版本"); 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.TemplateName)) throw Oops.Bah("请填写入职模板名称"); if (input.CompanyId <= 0) throw Oops.Bah("请选择所属公司"); if (input.DepartmentId <= 0) throw Oops.Bah("请选择所属部门"); if (input.PositionIds.Count == 0) throw Oops.Bah("请至少选择一个适用岗位"); if (input.PositionIds.Any(positionId => positionId <= 0)) throw Oops.Bah("适用岗位数据不正确"); if (input.Contracts.Count == 0) throw Oops.Bah("请至少选择一份合同文件"); if (input.Contracts.Any(item => item.ContractTemplateId <= 0 || item.ContractTemplateVersionId <= 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 departmentEnabled = await _db.Queryable() .AnyAsync(item => item.Id == input.DepartmentId && item.CompanyId == input.CompanyId && item.Status == "enable"); if (!departmentEnabled) throw Oops.Bah("所属部门不存在、未启用或不属于该公司"); var positionCount = await _db.Queryable() .CountAsync(item => input.PositionIds.Contains(item.Id) && item.CompanyId == input.CompanyId && item.DepartmentId == input.DepartmentId && 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("合同模板与版本不匹配"); } var now = DateTime.Now; if (versions.Any(version => !ContractTemplateVersionValidity.IsAvailable(version, now))) throw Oops.Bah("入职模板只能选择当前已生效且尚未失效的启用签署材料版本"); var contractTemplateIds = input.Contracts .Select(item => item.ContractTemplateId) .Distinct() .ToList(); var contractTemplates = await _db.Queryable() .Where(item => contractTemplateIds.Contains(item.Id)) .ToListAsync(); if (contractTemplates.Count != contractTemplateIds.Count || contractTemplates.Any(item => item.CompanyId != input.CompanyId || item.DepartmentId != input.DepartmentId)) { throw Oops.Bah("存在不属于当前公司、部门的合同模板"); } var contractPositionRelations = await _db.Queryable() .Where(item => contractTemplateIds.Contains(item.ContractTemplateId)) .ToListAsync(); var unsupportedContract = contractTemplateIds.FirstOrDefault(contractTemplateId => { var supportedPositionIds = contractPositionRelations .Where(item => item.ContractTemplateId == contractTemplateId) .Select(item => item.PositionId) .ToHashSet(); return input.PositionIds.Any(positionId => !supportedPositionIds.Contains(positionId)); }); if (unsupportedContract > 0) { var contractName = contractTemplates .First(item => item.Id == unsupportedContract) .TemplateName; throw Oops.Bah($"合同模板“{contractName}”不能覆盖当前选择的全部岗位"); } if (input.Status == "enable") { if (contractTemplates.Any(item => !string.Equals( item.Status, "enable", global::System.StringComparison.OrdinalIgnoreCase))) throw Oops.Bah("启用入职模板前,请先启用全部合同模板"); } 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 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.Materials != null && 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(); } 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 departmentIds = templates .Where(item => item.DepartmentId.HasValue) .Select(item => item.DepartmentId.Value) .Distinct() .ToList(); var companies = await _db.Queryable() .Where(item => companyIds.Contains(item.Id)) .ToListAsync(); var departments = departmentIds.Count == 0 ? new List() : await _db.Queryable() .Where(item => departmentIds.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 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, CreateUserId = template.CreateUserId, CreateUser = template.CreateUser, TemplateCode = template.TemplateCode, TemplateName = template.TemplateName, CompanyId = template.CompanyId, CompanyName = companies.FirstOrDefault(item => item.Id == template.CompanyId)?.CompanyName, DepartmentId = template.DepartmentId, DepartmentName = departments .FirstOrDefault(item => item.Id == template.DepartmentId)?.DepartmentName, 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 = 0, MaterialCount = materials.Count(item => item.OnboardingTemplateId == template.Id) }; }).ToList(); } }