322 lines
13 KiB
C#
322 lines
13 KiB
C#
namespace SimpleAdmin.Application;
|
|
|
|
/// <summary>
|
|
/// 合同模板主档案;具体 DOCX 文件、变量与适用范围均挂在此档案下。
|
|
/// </summary>
|
|
public class OnboardingContractTemplateService : DbRepository<ObContractTemplate>, IOnboardingContractTemplateService
|
|
{
|
|
private readonly IFileService _fileService;
|
|
|
|
public OnboardingContractTemplateService(IFileService fileService)
|
|
{
|
|
_fileService = fileService;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 分页查询合同模板主档案。
|
|
/// </summary>
|
|
public async Task<SqlSugarPagedList<ObContractTemplate>> Page(OnboardingContractTemplatePageInput input)
|
|
{
|
|
return await Context.Queryable<ObContractTemplate>()
|
|
.WhereIF(
|
|
!string.IsNullOrWhiteSpace(input.TemplateName),
|
|
it => it.TemplateName.Contains(input.TemplateName) || it.TemplateCode.Contains(input.TemplateName))
|
|
.WhereIF(!string.IsNullOrWhiteSpace(input.ContractType), it => it.ContractType == input.ContractType)
|
|
.WhereIF(!string.IsNullOrWhiteSpace(input.Status), it => it.Status == input.Status)
|
|
.OrderByIF(!string.IsNullOrWhiteSpace(input.SortField), $"{input.SortField} {input.SortOrder}")
|
|
.OrderBy(it => it.TemplateName)
|
|
.ToPagedListAsync(input.PageNum, input.PageSize);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 查询合同模板详情。
|
|
/// </summary>
|
|
public async Task<ObContractTemplate> Detail(BaseIdInput input)
|
|
{
|
|
return await GetByIdAsync(input.Id) ?? throw Oops.Bah("合同模板不存在");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 新增合同模板主档案。
|
|
/// </summary>
|
|
public async Task Add(OnboardingContractTemplateAddInput input)
|
|
{
|
|
await EnsureCodeUnique(input.TemplateCode, 0);
|
|
await InsertAsync(input.Adapt<ObContractTemplate>());
|
|
}
|
|
|
|
/// <summary>
|
|
/// 编辑合同模板主档案。
|
|
/// </summary>
|
|
public async Task Edit(OnboardingContractTemplateEditInput input)
|
|
{
|
|
await Detail(new BaseIdInput { Id = input.Id });
|
|
await EnsureCodeUnique(input.TemplateCode, input.Id);
|
|
await UpdateAsync(input.Adapt<ObContractTemplate>());
|
|
}
|
|
|
|
/// <summary>
|
|
/// 删除未被入职模板或任务合同引用的模板(级联删除未使用的版本和变量)。
|
|
/// </summary>
|
|
public async Task Delete(BaseIdListInput input)
|
|
{
|
|
if (input.Ids.Count == 0)
|
|
return;
|
|
|
|
var hasOnboardingTemplates = await Context.Queryable<ObOnboardingTemplateContract>()
|
|
.AnyAsync(it => input.Ids.Contains(it.ContractTemplateId));
|
|
if (hasOnboardingTemplates)
|
|
throw Oops.Bah("模板已被入职模板引用,不能删除,请先在入职模板中移除关联");
|
|
|
|
var hasContracts = await Context.Queryable<ObTaskContract>()
|
|
.AnyAsync(it => input.Ids.Contains(it.TemplateId));
|
|
if (hasContracts)
|
|
throw Oops.Bah("模板已生成入职任务合同,不能删除,可改为停用");
|
|
|
|
var versionIds = await Context.Queryable<ObContractTemplateVersion>()
|
|
.Where(it => input.Ids.Contains(it.TemplateId))
|
|
.Select(it => it.Id)
|
|
.ToListAsync();
|
|
|
|
await Context.Ado.BeginTranAsync();
|
|
try
|
|
{
|
|
if (versionIds.Count > 0)
|
|
{
|
|
await Context.Deleteable<ObTemplateVariable>()
|
|
.Where(it => versionIds.Contains(it.TemplateVersionId))
|
|
.ExecuteCommandAsync();
|
|
await Context.Deleteable<ObContractTemplateVersion>()
|
|
.In(versionIds)
|
|
.ExecuteCommandAsync();
|
|
}
|
|
|
|
await Context.Deleteable<ObContractScope>()
|
|
.Where(it => input.Ids.Contains(it.TemplateId))
|
|
.ExecuteCommandAsync();
|
|
|
|
await DeleteByIdsAsync(input.Ids.Cast<object>().ToArray());
|
|
await Context.Ado.CommitTranAsync();
|
|
}
|
|
catch
|
|
{
|
|
await Context.Ado.RollbackTranAsync();
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 删除未被入职模板或任务使用的合同版本。
|
|
/// </summary>
|
|
public async Task DeleteVersion(BaseIdInput input)
|
|
{
|
|
var version = await Context.Queryable<ObContractTemplateVersion>().InSingleAsync(input.Id)
|
|
?? throw Oops.Bah("合同版本不存在");
|
|
|
|
var isUsedInOnboardingTemplate = await Context.Queryable<ObOnboardingTemplateContract>()
|
|
.AnyAsync(it => it.ContractTemplateVersionId == version.Id);
|
|
if (isUsedInOnboardingTemplate)
|
|
throw Oops.Bah("该版本已被入职模板引用,不能删除,请先在入职模板中移除关联");
|
|
|
|
var isUsedInTask = await Context.Queryable<ObTaskContract>()
|
|
.AnyAsync(it => it.TemplateVersionId == version.Id);
|
|
if (isUsedInTask)
|
|
throw Oops.Bah("该版本已用于实际入职任务,不能删除,可改为停用");
|
|
|
|
await Context.Ado.BeginTranAsync();
|
|
try
|
|
{
|
|
await Context.Deleteable<ObTemplateVariable>()
|
|
.Where(it => it.TemplateVersionId == version.Id)
|
|
.ExecuteCommandAsync();
|
|
await Context.Deleteable<ObContractScope>()
|
|
.Where(it => it.TemplateVersionId == version.Id)
|
|
.ExecuteCommandAsync();
|
|
await Context.Deleteable<ObContractTemplateVersion>()
|
|
.In(version.Id)
|
|
.ExecuteCommandAsync();
|
|
await Context.Ado.CommitTranAsync();
|
|
}
|
|
catch
|
|
{
|
|
await Context.Ado.RollbackTranAsync();
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 分页查询指定合同模板的版本。
|
|
/// </summary>
|
|
public async Task<SqlSugarPagedList<ObContractTemplateVersion>> VersionPage(OnboardingContractVersionPageInput input)
|
|
{
|
|
return await Context.Queryable<ObContractTemplateVersion>()
|
|
.Where(it => it.TemplateId == input.TemplateId)
|
|
.WhereIF(!string.IsNullOrWhiteSpace(input.VersionStatus), it => it.VersionStatus == input.VersionStatus)
|
|
.OrderByDescending(it => it.CreateTime)
|
|
.ToPagedListAsync(input.PageNum, input.PageSize);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 新增合同模板 DOCX 版本。
|
|
/// </summary>
|
|
public async Task AddVersion(OnboardingContractVersionAddInput input)
|
|
{
|
|
await Detail(new BaseIdInput { Id = input.TemplateId });
|
|
var file = await Context.Queryable<SysFile>().InSingleAsync(input.SourceDocxFileId)
|
|
?? throw Oops.Bah("DOCX 源文件不存在");
|
|
if (!string.Equals(file.Suffix, "docx", StringComparison.OrdinalIgnoreCase))
|
|
throw Oops.Bah("合同模板仅支持 DOCX 文件");
|
|
|
|
var versionExists = await Context.Queryable<ObContractTemplateVersion>()
|
|
.AnyAsync(item => item.TemplateId == input.TemplateId && item.VersionNo == input.VersionNo);
|
|
if (versionExists)
|
|
throw Oops.Bah("该模板版本号已存在");
|
|
|
|
var sourceResult = await _fileService.Download(new BaseIdInput { Id = file.Id })
|
|
?? throw Oops.Bah("无法读取 DOCX 源文件");
|
|
await using var sourceStream = sourceResult.FileStream;
|
|
using var sourceBytes = new MemoryStream();
|
|
await sourceStream.CopyToAsync(sourceBytes);
|
|
var bytes = sourceBytes.ToArray();
|
|
var variableCodes = DocxTemplateProcessor.ExtractVariableCodes(bytes);
|
|
var versionId = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId();
|
|
var version = input.Adapt<ObContractTemplateVersion>();
|
|
version.Id = versionId;
|
|
version.VersionStatus = ContractTemplateVersionStatus.Draft;
|
|
version.FileHash = string.IsNullOrWhiteSpace(input.FileHash)
|
|
? Convert.ToHexString(global::System.Security.Cryptography.SHA256.HashData(bytes))
|
|
: input.FileHash;
|
|
var variables = variableCodes.Select((code, index) => BuildDetectedVariable(versionId, code, index)).ToList();
|
|
|
|
await Context.Ado.BeginTranAsync();
|
|
try
|
|
{
|
|
await Context.Insertable(version).ExecuteCommandAsync();
|
|
if (variables.Count > 0)
|
|
await Context.Insertable(variables).ExecuteCommandAsync();
|
|
await Context.Ado.CommitTranAsync();
|
|
}
|
|
catch
|
|
{
|
|
await Context.Ado.RollbackTranAsync();
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 启用或停用合同模板版本。
|
|
/// </summary>
|
|
public async Task SetVersionStatus(OnboardingContractVersionStatusInput input)
|
|
{
|
|
if (input.VersionStatus != ContractTemplateVersionStatus.Enabled
|
|
&& input.VersionStatus != ContractTemplateVersionStatus.Disabled)
|
|
{
|
|
throw Oops.Bah("版本状态仅支持启用或停用");
|
|
}
|
|
|
|
var version = await Context.Queryable<ObContractTemplateVersion>().InSingleAsync(input.Id)
|
|
?? throw Oops.Bah("模板版本不存在");
|
|
|
|
if (input.VersionStatus == ContractTemplateVersionStatus.Enabled)
|
|
{
|
|
var hasUnresolvedVariables = await Context.Queryable<ObTemplateVariable>()
|
|
.AnyAsync(item => item.TemplateVersionId == input.Id
|
|
&& item.VariableType == ContractVariableType.Unresolved);
|
|
if (hasUnresolvedVariables)
|
|
throw Oops.Bah("仍有未识别来源的合同变量,请逐项确认后再启用");
|
|
}
|
|
|
|
version.VersionStatus = input.VersionStatus;
|
|
await Context.Updateable(version).ExecuteCommandAsync();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 分页查询指定模板版本的变量定义。
|
|
/// </summary>
|
|
public async Task<SqlSugarPagedList<ObTemplateVariable>> VariablePage(OnboardingTemplateVariablePageInput input)
|
|
{
|
|
return await Context.Queryable<ObTemplateVariable>()
|
|
.Where(it => it.TemplateVersionId == input.TemplateVersionId)
|
|
.OrderBy(it => it.SortCode)
|
|
.OrderBy(it => it.VariableCode)
|
|
.ToPagedListAsync(input.PageNum, input.PageSize);
|
|
}
|
|
/// <summary>
|
|
/// 编辑模板版本中自动识别出的变量,不允许修改 DOCX 占位符编码。
|
|
/// </summary>
|
|
public async Task EditVariable(OnboardingTemplateVariableEditInput input)
|
|
{
|
|
var variable = await Context.Queryable<ObTemplateVariable>().InSingleAsync(input.Id)
|
|
?? throw Oops.Bah("模板变量不存在");
|
|
if (variable.TemplateVersionId != input.TemplateVersionId)
|
|
throw Oops.Bah("不允许变更变量所属版本");
|
|
if (!string.Equals(variable.VariableCode, input.VariableCode, StringComparison.Ordinal))
|
|
throw Oops.Bah("DOCX 占位符编码由系统识别,不能修改");
|
|
|
|
var isUsedInTask = await Context.Queryable<ObTaskContract>()
|
|
.AnyAsync(it => it.TemplateVersionId == input.TemplateVersionId);
|
|
if (isUsedInTask)
|
|
throw Oops.Bah("该版本已生成实际入职任务合同,变量配置已锁定");
|
|
|
|
var entity = input.Adapt<ObTemplateVariable>();
|
|
entity.VariableCode = variable.VariableCode;
|
|
await Context.Updateable(entity).ExecuteCommandAsync();
|
|
}
|
|
|
|
private static ObTemplateVariable BuildDetectedVariable(long versionId, string code, int index)
|
|
{
|
|
var separatorIndex = code.IndexOf('.');
|
|
var prefix = separatorIndex > 0 ? code[..separatorIndex].ToLowerInvariant() : string.Empty;
|
|
var sourceField = separatorIndex > 0 ? code[(separatorIndex + 1)..] : code;
|
|
var variableType = prefix switch
|
|
{
|
|
"employee" => ContractVariableType.Employee,
|
|
"company" => ContractVariableType.Company,
|
|
"hr" => ContractVariableType.Contract,
|
|
"system" => ContractVariableType.System,
|
|
_ => ContractVariableType.Unresolved
|
|
};
|
|
|
|
return new ObTemplateVariable
|
|
{
|
|
Id = global::SimpleAdmin.Core.Utils.CommonUtils.GetSingleId(),
|
|
TemplateVersionId = versionId,
|
|
VariableCode = code,
|
|
VariableName = sourceField,
|
|
VariableType = variableType,
|
|
DataType = InferDataType(sourceField),
|
|
EmployeeField = variableType is ContractVariableType.Employee or ContractVariableType.Company
|
|
? sourceField
|
|
: null,
|
|
Required = true,
|
|
HrEditable = variableType == ContractVariableType.Contract,
|
|
SortCode = (index + 1) * 10,
|
|
Remark = variableType == ContractVariableType.Unresolved ? "导入时未识别变量来源,请管理员确认" : null
|
|
};
|
|
}
|
|
|
|
private static string InferDataType(string sourceField)
|
|
{
|
|
var normalized = sourceField.ToLowerInvariant();
|
|
if (normalized.Contains("date") || normalized.EndsWith("at"))
|
|
return "date";
|
|
if (normalized.Contains("salary")
|
|
|| normalized.Contains("amount")
|
|
|| normalized.Contains("wage")
|
|
|| normalized.Contains("copies"))
|
|
{
|
|
return "decimal";
|
|
}
|
|
|
|
return "string";
|
|
}
|
|
|
|
private async Task EnsureCodeUnique(string code, long id)
|
|
{
|
|
var exists = await Context.Queryable<ObContractTemplate>()
|
|
.AnyAsync(it => it.TemplateCode == code && it.Id != id);
|
|
if (exists)
|
|
throw Oops.Bah("模板编码已存在");
|
|
}
|
|
}
|