逻辑调整

This commit is contained in:
fuenmao
2026-08-25 10:20:53 +08:00
parent 4679bd6c13
commit 085bf5e2ab
36 changed files with 2770 additions and 1209 deletions
@@ -5,6 +5,12 @@ namespace SimpleAdmin.Application;
/// </summary>
public class OnboardingContractTemplateService : DbRepository<ObContractTemplate>, IOnboardingContractTemplateService
{
private readonly IFileService _fileService;
public OnboardingContractTemplateService(IFileService fileService)
{
_fileService = fileService;
}
/// <summary>
/// 分页查询合同模板主档案。
/// </summary>
@@ -64,7 +70,12 @@ public class OnboardingContractTemplateService : DbRepository<ObContractTemplate
var hasScopes = await Context.Queryable<ObContractScope>()
.AnyAsync(it => input.Ids.Contains(it.TemplateId));
if (hasScopes)
throw Oops.Bah("模板已配置适用范围,不能删除");
throw Oops.Bah("模板已配置旧版适用范围,不能删除");
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));
@@ -92,18 +103,45 @@ public class OnboardingContractTemplateService : DbRepository<ObContractTemplate
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源文件不存在");
?? throw Oops.Bah("DOCX 源文件不存在");
if (!string.Equals(file.Suffix, "docx", StringComparison.OrdinalIgnoreCase))
throw Oops.Bah("合同模板仅支持DOCX文件");
throw Oops.Bah("合同模板仅支持 DOCX 文件");
var versionExists = await Context.Queryable<ObContractTemplateVersion>()
.AnyAsync(it => it.TemplateId == input.TemplateId && it.VersionNo == input.VersionNo);
.AnyAsync(item => item.TemplateId == input.TemplateId && item.VersionNo == input.VersionNo);
if (versionExists)
throw Oops.Bah("该模板版本号已存在");
await Context.Insertable(input.Adapt<ObContractTemplateVersion>()).ExecuteCommandAsync();
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>
@@ -122,10 +160,11 @@ public class OnboardingContractTemplateService : DbRepository<ObContractTemplate
if (input.VersionStatus == ContractTemplateVersionStatus.Enabled)
{
var hasVariables = await Context.Queryable<ObTemplateVariable>()
.AnyAsync(it => it.TemplateVersionId == input.Id);
if (!hasVariables)
throw Oops.Bah("请先配置模板变量后再启用");
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;
@@ -143,19 +182,8 @@ public class OnboardingContractTemplateService : DbRepository<ObContractTemplate
.OrderBy(it => it.VariableCode)
.ToPagedListAsync(input.PageNum, input.PageSize);
}
/// <summary>
/// 为草稿模板版本新增变量。
/// </summary>
public async Task AddVariable(OnboardingTemplateVariableAddInput input)
{
await EnsureVersionDraft(input.TemplateVersionId);
await EnsureVariableUnique(input.TemplateVersionId, input.VariableCode, 0);
await Context.Insertable(input.Adapt<ObTemplateVariable>()).ExecuteCommandAsync();
}
/// <summary>
/// 编辑草稿模板版本中的变量。
/// 编辑草稿模板版本中自动识别出的变量,不允许修改 DOCX 占位符编码。
/// </summary>
public async Task EditVariable(OnboardingTemplateVariableEditInput input)
{
@@ -163,77 +191,61 @@ public class OnboardingContractTemplateService : DbRepository<ObContractTemplate
?? throw Oops.Bah("模板变量不存在");
if (variable.TemplateVersionId != input.TemplateVersionId)
throw Oops.Bah("不允许变更变量所属版本");
if (!string.Equals(variable.VariableCode, input.VariableCode, StringComparison.Ordinal))
throw Oops.Bah("DOCX 占位符编码由系统识别,不能修改");
await EnsureVersionDraft(input.TemplateVersionId);
await EnsureVariableUnique(input.TemplateVersionId, input.VariableCode, input.Id);
await Context.Updateable(input.Adapt<ObTemplateVariable>()).ExecuteCommandAsync();
var entity = input.Adapt<ObTemplateVariable>();
entity.VariableCode = variable.VariableCode;
await Context.Updateable(entity).ExecuteCommandAsync();
}
/// <summary>
/// 删除草稿模板版本中的变量。
/// </summary>
public async Task DeleteVariable(BaseIdListInput input)
private static ObTemplateVariable BuildDetectedVariable(long versionId, string code, int index)
{
if (input.Ids.Count == 0)
return;
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
};
var versionIds = await Context.Queryable<ObTemplateVariable>()
.Where(it => input.Ids.Contains(it.Id))
.Select(it => it.TemplateVersionId)
.ToListAsync();
foreach (var versionId in versionIds.Distinct())
await EnsureVersionDraft(versionId);
await Context.Deleteable<ObTemplateVariable>().In(input.Ids).ExecuteCommandAsync();
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
};
}
/// <summary>
/// 分页查询合同适用范围。
/// </summary>
public async Task<SqlSugarPagedList<ObContractScope>> ScopePage(OnboardingContractScopePageInput input)
private static string InferDataType(string sourceField)
{
return await Context.Queryable<ObContractScope>()
.WhereIF(input.CompanyId.HasValue, it => it.CompanyId == input.CompanyId.Value)
.WhereIF(input.PositionId.HasValue, it => it.PositionId == input.PositionId.Value)
.WhereIF(input.TemplateId.HasValue, it => it.TemplateId == input.TemplateId.Value)
.OrderBy(it => it.SortCode)
.ToPagedListAsync(input.PageNum, input.PageSize);
}
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";
}
/// <summary>
/// 新增公司或岗位的合同适用范围。
/// </summary>
public async Task AddScope(OnboardingContractScopeAddInput input)
{
await EnsureScopeValid(input.CompanyId, input.PositionId, input.TemplateId, input.TemplateVersionId);
await EnsureScopeUnique(input.CompanyId, input.PositionId, input.TemplateVersionId, 0);
await Context.Insertable(input.Adapt<ObContractScope>()).ExecuteCommandAsync();
}
/// <summary>
/// 编辑合同适用范围。
/// </summary>
public async Task EditScope(OnboardingContractScopeEditInput input)
{
var scopeExists = await Context.Queryable<ObContractScope>().AnyAsync(it => it.Id == input.Id);
if (!scopeExists)
throw Oops.Bah("合同适用范围不存在");
await EnsureScopeValid(input.CompanyId, input.PositionId, input.TemplateId, input.TemplateVersionId);
await EnsureScopeUnique(input.CompanyId, input.PositionId, input.TemplateVersionId, input.Id);
await Context.Updateable(input.Adapt<ObContractScope>()).ExecuteCommandAsync();
}
/// <summary>
/// 删除合同适用范围。
/// </summary>
public async Task DeleteScope(BaseIdListInput input)
{
if (input.Ids.Count == 0)
return;
await Context.Deleteable<ObContractScope>().In(input.Ids).ExecuteCommandAsync();
return "string";
}
private async Task EnsureCodeUnique(string code, long id)
@@ -251,44 +263,4 @@ public class OnboardingContractTemplateService : DbRepository<ObContractTemplate
if (version.VersionStatus != ContractTemplateVersionStatus.Draft)
throw Oops.Bah("只有草稿版本可维护变量");
}
private async Task EnsureVariableUnique(long versionId, string code, long id)
{
var exists = await Context.Queryable<ObTemplateVariable>()
.AnyAsync(it => it.TemplateVersionId == versionId && it.VariableCode == code && it.Id != id);
if (exists)
throw Oops.Bah("该版本变量编码已存在");
}
private async Task EnsureScopeValid(long companyId, long? positionId, long templateId, long versionId)
{
var companyExists = await Context.Queryable<ObCompany>()
.AnyAsync(it => it.Id == companyId && it.Status == "enable");
if (!companyExists)
throw Oops.Bah("所属公司不存在或未启用");
if (positionId.HasValue)
{
var positionExists = await Context.Queryable<ObPosition>()
.AnyAsync(it => it.Id == positionId.Value && it.CompanyId == companyId && it.Status == "enable");
if (!positionExists)
throw Oops.Bah("岗位不存在、未启用或不属于该公司");
}
var version = await Context.Queryable<ObContractTemplateVersion>().InSingleAsync(versionId)
?? throw Oops.Bah("合同版本不存在");
if (version.TemplateId != templateId || version.VersionStatus != ContractTemplateVersionStatus.Enabled)
throw Oops.Bah("只能配置该模板的已启用合同版本");
}
private async Task EnsureScopeUnique(long companyId, long? positionId, long versionId, long id)
{
var exists = await Context.Queryable<ObContractScope>()
.AnyAsync(it => it.CompanyId == companyId
&& it.PositionId == positionId
&& it.TemplateVersionId == versionId
&& it.Id != id);
if (exists)
throw Oops.Bah("该公司、岗位和合同版本的适用范围已存在");
}
}