电子签基础功能
This commit is contained in:
+170
-50
@@ -15,10 +15,62 @@ public class OnboardingContractDocumentService : IOnboardingContractDocumentServ
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在任务创建前生成单份临时 DOCX,仅通过响应返回,不写文件表和 OSS。
|
||||
/// 在任务创建前生成指定合同的临时 DOCX,仅通过响应返回,不写文件表和 OSS。
|
||||
/// </summary>
|
||||
public async Task<OnboardingContractPreviewOutput> PreviewBeforeCreate(
|
||||
OnboardingContractPreviewInput input)
|
||||
{
|
||||
var context = await LoadPreviewContext(input);
|
||||
var contractConfig = input.ContractTemplateVersionId.HasValue
|
||||
? context.ContractConfigs.FirstOrDefault(item =>
|
||||
item.ContractTemplateVersionId == input.ContractTemplateVersionId.Value)
|
||||
: context.ContractConfigs.FirstOrDefault();
|
||||
if (contractConfig == null)
|
||||
throw Oops.Bah("所选合同不属于当前入职模板或已被移除");
|
||||
|
||||
return await RenderPreview(input, context, contractConfig, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在任务创建前生成全部合同预览,并按模板顺序打包为 ZIP。
|
||||
/// </summary>
|
||||
public async Task<OnboardingContractPreviewOutput> PreviewAllBeforeCreate(
|
||||
OnboardingContractPreviewInput input)
|
||||
{
|
||||
var context = await LoadPreviewContext(input);
|
||||
using var zipBytes = new MemoryStream();
|
||||
using (var archive = new global::System.IO.Compression.ZipArchive(
|
||||
zipBytes,
|
||||
global::System.IO.Compression.ZipArchiveMode.Create,
|
||||
true))
|
||||
{
|
||||
for (var index = 0; index < context.ContractConfigs.Count; index++)
|
||||
{
|
||||
var preview = await RenderPreview(
|
||||
input,
|
||||
context,
|
||||
context.ContractConfigs[index],
|
||||
index + 1);
|
||||
var entry = archive.CreateEntry(
|
||||
preview.FileName,
|
||||
global::System.IO.Compression.CompressionLevel.Fastest);
|
||||
await using var entryStream = entry.Open();
|
||||
await entryStream.WriteAsync(preview.FileBytes);
|
||||
}
|
||||
}
|
||||
|
||||
return new OnboardingContractPreviewOutput
|
||||
{
|
||||
FileBytes = zipBytes.ToArray(),
|
||||
FileName = $"全部合同_人事预览_{DateTime.Now:yyyyMMddHHmmss}.zip"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 校验预览上下文,并读取入职模板锁定的全部合同配置。
|
||||
/// </summary>
|
||||
private async Task<ContractPreviewContext> LoadPreviewContext(
|
||||
OnboardingContractPreviewInput input)
|
||||
{
|
||||
var company = await _db.Queryable<ObCompany>()
|
||||
.FirstAsync(item => item.Id == input.CompanyId
|
||||
@@ -53,32 +105,97 @@ public class OnboardingContractDocumentService : IOnboardingContractDocumentServ
|
||||
if (!supportsPosition)
|
||||
throw Oops.Bah("所选入职模板不适用于该岗位");
|
||||
|
||||
// 当前业务只预览入职模板中排序第一的合同,后续多合同可在此扩展。
|
||||
var contractConfig = await _db.Queryable<ObOnboardingTemplateContract>()
|
||||
var contractConfigs = await _db.Queryable<ObOnboardingTemplateContract>()
|
||||
.Where(item => item.OnboardingTemplateId == onboardingTemplate.Id)
|
||||
.OrderBy(item => item.SortCode)
|
||||
.FirstAsync()
|
||||
?? throw Oops.Bah("入职模板尚未配置合同文件");
|
||||
.OrderBy(item => item.Id)
|
||||
.ToListAsync();
|
||||
if (contractConfigs.Count == 0)
|
||||
throw Oops.Bah("入职模板尚未配置合同文件");
|
||||
|
||||
var contractTemplate = await _db.Queryable<ObContractTemplate>()
|
||||
.FirstAsync(item => item.Id == contractConfig.ContractTemplateId
|
||||
&& item.CompanyId == input.CompanyId
|
||||
&& item.DepartmentId == input.DepartmentId
|
||||
&& item.Status == CommonStatusConst.ENABLE)
|
||||
?? throw Oops.Bah("合同模板不存在、未启用或不属于当前公司、部门");
|
||||
var duplicateVersion = contractConfigs
|
||||
.GroupBy(item => item.ContractTemplateVersionId)
|
||||
.FirstOrDefault(group => group.Count() > 1);
|
||||
if (duplicateVersion != null)
|
||||
throw Oops.Bah("入职模板中存在重复的合同版本,请先调整模板配置");
|
||||
|
||||
var contractSupportsPosition = await _db.Queryable<ObContractTemplatePosition>()
|
||||
.AnyAsync(item => item.ContractTemplateId == contractTemplate.Id
|
||||
&& item.PositionId == input.PositionId);
|
||||
if (!contractSupportsPosition)
|
||||
throw Oops.Bah("合同模板不适用于当前岗位");
|
||||
var templateIds = contractConfigs
|
||||
.Select(item => item.ContractTemplateId)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
var contractTemplates = await _db.Queryable<ObContractTemplate>()
|
||||
.Where(item => templateIds.Contains(item.Id))
|
||||
.ToListAsync();
|
||||
if (contractTemplates.Count != templateIds.Count
|
||||
|| contractTemplates.Any(item => item.CompanyId != input.CompanyId
|
||||
|| item.DepartmentId != input.DepartmentId
|
||||
|| !string.Equals(
|
||||
item.Status,
|
||||
CommonStatusConst.ENABLE,
|
||||
global::System.StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
throw Oops.Bah("入职模板包含未启用或不属于当前公司、部门的合同模板");
|
||||
}
|
||||
|
||||
var version = await _db.Queryable<ObContractTemplateVersion>()
|
||||
.FirstAsync(item => item.Id == contractConfig.ContractTemplateVersionId
|
||||
&& item.TemplateId == contractTemplate.Id
|
||||
&& item.VersionStatus == ContractTemplateVersionStatus.Enabled)
|
||||
?? throw Oops.Bah("合同版本不存在、未启用或不属于当前合同模板");
|
||||
var supportedTemplateCount = await _db.Queryable<ObContractTemplatePosition>()
|
||||
.CountAsync(item => templateIds.Contains(item.ContractTemplateId)
|
||||
&& item.PositionId == input.PositionId);
|
||||
if (supportedTemplateCount != templateIds.Count)
|
||||
throw Oops.Bah("入职模板包含不适用于当前岗位的合同模板");
|
||||
|
||||
var versionIds = contractConfigs
|
||||
.Select(item => item.ContractTemplateVersionId)
|
||||
.ToList();
|
||||
var versions = await _db.Queryable<ObContractTemplateVersion>()
|
||||
.Where(item => versionIds.Contains(item.Id))
|
||||
.ToListAsync();
|
||||
var versionsValid = versions.Count == versionIds.Count
|
||||
&& contractConfigs.All(config => versions.Any(version =>
|
||||
version.Id == config.ContractTemplateVersionId
|
||||
&& version.TemplateId == config.ContractTemplateId
|
||||
&& version.VersionStatus == ContractTemplateVersionStatus.Enabled));
|
||||
if (!versionsValid)
|
||||
throw Oops.Bah("入职模板包含不存在、未启用或归属错误的合同版本");
|
||||
|
||||
var inputs = input.ContractVariables
|
||||
?? new List<OnboardingInitialContractVariableInput>();
|
||||
OnboardingTaskService.ValidateInitialContractDateRanges(inputs);
|
||||
var duplicateInput = inputs
|
||||
.GroupBy(item => new
|
||||
{
|
||||
item.ContractTemplateVersionId,
|
||||
VariableCode = item.VariableCode?.ToUpperInvariant()
|
||||
})
|
||||
.FirstOrDefault(group => group.Count() > 1);
|
||||
if (duplicateInput != null)
|
||||
throw Oops.Bah($"合同变量“{duplicateInput.Key.VariableCode}”重复提交");
|
||||
var unknownVersionInput = inputs.FirstOrDefault(item =>
|
||||
!versionIds.Contains(item.ContractTemplateVersionId));
|
||||
if (unknownVersionInput != null)
|
||||
throw Oops.Bah($"合同变量“{unknownVersionInput.VariableCode}”不属于当前入职模板");
|
||||
|
||||
return new ContractPreviewContext
|
||||
{
|
||||
Company = company,
|
||||
ContractConfigs = contractConfigs,
|
||||
ContractTemplates = contractTemplates,
|
||||
Versions = versions
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据一条合同配置和当前人事参数渲染一份 DOCX。
|
||||
/// </summary>
|
||||
private async Task<OnboardingContractPreviewOutput> RenderPreview(
|
||||
OnboardingContractPreviewInput input,
|
||||
ContractPreviewContext context,
|
||||
ObOnboardingTemplateContract contractConfig,
|
||||
int? fileOrder)
|
||||
{
|
||||
var contractTemplate = context.ContractTemplates.First(item =>
|
||||
item.Id == contractConfig.ContractTemplateId);
|
||||
var version = context.Versions.First(item =>
|
||||
item.Id == contractConfig.ContractTemplateVersionId);
|
||||
var definitions = await _db.Queryable<ObTemplateVariable>()
|
||||
.Where(item => item.TemplateVersionId == version.Id)
|
||||
.OrderBy(item => item.SortCode)
|
||||
@@ -89,31 +206,20 @@ public class OnboardingContractDocumentService : IOnboardingContractDocumentServ
|
||||
if (duplicateDefinition != null)
|
||||
throw Oops.Bah($"合同变量“{duplicateDefinition.Key}”存在重复定义");
|
||||
|
||||
var inputs = input.ContractVariables
|
||||
?? new List<OnboardingInitialContractVariableInput>();
|
||||
OnboardingTaskService.ValidateInitialContractDateRanges(inputs);
|
||||
|
||||
var duplicateInput = inputs
|
||||
.GroupBy(item => new
|
||||
{
|
||||
item.ContractTemplateVersionId,
|
||||
VariableCode = item.VariableCode?.ToUpperInvariant()
|
||||
})
|
||||
.FirstOrDefault(group => group.Count() > 1);
|
||||
if (duplicateInput != null)
|
||||
throw Oops.Bah($"合同变量“{duplicateInput.Key.VariableCode}”重复提交");
|
||||
|
||||
var inputs = (input.ContractVariables
|
||||
?? new List<OnboardingInitialContractVariableInput>())
|
||||
.Where(item => item.ContractTemplateVersionId == version.Id)
|
||||
.ToList();
|
||||
var editableDefinitions = definitions
|
||||
.Where(IsHrEditableDefinition)
|
||||
.ToList();
|
||||
foreach (var submitted in inputs)
|
||||
{
|
||||
var definitionExists = submitted.ContractTemplateVersionId == version.Id
|
||||
&& editableDefinitions.Any(definition =>
|
||||
string.Equals(
|
||||
definition.VariableCode,
|
||||
submitted.VariableCode,
|
||||
StringComparison.OrdinalIgnoreCase));
|
||||
var definitionExists = editableDefinitions.Any(definition =>
|
||||
string.Equals(
|
||||
definition.VariableCode,
|
||||
submitted.VariableCode,
|
||||
StringComparison.OrdinalIgnoreCase));
|
||||
if (!definitionExists)
|
||||
throw Oops.Bah($"合同变量“{submitted.VariableCode}”不存在或不允许人事填写");
|
||||
}
|
||||
@@ -124,8 +230,7 @@ public class OnboardingContractDocumentService : IOnboardingContractDocumentServ
|
||||
return false;
|
||||
|
||||
var submitted = inputs.FirstOrDefault(item =>
|
||||
item.ContractTemplateVersionId == version.Id
|
||||
&& string.Equals(
|
||||
string.Equals(
|
||||
item.VariableCode,
|
||||
definition.VariableCode,
|
||||
StringComparison.OrdinalIgnoreCase));
|
||||
@@ -150,8 +255,7 @@ public class OnboardingContractDocumentService : IOnboardingContractDocumentServ
|
||||
definition =>
|
||||
{
|
||||
var submitted = inputs.FirstOrDefault(item =>
|
||||
item.ContractTemplateVersionId == version.Id
|
||||
&& string.Equals(
|
||||
string.Equals(
|
||||
item.VariableCode,
|
||||
definition.VariableCode,
|
||||
StringComparison.OrdinalIgnoreCase));
|
||||
@@ -162,7 +266,7 @@ public class OnboardingContractDocumentService : IOnboardingContractDocumentServ
|
||||
definition,
|
||||
null,
|
||||
emptyDynamicValues,
|
||||
company,
|
||||
context.Company,
|
||||
previewTask,
|
||||
true)
|
||||
?? string.Empty;
|
||||
@@ -181,7 +285,7 @@ public class OnboardingContractDocumentService : IOnboardingContractDocumentServ
|
||||
FileBytes = DocxTemplateProcessor.Render(
|
||||
sourceBytes.ToArray(),
|
||||
values),
|
||||
FileName = BuildPreviewFileName(contractTemplate.TemplateName)
|
||||
FileName = BuildPreviewFileName(contractTemplate.TemplateName, fileOrder)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -197,7 +301,8 @@ public class OnboardingContractDocumentService : IOnboardingContractDocumentServ
|
||||
var statusAllowsGeneration = task.TaskStatus == OnboardingTaskStatus.PendingFill
|
||||
|| task.TaskStatus == OnboardingTaskStatus.Rejected
|
||||
|| task.TaskStatus == OnboardingTaskStatus.Reviewing
|
||||
|| task.TaskStatus == OnboardingTaskStatus.Approved;
|
||||
|| task.TaskStatus == OnboardingTaskStatus.Approved
|
||||
|| task.TaskStatus == OnboardingTaskStatus.Archived;
|
||||
if (!statusAllowsGeneration)
|
||||
throw Oops.Bah("当前任务状态不能生成合同");
|
||||
if (contract.GeneratedDocxFileId.HasValue)
|
||||
@@ -275,12 +380,27 @@ public class OnboardingContractDocumentService : IOnboardingContractDocumentServ
|
||||
/// <summary>
|
||||
/// 生成兼容 Windows 和 Linux 的合同预览下载文件名。
|
||||
/// </summary>
|
||||
private static string BuildPreviewFileName(string contractName)
|
||||
private static string BuildPreviewFileName(string contractName, int? fileOrder = null)
|
||||
{
|
||||
var invalidChars = global::System.IO.Path.GetInvalidFileNameChars();
|
||||
var safeName = new string((contractName ?? "合同")
|
||||
.Select(character => invalidChars.Contains(character) ? '_' : character)
|
||||
.ToArray());
|
||||
return $"{safeName}_人事预览_{DateTime.Now:yyyyMMddHHmmss}.docx";
|
||||
var orderPrefix = fileOrder.HasValue ? $"{fileOrder.Value:D2}_" : string.Empty;
|
||||
return $"{orderPrefix}{safeName}_人事预览_{DateTime.Now:yyyyMMddHHmmss}.docx";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建任务前预览合同所需的已校验数据。
|
||||
/// </summary>
|
||||
private sealed class ContractPreviewContext
|
||||
{
|
||||
public ObCompany Company { get; init; }
|
||||
|
||||
public List<ObOnboardingTemplateContract> ContractConfigs { get; init; }
|
||||
|
||||
public List<ObContractTemplate> ContractTemplates { get; init; }
|
||||
|
||||
public List<ObContractTemplateVersion> Versions { get; init; }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user