440 lines
19 KiB
C#
440 lines
19 KiB
C#
namespace SimpleAdmin.Application;
|
|
|
|
/// <summary>
|
|
/// 实现入职模板聚合配置及公司岗位匹配能力。
|
|
/// </summary>
|
|
public class OnboardingTemplateService : IOnboardingTemplateService
|
|
{
|
|
private readonly SqlSugarScope _db;
|
|
|
|
public OnboardingTemplateService()
|
|
{
|
|
_db = DbContext.DB;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public async Task<SqlSugarPagedList<OnboardingTemplateOutput>> Page(OnboardingTemplatePageInput input)
|
|
{
|
|
var query = _db.Queryable<ObOnboardingTemplate>()
|
|
.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<ObOnboardingTemplatePosition>()
|
|
.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<OnboardingTemplateOutput>
|
|
{
|
|
PageNum = page.PageNum,
|
|
PageSize = page.PageSize,
|
|
Total = page.Total,
|
|
Pages = page.Pages,
|
|
HasPrevPages = page.HasPrevPages,
|
|
HasNextPages = page.HasNextPages,
|
|
List = rows
|
|
};
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public async Task<OnboardingTemplateDetailOutput> Detail(BaseIdInput input)
|
|
{
|
|
var template = await _db.Queryable<ObOnboardingTemplate>().InSingleAsync(input.Id)
|
|
?? throw Oops.Bah("入职模板不存在");
|
|
var summary = (await BuildOutputs(new List<ObOnboardingTemplate> { 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<ObOnboardingTemplateContract>()
|
|
.Where(item => item.OnboardingTemplateId == template.Id)
|
|
.OrderBy(item => item.SortCode)
|
|
.ToListAsync(),
|
|
FormFields = await _db.Queryable<ObOnboardingTemplateFormField>()
|
|
.Where(item => item.OnboardingTemplateId == template.Id)
|
|
.OrderBy(item => item.SortCode)
|
|
.ToListAsync(),
|
|
Materials = await _db.Queryable<ObOnboardingTemplateMaterial>()
|
|
.Where(item => item.OnboardingTemplateId == template.Id)
|
|
.OrderBy(item => item.SortCode)
|
|
.ToListAsync()
|
|
};
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public async Task<long> Save(OnboardingTemplateSaveInput input)
|
|
{
|
|
NormalizeAndValidateInput(input);
|
|
await ValidateReferences(input);
|
|
|
|
var templateId = input.Id ?? global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId();
|
|
var codeExists = await _db.Queryable<ObOnboardingTemplate>()
|
|
.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<ObOnboardingTemplate>().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;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public async Task Delete(BaseIdListInput input)
|
|
{
|
|
if (input.Ids.Count == 0)
|
|
return;
|
|
|
|
var isUsed = await _db.Queryable<ObOnboardingTask>()
|
|
.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<ObOnboardingTemplate>()
|
|
.Where(it => input.Ids.Contains(it.Id))
|
|
.ExecuteCommandAsync();
|
|
await _db.Updateable<ObOnboardingTemplate>()
|
|
.SetColumns(it => it.IsDelete == true)
|
|
.Where(it => input.Ids.Contains(it.Id))
|
|
.ExecuteCommandAsync();
|
|
|
|
await _db.Ado.CommitTranAsync();
|
|
}
|
|
catch
|
|
{
|
|
await _db.Ado.RollbackTranAsync();
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public async Task<List<OnboardingTemplateOutput>> ListEnabled(long companyId, long positionId)
|
|
{
|
|
var templateIds = await _db.Queryable<ObOnboardingTemplatePosition>()
|
|
.Where(item => item.PositionId == positionId)
|
|
.Select(item => item.OnboardingTemplateId)
|
|
.ToListAsync();
|
|
var templates = await _db.Queryable<ObOnboardingTemplate>()
|
|
.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<long>();
|
|
input.Contracts = input.Contracts?
|
|
.GroupBy(item => item.ContractTemplateVersionId)
|
|
.Select(group => group.First())
|
|
.ToList() ?? new List<OnboardingTemplateContractInput>();
|
|
input.FormFields = input.FormFields?
|
|
.GroupBy(item => item.FieldId)
|
|
.Select(group => group.First())
|
|
.ToList() ?? new List<OnboardingTemplateFormFieldInput>();
|
|
input.Materials = input.Materials?
|
|
.GroupBy(item => item.MaterialTypeId)
|
|
.Select(group => group.First())
|
|
.ToList() ?? new List<OnboardingTemplateMaterialInput>();
|
|
|
|
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<ObCompany>()
|
|
.AnyAsync(item => item.Id == input.CompanyId && item.Status == "enable");
|
|
if (!companyEnabled)
|
|
throw Oops.Bah("所属公司不存在或未启用");
|
|
|
|
var positionCount = await _db.Queryable<ObPosition>()
|
|
.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<ObContractTemplateVersion>()
|
|
.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<ObTemplateVariable>()
|
|
.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<ObFormField>()
|
|
: await _db.Queryable<ObFormField>()
|
|
.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<ObTemplateVariable>()
|
|
.Where(item => versionIds.Contains(item.TemplateVersionId)
|
|
&& item.VariableType == ContractVariableType.Employee)
|
|
.ToListAsync();
|
|
var availableFields = new HashSet<string>(
|
|
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<ObMaterialType>()
|
|
.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<ObOnboardingTemplatePosition>()
|
|
.Where(item => item.OnboardingTemplateId == templateId)
|
|
.ExecuteCommandAsync();
|
|
await _db.Deleteable<ObOnboardingTemplateContract>()
|
|
.Where(item => item.OnboardingTemplateId == templateId)
|
|
.ExecuteCommandAsync();
|
|
await _db.Deleteable<ObOnboardingTemplateFormField>()
|
|
.Where(item => item.OnboardingTemplateId == templateId)
|
|
.ExecuteCommandAsync();
|
|
await _db.Deleteable<ObOnboardingTemplateMaterial>()
|
|
.Where(item => item.OnboardingTemplateId == templateId)
|
|
.ExecuteCommandAsync();
|
|
}
|
|
|
|
private async Task<List<OnboardingTemplateOutput>> BuildOutputs(List<ObOnboardingTemplate> templates)
|
|
{
|
|
if (templates.Count == 0)
|
|
return new List<OnboardingTemplateOutput>();
|
|
|
|
var templateIds = templates.Select(item => item.Id).ToList();
|
|
var companyIds = templates.Select(item => item.CompanyId).Distinct().ToList();
|
|
var companies = await _db.Queryable<ObCompany>()
|
|
.Where(item => companyIds.Contains(item.Id))
|
|
.ToListAsync();
|
|
var relations = await _db.Queryable<ObOnboardingTemplatePosition>()
|
|
.Where(item => templateIds.Contains(item.OnboardingTemplateId))
|
|
.ToListAsync();
|
|
var positionIds = relations.Select(item => item.PositionId).Distinct().ToList();
|
|
var positions = await _db.Queryable<ObPosition>()
|
|
.Where(item => positionIds.Contains(item.Id))
|
|
.ToListAsync();
|
|
var contracts = await _db.Queryable<ObOnboardingTemplateContract>()
|
|
.Where(item => templateIds.Contains(item.OnboardingTemplateId))
|
|
.ToListAsync();
|
|
var fields = await _db.Queryable<ObOnboardingTemplateFormField>()
|
|
.Where(item => templateIds.Contains(item.OnboardingTemplateId))
|
|
.ToListAsync();
|
|
var materials = await _db.Queryable<ObOnboardingTemplateMaterial>()
|
|
.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();
|
|
}
|
|
} |