逻辑调整
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
namespace SimpleAdmin.Application;
|
||||
|
||||
/// <summary>
|
||||
/// 实现入职任务创建业务能力。
|
||||
/// 实现基于入职模板创建任务及冻结配置快照的业务能力。
|
||||
/// </summary>
|
||||
public class OnboardingTaskService : IOnboardingTaskService
|
||||
{
|
||||
@@ -19,42 +19,69 @@ public class OnboardingTaskService : IOnboardingTaskService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 校验公司、岗位和合同范围后创建入职任务、链接及任务合同。
|
||||
/// 校验公司、岗位和入职模板后创建任务,并冻结合同、补充字段及材料要求。
|
||||
/// </summary>
|
||||
public async Task<OnboardingTaskCreatedOutput> Create(OnboardingTaskCreateInput input)
|
||||
{
|
||||
var companyEnabled = await _db.Queryable<ObCompany>()
|
||||
.AnyAsync(item => item.Id == input.CompanyId && item.Status == "enable");
|
||||
if (!companyEnabled)
|
||||
{
|
||||
throw Oops.Bah("公司不存在或未启用");
|
||||
}
|
||||
|
||||
var positionEnabled = await _db.Queryable<ObPosition>()
|
||||
.AnyAsync(item => item.Id == input.PositionId
|
||||
&& item.CompanyId == input.CompanyId
|
||||
&& item.Status == "enable");
|
||||
if (!positionEnabled)
|
||||
{
|
||||
throw Oops.Bah("岗位不存在、未启用或不属于该公司");
|
||||
}
|
||||
|
||||
var scopes = await _db.Queryable<ObContractScope>()
|
||||
.Where(item => item.CompanyId == input.CompanyId
|
||||
&& (item.PositionId == null || item.PositionId == input.PositionId))
|
||||
var onboardingTemplate = await _db.Queryable<ObOnboardingTemplate>()
|
||||
.FirstAsync(item => item.Id == input.OnboardingTemplateId
|
||||
&& item.CompanyId == input.CompanyId
|
||||
&& item.Status == "enable")
|
||||
?? throw Oops.Bah("入职模板不存在、未启用或不属于该公司");
|
||||
var templateSupportsPosition = await _db.Queryable<ObOnboardingTemplatePosition>()
|
||||
.AnyAsync(item => item.OnboardingTemplateId == onboardingTemplate.Id
|
||||
&& item.PositionId == input.PositionId);
|
||||
if (!templateSupportsPosition)
|
||||
throw Oops.Bah("所选入职模板不适用于该岗位");
|
||||
|
||||
var contractConfigs = await _db.Queryable<ObOnboardingTemplateContract>()
|
||||
.Where(item => item.OnboardingTemplateId == onboardingTemplate.Id)
|
||||
.OrderBy(item => item.SortCode)
|
||||
.ToListAsync();
|
||||
if (contractConfigs.Count == 0)
|
||||
throw Oops.Bah("入职模板尚未配置合同文件");
|
||||
|
||||
var fieldConfigs = await _db.Queryable<ObOnboardingTemplateFormField>()
|
||||
.Where(item => item.OnboardingTemplateId == onboardingTemplate.Id)
|
||||
.OrderBy(item => item.SortCode)
|
||||
.ToListAsync();
|
||||
var materialConfigs = await _db.Queryable<ObOnboardingTemplateMaterial>()
|
||||
.Where(item => item.OnboardingTemplateId == onboardingTemplate.Id)
|
||||
.OrderBy(item => item.SortCode)
|
||||
.ToListAsync();
|
||||
|
||||
// 同一版本同时存在“全公司”和“指定岗位”范围时,岗位专属配置优先,避免同一合同生成两次。
|
||||
scopes = scopes
|
||||
.GroupBy(item => item.TemplateVersionId)
|
||||
.Select(group => group.FirstOrDefault(item => item.PositionId == input.PositionId) ?? group.First())
|
||||
.ToList();
|
||||
if (scopes.Count == 0)
|
||||
{
|
||||
throw Oops.Bah("该公司和岗位尚未配置合同适用范围");
|
||||
}
|
||||
var templateIds = contractConfigs.Select(item => item.ContractTemplateId).Distinct().ToList();
|
||||
var contractTemplates = await _db.Queryable<ObContractTemplate>()
|
||||
.Where(item => templateIds.Contains(item.Id))
|
||||
.ToListAsync();
|
||||
var versionIds = contractConfigs.Select(item => item.ContractTemplateVersionId).Distinct().ToList();
|
||||
var enabledVersionCount = await _db.Queryable<ObContractTemplateVersion>()
|
||||
.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<ObFormField>()
|
||||
: await _db.Queryable<ObFormField>().Where(item => fieldIds.Contains(item.Id)).ToListAsync();
|
||||
var materialIds = materialConfigs.Select(item => item.MaterialTypeId).Distinct().ToList();
|
||||
var materialTypes = materialIds.Count == 0
|
||||
? new List<ObMaterialType>()
|
||||
: await _db.Queryable<ObMaterialType>().Where(item => materialIds.Contains(item.Id)).ToListAsync();
|
||||
|
||||
var taskId = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId();
|
||||
var expiryConfig = await _configService.GetByConfigKey(
|
||||
CateGoryConst.CONFIG_BIZ_DEFINE,
|
||||
"onboard.link.expireHours");
|
||||
@@ -65,6 +92,7 @@ public class OnboardingTaskService : IOnboardingTaskService
|
||||
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
|
||||
@@ -73,13 +101,13 @@ public class OnboardingTaskService : IOnboardingTaskService
|
||||
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
|
||||
{
|
||||
@@ -93,27 +121,68 @@ public class OnboardingTaskService : IOnboardingTaskService
|
||||
ExpiresAt = expiresAt
|
||||
}).ExecuteCommandAsync();
|
||||
|
||||
var templateIds = scopes.Select(item => item.TemplateId).Distinct().ToList();
|
||||
var templates = await _db.Queryable<ObContractTemplate>()
|
||||
.Where(item => templateIds.Contains(item.Id))
|
||||
.ToListAsync();
|
||||
var contracts = scopes.Select(scope =>
|
||||
var contracts = contractConfigs.Select(config =>
|
||||
{
|
||||
var template = templates.First(item => item.Id == scope.TemplateId);
|
||||
var template = contractTemplates.First(item => item.Id == config.ContractTemplateId);
|
||||
return new ObTaskContract
|
||||
{
|
||||
Id = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(),
|
||||
TaskId = taskId,
|
||||
TemplateId = scope.TemplateId,
|
||||
TemplateVersionId = scope.TemplateVersionId,
|
||||
TemplateId = config.ContractTemplateId,
|
||||
TemplateVersionId = config.ContractTemplateVersionId,
|
||||
ContractName = template.TemplateName,
|
||||
ContractType = template.ContractType,
|
||||
Required = scope.Required
|
||||
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
|
||||
@@ -130,4 +199,73 @@ public class OnboardingTaskService : IOnboardingTaskService
|
||||
ExpiresAt = expiresAt
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 校验并保存任务创建时填写的人事合同变量。
|
||||
/// </summary>
|
||||
private async Task ApplyInitialContractVariables(
|
||||
List<OnboardingInitialContractVariableInput> inputs,
|
||||
List<ObTaskContract> contracts)
|
||||
{
|
||||
inputs ??= new List<OnboardingInitialContractVariableInput>();
|
||||
var versionIds = contracts.Select(item => item.TemplateVersionId).Distinct().ToList();
|
||||
var definitions = await _db.Queryable<ObTemplateVariable>()
|
||||
.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<ObTaskContractVariable>()
|
||||
.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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user