namespace SimpleAdmin.Application; /// /// 实现入职合同预览和归档 DOCX 文件生成能力。 /// public class OnboardingContractDocumentService : IOnboardingContractDocumentService { private readonly SqlSugarScope _db; private readonly IFileService _fileService; public OnboardingContractDocumentService(IFileService fileService) { _db = DbContext.DB; _fileService = fileService; } /// /// 根据锁定的模板版本和任务变量快照生成已替换变量的合同 DOCX。 /// public async Task Generate(OnboardingGenerateContractInput input) { var contract = await _db.Queryable().InSingleAsync(input.TaskContractId) ?? throw Oops.Bah("任务合同不存在"); var task = await _db.Queryable().InSingleAsync(contract.TaskId) ?? throw Oops.Bah("入职任务不存在"); var statusAllowsGeneration = task.TaskStatus == OnboardingTaskStatus.PendingFill || task.TaskStatus == OnboardingTaskStatus.Rejected || task.TaskStatus == OnboardingTaskStatus.Reviewing || task.TaskStatus == OnboardingTaskStatus.Approved; if (!statusAllowsGeneration) throw Oops.Bah("当前任务状态不能生成合同"); if (contract.GeneratedDocxFileId.HasValue) return contract.GeneratedDocxFileId.Value; var version = await _db.Queryable().InSingleAsync(contract.TemplateVersionId) ?? throw Oops.Bah("合同版本不存在"); var source = await _db.Queryable().InSingleAsync(version.SourceDocxFileId) ?? throw Oops.Bah("合同源文件不存在"); var definitions = await _db.Queryable() .Where(item => item.TemplateVersionId == version.Id) .ToListAsync(); var variables = await _db.Queryable() .Where(item => item.TaskContractId == contract.Id) .ToListAsync(); var missing = definitions .Where(item => item.Required) .FirstOrDefault(definition => string.IsNullOrWhiteSpace( variables.FirstOrDefault(variable => variable.VariableCode == definition.VariableCode)?.VariableValue)); if (missing != null) throw Oops.Bah($"合同变量“{missing.VariableName}”尚未填写,暂时不能预览合同"); var sourceResult = await _fileService.Download(new BaseIdInput { Id = source.Id }) ?? throw Oops.Bah("无法读取合同源文件"); await using var sourceStream = sourceResult.FileStream; using var sourceBytes = new MemoryStream(); await sourceStream.CopyToAsync(sourceBytes); var rendered = DocxTemplateProcessor.Render( sourceBytes.ToArray(), variables.ToDictionary( item => item.VariableCode, item => item.VariableValue ?? string.Empty, StringComparer.OrdinalIgnoreCase)); var generatedAt = DateTime.Now; // 在文件名中加入生成批次,避免手机系统把同名的新合同误认为历史下载文件。 var name = $"{task.TaskNo}_{contract.ContractName}_{generatedAt:yyyyMMddHHmmssfff}.docx"; var fileId = await _fileService.StoreGeneratedFile( SysDictConst.FILE_ENGINE_ALIYUN_OSS, name, rendered); contract.GeneratedDocxFileId = fileId; contract.GeneratedAt = generatedAt; contract.ContractStatus = task.TaskStatus == OnboardingTaskStatus.Approved ? TaskContractStatus.OfflinePendingSign : TaskContractStatus.PendingConfirm; await _db.Updateable(contract).ExecuteCommandAsync(); return fileId; } }