This commit is contained in:
fuenmao
2026-08-26 18:17:58 +08:00
parent db3d58bc99
commit 9741481a62
8 changed files with 117 additions and 14 deletions
+3 -2
View File
@@ -8,6 +8,7 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<DocumentationFile>$(MSBuildProjectName).xml</DocumentationFile> <!-- 由 SDK 按目标框架输出 XML,避免 net6/net7/net8 并行编译时共同写入项目根目录的同名文件。 -->
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup> </PropertyGroup>
</Project> </Project>
@@ -61,18 +61,20 @@ public class OnboardingContractDocumentService : IOnboardingContractDocumentServ
item => item.VariableCode, item => item.VariableCode,
item => item.VariableValue ?? string.Empty, item => item.VariableValue ?? string.Empty,
StringComparer.OrdinalIgnoreCase)); StringComparer.OrdinalIgnoreCase));
var name = $"{task.TaskNo}_{contract.ContractName}.docx"; var generatedAt = DateTime.Now;
// 在文件名中加入生成批次,避免手机系统把同名的新合同误认为历史下载文件。
var name = $"{task.TaskNo}_{contract.ContractName}_{generatedAt:yyyyMMddHHmmssfff}.docx";
var fileId = await _fileService.StoreGeneratedFile( var fileId = await _fileService.StoreGeneratedFile(
SysDictConst.FILE_ENGINE_ALIYUN_OSS, SysDictConst.FILE_ENGINE_ALIYUN_OSS,
name, name,
rendered); rendered);
contract.GeneratedDocxFileId = fileId; contract.GeneratedDocxFileId = fileId;
contract.GeneratedAt = DateTime.Now; contract.GeneratedAt = generatedAt;
contract.ContractStatus = task.TaskStatus == OnboardingTaskStatus.Approved contract.ContractStatus = task.TaskStatus == OnboardingTaskStatus.Approved
? TaskContractStatus.OfflinePendingSign ? TaskContractStatus.OfflinePendingSign
: TaskContractStatus.PendingConfirm; : TaskContractStatus.PendingConfirm;
await _db.Updateable(contract).ExecuteCommandAsync(); await _db.Updateable(contract).ExecuteCommandAsync();
return fileId; return fileId;
} }
} }
@@ -14,7 +14,13 @@ public interface IOnboardingPublicTaskService : ITransient
/// </summary> /// </summary>
Task SaveEmployeeInfo(OnboardingTaskPublicSaveInput input); Task SaveEmployeeInfo(OnboardingTaskPublicSaveInput input);
/// <summary> /// <summary>
/// 校验任务归属后返回合同模板源文件 Id。 /// 校验合同变量并提前生成候选人的合同 DOCX。
/// </summary>
Task<OnboardingPublicContractPrepareOutput> PrepareContract(
OnboardingPublicContractSourceInput input);
/// <summary>
/// 校验任务归属后返回已经准备完成的合同文件 Id。
/// </summary> /// </summary>
Task<long> GetContractSourceFileId(OnboardingPublicContractSourceInput input); Task<long> GetContractSourceFileId(OnboardingPublicContractSourceInput input);
@@ -162,25 +162,71 @@ public class OnboardingPublicTaskService : IOnboardingPublicTaskService
} }
/// <summary> /// <summary>
/// 根据员工当前已填写信息生成合同预览,并返回已替换变量的 DOCX 文件 Id。 /// 根据员工当前已填写信息校验变量并提前生成合同 DOCX。
/// </summary> /// </summary>
public async Task<long> GetContractSourceFileId(OnboardingPublicContractSourceInput input) public async Task<OnboardingPublicContractPrepareOutput> PrepareContract(
OnboardingPublicContractSourceInput input)
{ {
var link = await GetActiveLink(input.Token); var link = await GetActiveLink(input.Token);
var contract = await _db.Queryable<ObTaskContract>() var contract = await _db.Queryable<ObTaskContract>()
.FirstAsync(item => item.Id == input.TaskContractId && item.TaskId == link.TaskId) .FirstAsync(item => item.Id == input.TaskContractId && item.TaskId == link.TaskId)
?? throw Oops.Bah("合同不存在"); ?? throw Oops.Bah("合同不存在");
// 员工资料保存时会清除旧文件 Id;仍有文件 Id 时还需确认文件记录没有被异常删除。
if (contract.GeneratedDocxFileId.HasValue)
{
var generatedFileExists = await _db.Queryable<SysFile>()
.AnyAsync(item => item.Id == contract.GeneratedDocxFileId.Value);
if (!generatedFileExists)
throw Oops.Bah("已生成的合同文件不存在,请联系管理员重新生成");
return new OnboardingPublicContractPrepareOutput
{
FileId = contract.GeneratedDocxFileId.Value,
GeneratedAt = contract.GeneratedAt
};
}
await _snapshotService.Build(link.TaskId); await _snapshotService.Build(link.TaskId);
var fileId = await _contractDocumentService.Generate(new OnboardingGenerateContractInput var fileId = await _contractDocumentService.Generate(new OnboardingGenerateContractInput
{ {
TaskContractId = contract.Id TaskContractId = contract.Id
}); });
var generatedAt = await _db.Queryable<ObTaskContract>()
.Where(item => item.Id == contract.Id)
.Select(item => item.GeneratedAt)
.SingleAsync();
return new OnboardingPublicContractPrepareOutput
{
FileId = fileId,
GeneratedAt = generatedAt
};
}
/// <summary>
/// 返回已经准备完成的合同文件,下载阶段不再执行变量校验和文档生成。
/// </summary>
public async Task<long> GetContractSourceFileId(OnboardingPublicContractSourceInput input)
{
var link = await GetActiveLink(input.Token);
var contract = await _db.Queryable<ObTaskContract>()
.FirstAsync(item => item.Id == input.TaskContractId && item.TaskId == link.TaskId)
?? throw Oops.Bah("合同不存在");
var fileId = contract.GeneratedDocxFileId
?? throw Oops.Bah("合同尚未准备完成,请刷新页面后重试");
var fileExists = await _db.Queryable<SysFile>()
.AnyAsync(item => item.Id == fileId);
if (!fileExists)
throw Oops.Bah("合同文件不存在,请重新准备合同");
contract.ReadAt ??= DateTime.Now; contract.ReadAt ??= DateTime.Now;
if (contract.ContractStatus == TaskContractStatus.PendingGenerate) if (contract.ContractStatus == TaskContractStatus.PendingGenerate)
contract.ContractStatus = TaskContractStatus.PendingConfirm; contract.ContractStatus = TaskContractStatus.PendingConfirm;
await _db.Updateable(contract).ExecuteCommandAsync();
await _db.Updateable(contract)
.UpdateColumns(item => new { item.ReadAt, item.ContractStatus })
.ExecuteCommandAsync();
return fileId; return fileId;
} }
@@ -28,6 +28,27 @@ public class OnboardingPublicContractSourceInput
/// </summary> /// </summary>
[IdNotNull(ErrorMessage = "任务合同Id不能为空")] [IdNotNull(ErrorMessage = "任务合同Id不能为空")]
public long TaskContractId { get; set; } public long TaskContractId { get; set; }
/// <summary>
/// 当前生成文件的版本标识,仅用于区分手机下载缓存,不参与文件权限判断。
/// </summary>
public long? Version { get; set; }
}
/// <summary>
/// 员工端合同准备结果。
/// </summary>
public class OnboardingPublicContractPrepareOutput
{
/// <summary>
/// 当前已经生成并可下载的系统文件 Id,同时作为客户端下载版本号。
/// </summary>
public long FileId { get; set; }
/// <summary>
/// 合同文件生成时间;复用现有文件时返回原生成时间。
/// </summary>
public DateTime? GeneratedAt { get; set; }
} }
/// <summary> /// <summary>
@@ -3182,9 +3182,14 @@
保存候选人固定基础信息并刷新合同变量快照。 保存候选人固定基础信息并刷新合同变量快照。
</summary> </summary>
</member> </member>
<member name="M:SimpleAdmin.Application.IOnboardingPublicTaskService.PrepareContract(SimpleAdmin.Application.OnboardingPublicContractSourceInput)">
<summary>
校验合同变量并提前生成候选人的合同 DOCX。
</summary>
</member>
<member name="M:SimpleAdmin.Application.IOnboardingPublicTaskService.GetContractSourceFileId(SimpleAdmin.Application.OnboardingPublicContractSourceInput)"> <member name="M:SimpleAdmin.Application.IOnboardingPublicTaskService.GetContractSourceFileId(SimpleAdmin.Application.OnboardingPublicContractSourceInput)">
<summary> <summary>
校验任务归属后返回合同模板源文件 Id。 校验任务归属后返回已经准备完成的合同文件 Id。
</summary> </summary>
</member> </member>
<member name="M:SimpleAdmin.Application.IOnboardingPublicTaskService.GetMaterialSource(SimpleAdmin.Application.OnboardingPublicMaterialSourceInput)"> <member name="M:SimpleAdmin.Application.IOnboardingPublicTaskService.GetMaterialSource(SimpleAdmin.Application.OnboardingPublicMaterialSourceInput)">
@@ -3207,9 +3212,14 @@
保存候选人的固定基础信息并刷新尚未确认的合同变量快照。 保存候选人的固定基础信息并刷新尚未确认的合同变量快照。
</summary> </summary>
</member> </member>
<member name="M:SimpleAdmin.Application.OnboardingPublicTaskService.PrepareContract(SimpleAdmin.Application.OnboardingPublicContractSourceInput)">
<summary>
根据员工当前已填写信息校验变量并提前生成合同 DOCX。
</summary>
</member>
<member name="M:SimpleAdmin.Application.OnboardingPublicTaskService.GetContractSourceFileId(SimpleAdmin.Application.OnboardingPublicContractSourceInput)"> <member name="M:SimpleAdmin.Application.OnboardingPublicTaskService.GetContractSourceFileId(SimpleAdmin.Application.OnboardingPublicContractSourceInput)">
<summary> <summary>
根据员工当前已填写信息生成合同预览,并返回已替换变量的 DOCX 文件 Id。 返回已经准备完成的合同文件,下载阶段不再执行变量校验和文档生成。
</summary> </summary>
</member> </member>
<member name="M:SimpleAdmin.Application.OnboardingPublicTaskService.GetMaterialSource(SimpleAdmin.Application.OnboardingPublicMaterialSourceInput)"> <member name="M:SimpleAdmin.Application.OnboardingPublicTaskService.GetMaterialSource(SimpleAdmin.Application.OnboardingPublicMaterialSourceInput)">
@@ -36,13 +36,25 @@ public class OnboardingPublicTaskController
} }
/// <summary> /// <summary>
/// 下载根据当前任务资料动态生成并已替换变量的合同文件。 /// 校验合同变量并提前生成候选人的合同文件。
/// </summary>
[HttpPost("prepare-contract")]
public async Task<OnboardingPublicContractPrepareOutput> PrepareContract(
[FromBody] OnboardingPublicContractSourceInput input)
{
return await _service.PrepareContract(input);
}
/// <summary>
/// 下载已经准备完成并替换变量的合同文件。
/// </summary> /// </summary>
[HttpGet("contract-source")] [HttpGet("contract-source")]
[ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)]
public async Task<FileStreamResult> ContractSource([FromQuery] OnboardingPublicContractSourceInput input) public async Task<FileStreamResult> ContractSource([FromQuery] OnboardingPublicContractSourceInput input)
{ {
var fileId = await _service.GetContractSourceFileId(input); var fileId = await _service.GetContractSourceFileId(input);
return await _fileService.Download(new BaseIdInput { Id = fileId }); return await _fileService.Download(new BaseIdInput { Id = fileId })
?? throw Oops.Bah("合同文件不存在,请重新准备合同");
} }
/// <summary> /// <summary>
@@ -296,9 +296,14 @@
保存候选人固定基础信息。 保存候选人固定基础信息。
</summary> </summary>
</member> </member>
<member name="M:SimpleAdmin.Web.Core.OnboardingPublicTaskController.PrepareContract(SimpleAdmin.Application.OnboardingPublicContractSourceInput)">
<summary>
校验合同变量并提前生成候选人的合同文件。
</summary>
</member>
<member name="M:SimpleAdmin.Web.Core.OnboardingPublicTaskController.ContractSource(SimpleAdmin.Application.OnboardingPublicContractSourceInput)"> <member name="M:SimpleAdmin.Web.Core.OnboardingPublicTaskController.ContractSource(SimpleAdmin.Application.OnboardingPublicContractSourceInput)">
<summary> <summary>
下载根据当前任务资料动态生成并已替换变量的合同文件。 下载已经准备完成并替换变量的合同文件。
</summary> </summary>
</member> </member>
<member name="M:SimpleAdmin.Web.Core.OnboardingPublicTaskController.MaterialSource(SimpleAdmin.Application.OnboardingPublicMaterialSourceInput)"> <member name="M:SimpleAdmin.Web.Core.OnboardingPublicTaskController.MaterialSource(SimpleAdmin.Application.OnboardingPublicMaterialSourceInput)">