Files
OnboardXAdmin/SimpleAdmin.Application/Services/Onboarding/OnboardingTemplate/OnboardingTemplateService.cs
T
2026-08-25 10:20:53 +08:00

431 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>()
.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>().In(input.Ids).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();
}
}