484 lines
21 KiB
C#
484 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 codeOwnerQuery = _db.Queryable<ObOnboardingTemplate>()
|
|
.ClearFilter()
|
|
.Where(item => item.TemplateCode == input.TemplateCode);
|
|
if (input.Id.HasValue)
|
|
{
|
|
var currentTemplateId = input.Id.Value;
|
|
codeOwnerQuery = codeOwnerQuery.Where(item => item.Id != currentTemplateId);
|
|
}
|
|
|
|
var codeOwner = await codeOwnerQuery.FirstAsync();
|
|
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 now = DateTime.Now;
|
|
if (versions.Any(version => !ContractTemplateVersionValidity.IsAvailable(version, now)))
|
|
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("启用入职模板前,请先启用全部合同模板");
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|