diff --git a/SimpleAdmin.Application/Services/Onboarding/ContractTemplate/OnboardingContractTemplateService.cs b/SimpleAdmin.Application/Services/Onboarding/ContractTemplate/OnboardingContractTemplateService.cs index f82129e..1ff7981 100644 --- a/SimpleAdmin.Application/Services/Onboarding/ContractTemplate/OnboardingContractTemplateService.cs +++ b/SimpleAdmin.Application/Services/Onboarding/ContractTemplate/OnboardingContractTemplateService.cs @@ -147,7 +147,7 @@ public class OnboardingContractTemplateService : DbRepository 0) + { + throw Oops.Bah( + $"该签署材料版本正在被启用中的入职模板{FormatTemplateNames(referencedNames)}引用,不能停用,请先调整或停用这些入职模板"); + } + } if (input.VersionStatus == ContractTemplateVersionStatus.Enabled) { ContractTemplateVersionValidity.ValidateRange(version.EffectiveDate, version.ExpiryDate); @@ -550,7 +562,8 @@ public class OnboardingContractTemplateService : DbRepository private async Task ValidateReferencedOnboardingTemplates( - OnboardingContractTemplateEditInput input) + OnboardingContractTemplateEditInput input, + string currentStatus) { var relations = await Context.Queryable() .Where(item => item.ContractTemplateId == input.Id) @@ -565,6 +578,21 @@ public class OnboardingContractTemplateService : DbRepository() .Where(item => onboardingTemplateIds.Contains(item.Id)) .ToListAsync(); + if (string.Equals(currentStatus, "enable", StringComparison.OrdinalIgnoreCase) + && !string.Equals(input.Status, "enable", StringComparison.OrdinalIgnoreCase)) + { + var enabledTemplateNames = onboardingTemplates + .Where(item => !item.IsDelete && item.Status == "enable") + .Select(item => item.TemplateName) + .Distinct() + .OrderBy(item => item) + .ToList(); + if (enabledTemplateNames.Count > 0) + { + throw Oops.Bah( + $"该签署材料模板正在被启用中的入职模板{FormatTemplateNames(enabledTemplateNames)}引用,不能停用,请先调整或停用这些入职模板"); + } + } var positionRelations = await Context.Queryable() .Where(item => onboardingTemplateIds.Contains(item.OnboardingTemplateId)) .ToListAsync(); @@ -589,6 +617,39 @@ public class OnboardingContractTemplateService : DbRepository> GetEnabledOnboardingTemplateNames( + long contractTemplateId, + long? contractTemplateVersionId = null) + { + var relations = await Context.Queryable() + .Where(item => item.ContractTemplateId == contractTemplateId) + .WhereIF(contractTemplateVersionId.HasValue, + item => item.ContractTemplateVersionId == contractTemplateVersionId.Value) + .ToListAsync(); + if (relations.Count == 0) + return new List(); + + var onboardingTemplateIds = relations + .Select(item => item.OnboardingTemplateId) + .Distinct() + .ToList(); + var names = await Context.Queryable() + .Where(item => onboardingTemplateIds.Contains(item.Id) + && !item.IsDelete + && item.Status == "enable") + .Select(item => item.TemplateName) + .ToListAsync(); + return names + .Where(item => !string.IsNullOrWhiteSpace(item)) + .Distinct() + .OrderBy(item => item) + .ToList(); + } + + private static string FormatTemplateNames(IEnumerable templateNames) + { + return string.Join("、", templateNames.Select(item => $"“{item}”")); + } /// /// 构建合同模板主档案实体。 /// diff --git a/SimpleAdmin.Application/Services/Onboarding/IdentityValidation/OnboardingIdentityValidationService.cs b/SimpleAdmin.Application/Services/Onboarding/IdentityValidation/OnboardingIdentityValidationService.cs index 40545de..3705961 100644 --- a/SimpleAdmin.Application/Services/Onboarding/IdentityValidation/OnboardingIdentityValidationService.cs +++ b/SimpleAdmin.Application/Services/Onboarding/IdentityValidation/OnboardingIdentityValidationService.cs @@ -31,6 +31,14 @@ public class OnboardingIdentityValidationService : IOnboardingIdentityValidation throw Oops.Bah("该姓名和身份证号已完成正式员工建档,请勿重复提交入职资料"); } + // 身份证号码代表唯一自然人。即使姓名填写不同,也不能绕过正式员工重复校验。 + var formalEmployeeIdCardExists = await _db.Queryable() + .AnyAsync(item => item.IdCardNumber == idCardNumber); + if (formalEmployeeIdCardExists) + { + throw Oops.Bah("该身份证号码已完成正式员工建档,请核对姓名和身份证号码;如需更正,请联系 HR"); + } + // 待填写任务不占用身份,以实际先提交的任务为准;撤销或过期任务允许重新发起。 var duplicateTaskExists = await _db.Queryable() .InnerJoin((info, task) => info.TaskId == task.Id) @@ -42,9 +50,24 @@ public class OnboardingIdentityValidationService : IOnboardingIdentityValidation || task.TaskStatus == OnboardingTaskStatus.Approved || task.TaskStatus == OnboardingTaskStatus.Archived)) .AnyAsync(); - if (!duplicateTaskExists) - return; + if (duplicateTaskExists) + { + throw Oops.Bah("该姓名和身份证号已存在正在办理的入职任务,请勿重复提交;如需补充材料,请联系 HR 继续使用原任务链接"); + } - throw Oops.Bah("该姓名和身份证号已存在正在办理的入职任务,请勿重复提交;如需补充材料,请联系 HR 继续使用原任务链接"); + // 再单独按身份证号码校验,防止不同姓名使用同一证件重复提交不同任务。 + var duplicateIdCardTaskExists = await _db.Queryable() + .InnerJoin((info, task) => info.TaskId == task.Id) + .Where((info, task) => info.TaskId != currentTaskId + && info.IdCardNumber == idCardNumber + && (task.TaskStatus == OnboardingTaskStatus.Reviewing + || task.TaskStatus == OnboardingTaskStatus.Rejected + || task.TaskStatus == OnboardingTaskStatus.Approved + || task.TaskStatus == OnboardingTaskStatus.Archived)) + .AnyAsync(); + if (duplicateIdCardTaskExists) + { + throw Oops.Bah("该身份证号码已存在正在办理的入职任务,请核对姓名和身份证号码;如需补充材料,请联系 HR 继续使用原任务链接"); + } } -} +} \ No newline at end of file diff --git a/SimpleAdmin.Application/Services/Onboarding/OnboardingTemplate/OnboardingTemplateService.cs b/SimpleAdmin.Application/Services/Onboarding/OnboardingTemplate/OnboardingTemplateService.cs index 6a94678..29ddd79 100644 --- a/SimpleAdmin.Application/Services/Onboarding/OnboardingTemplate/OnboardingTemplateService.cs +++ b/SimpleAdmin.Application/Services/Onboarding/OnboardingTemplate/OnboardingTemplateService.cs @@ -229,7 +229,65 @@ public class OnboardingTemplateService : IOnboardingTemplateService && templateIds.Contains(item.Id)) .OrderBy(item => item.TemplateName) .ToListAsync(); - return await BuildOutputs(templates); + if (templates.Count == 0) + return await BuildOutputs(templates); + + var onboardingTemplateIds = templates.Select(item => item.Id).ToList(); + var contractConfigs = await _db.Queryable() + .Where(item => onboardingTemplateIds.Contains(item.OnboardingTemplateId)) + .ToListAsync(); + if (contractConfigs.Count == 0) + return await BuildOutputs(new List()); + + var contractTemplateIds = contractConfigs + .Select(item => item.ContractTemplateId) + .Distinct() + .ToList(); + var contractVersionIds = contractConfigs + .Select(item => item.ContractTemplateVersionId) + .Distinct() + .ToList(); + var contractTemplates = await _db.Queryable() + .Where(item => contractTemplateIds.Contains(item.Id)) + .ToListAsync(); + var contractVersions = await _db.Queryable() + .Where(item => contractVersionIds.Contains(item.Id)) + .ToListAsync(); + var contractPositions = await _db.Queryable() + .Where(item => contractTemplateIds.Contains(item.ContractTemplateId) + && item.PositionId == positionId) + .ToListAsync(); + var now = DateTime.Now; + var availableTemplates = templates.Where(template => + { + var configs = contractConfigs + .Where(item => item.OnboardingTemplateId == template.Id) + .ToList(); + if (configs.Count == 0) + return false; + + return configs.All(config => + { + var contractTemplate = contractTemplates + .FirstOrDefault(item => item.Id == config.ContractTemplateId); + var version = contractVersions + .FirstOrDefault(item => item.Id == config.ContractTemplateVersionId); + return contractTemplate != null + && string.Equals( + contractTemplate.Status, + "enable", + StringComparison.OrdinalIgnoreCase) + && contractTemplate.CompanyId == template.CompanyId + && contractTemplate.DepartmentId == template.DepartmentId + && contractPositions.Any(item => + item.ContractTemplateId == config.ContractTemplateId) + && version != null + && version.TemplateId == config.ContractTemplateId + && ContractTemplateVersionValidity.IsAvailable(version, now); + }); + }).ToList(); + + return await BuildOutputs(availableTemplates); } private static void NormalizeAndValidateInput(OnboardingTemplateSaveInput input) diff --git a/SimpleAdmin.System/Services/Ops/Config/ConfigService.cs b/SimpleAdmin.System/Services/Ops/Config/ConfigService.cs index 46e0698..6ca354d 100644 --- a/SimpleAdmin.System/Services/Ops/Config/ConfigService.cs +++ b/SimpleAdmin.System/Services/Ops/Config/ConfigService.cs @@ -25,6 +25,10 @@ public class ConfigService : DbRepository, IConfigService /// public async Task> GetConfigsByCategory(string category) { + // 业务自定义配置需立即生效,始终以数据库中的最新数据为准。 + if (category == CateGoryConst.CONFIG_BIZ_DEFINE) + return await GetListAsync(it => it.Category == category); + var key = SystemConst.CACHE_DEV_CONFIG + category;//系统配置key //先从redis拿配置 var configList = _simpleCacheService.HashGetAll(key).Values.ToList(); @@ -51,6 +55,10 @@ public class ConfigService : DbRepository, IConfigService /// public async Task GetByConfigKey(string category, string configKey) { + // 业务自定义配置不走缓存,避免修改后仍读取到本机旧值。 + if (category == CateGoryConst.CONFIG_BIZ_DEFINE) + return await GetFirstAsync(it => it.Category == category && it.ConfigKey == configKey); + var key = SystemConst.CACHE_DEV_CONFIG + category;//系统配置key //先从redis拿配置 var config = _simpleCacheService.HashGetOne(key, configKey);