Files
OnboardXAdmin/SimpleAdmin.Application/Services/Onboarding/OnboardingTemplate/OnboardingTemplateService.cs
T
2026-09-10 16:43:11 +08:00

477 lines
21 KiB
C#

namespace SimpleAdmin.Application;
/// <summary>
/// 实现入职模板聚合配置及公司岗位匹配能力。
/// </summary>
public class OnboardingTemplateService : IOnboardingTemplateService
{
private readonly SqlSugarScope _db;
private readonly IOnboardingDataPermissionService _dataPermission;
public OnboardingTemplateService(IOnboardingDataPermissionService dataPermission)
{
_db = DbContext.DB;
_dataPermission = dataPermission;
}
/// <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(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<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,
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<ObOnboardingTemplateContract>()
.Where(item => item.OnboardingTemplateId == template.Id)
.OrderBy(item => item.SortCode)
.ToListAsync(),
FormFields = new List<ObOnboardingTemplateFormField>(),
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 codeOwner = await _db.Queryable<ObOnboardingTemplate>()
.ClearFilter()
.FirstAsync(item => item.TemplateCode == input.TemplateCode
&& (!input.Id.HasValue || item.Id != input.Id.Value));
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<ObOnboardingTemplate>().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<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 templates = await _db.Queryable<ObOnboardingTemplate>()
.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<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.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 departmentId,
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.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<long>();
input.Contracts ??= new List<OnboardingTemplateContractInput>();
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<OnboardingTemplateFormFieldInput>();
input.Materials = input.Materials?
.GroupBy(item => item.MaterialTypeId)
.Select(group => group.First())
.ToList() ?? new List<OnboardingTemplateMaterialInput>();
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<ObCompany>()
.AnyAsync(item => item.Id == input.CompanyId && item.Status == "enable");
if (!companyEnabled)
throw Oops.Bah("所属公司不存在或未启用");
var departmentEnabled = await _db.Queryable<ObDepartment>()
.AnyAsync(item => item.Id == input.DepartmentId
&& item.CompanyId == input.CompanyId
&& item.Status == "enable");
if (!departmentEnabled)
throw Oops.Bah("所属部门不存在、未启用或不属于该公司");
var positionCount = await _db.Queryable<ObPosition>()
.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<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("合同模板与版本不匹配");
}
var contractTemplateIds = input.Contracts
.Select(item => item.ContractTemplateId)
.Distinct()
.ToList();
var contractTemplates = await _db.Queryable<ObContractTemplate>()
.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<ObContractTemplatePosition>()
.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("启用入职模板前,请先启用全部合同模板");
if (versions.Any(item => item.VersionStatus != ContractTemplateVersionStatus.Enabled))
throw Oops.Bah("启用入职模板前,请先启用全部合同版本");
}
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 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<ObOnboardingTemplatePosition>()
.Where(item => item.OnboardingTemplateId == templateId)
.ExecuteCommandAsync();
await _db.Deleteable<ObOnboardingTemplateContract>()
.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 departmentIds = templates
.Where(item => item.DepartmentId.HasValue)
.Select(item => item.DepartmentId.Value)
.Distinct()
.ToList();
var companies = await _db.Queryable<ObCompany>()
.Where(item => companyIds.Contains(item.Id))
.ToListAsync();
var departments = departmentIds.Count == 0
? new List<ObDepartment>()
: await _db.Queryable<ObDepartment>()
.Where(item => departmentIds.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 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,
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();
}
}