fix(planting-service): 修复合同签名无法放到指定位置的问题
- 修改 generateSignedContractPdf 在同一个 PDFDocument 实例上完成填充和签名 - 移除 fillFormFields 中的 form.flatten(),保留签名字段供后续使用 - 最后统一扁平化所有表单字段,确保签名放到正确位置 - 控制器改用 generateSignedContractPdf 替代分两步调用 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
09cc696efa
commit
bdeff3b372
|
|
@ -248,21 +248,19 @@ export class ContractSigningController {
|
|||
signatureCloudUrl = `data:image/png;base64,${dto.signatureBase64.slice(0, 100)}...`;
|
||||
}
|
||||
|
||||
// 5. 生成带签名的 PDF
|
||||
// 5. 生成带签名的 PDF(在同一个实例上完成填充和签名)
|
||||
const signingDate = new Date().toISOString().split('T')[0];
|
||||
let pdfBuffer = await this.pdfGeneratorService.generateContractPdf({
|
||||
contractNo: task.contractNo,
|
||||
userRealName: task.userRealName || '未认证',
|
||||
userIdCard: task.userIdCardNumber || '',
|
||||
userPhone: task.userPhoneNumber || '',
|
||||
treeCount: task.treeCount,
|
||||
signingDate,
|
||||
});
|
||||
|
||||
// 6. 嵌入签名到 PDF
|
||||
pdfBuffer = await this.pdfGeneratorService.embedSignature(pdfBuffer, {
|
||||
signatureImagePng: signatureBuffer,
|
||||
});
|
||||
const pdfBuffer = await this.pdfGeneratorService.generateSignedContractPdf(
|
||||
{
|
||||
contractNo: task.contractNo,
|
||||
userRealName: task.userRealName || '未认证',
|
||||
userIdCard: task.userIdCardNumber || '',
|
||||
userPhone: task.userPhoneNumber || '',
|
||||
treeCount: task.treeCount,
|
||||
signingDate,
|
||||
},
|
||||
{ signatureImagePng: signatureBuffer },
|
||||
);
|
||||
this.logger.log(`Signed PDF generated: ${pdfBuffer.length} bytes`);
|
||||
|
||||
// 7. 上传签署后的 PDF 到 MinIO
|
||||
|
|
@ -338,21 +336,19 @@ export class ContractSigningController {
|
|||
signatureCloudUrl = `data:image/png;base64,${dto.signatureBase64.slice(0, 100)}...`;
|
||||
}
|
||||
|
||||
// 5. 生成带签名的 PDF
|
||||
// 5. 生成带签名的 PDF(在同一个实例上完成填充和签名)
|
||||
const signingDate = new Date().toISOString().split('T')[0];
|
||||
let pdfBuffer = await this.pdfGeneratorService.generateContractPdf({
|
||||
contractNo: task.contractNo,
|
||||
userRealName: task.userRealName || '未认证',
|
||||
userIdCard: task.userIdCardNumber || '',
|
||||
userPhone: task.userPhoneNumber || '',
|
||||
treeCount: task.treeCount,
|
||||
signingDate,
|
||||
});
|
||||
|
||||
// 6. 嵌入签名到 PDF
|
||||
pdfBuffer = await this.pdfGeneratorService.embedSignature(pdfBuffer, {
|
||||
signatureImagePng: signatureBuffer,
|
||||
});
|
||||
const pdfBuffer = await this.pdfGeneratorService.generateSignedContractPdf(
|
||||
{
|
||||
contractNo: task.contractNo,
|
||||
userRealName: task.userRealName || '未认证',
|
||||
userIdCard: task.userIdCardNumber || '',
|
||||
userPhone: task.userPhoneNumber || '',
|
||||
treeCount: task.treeCount,
|
||||
signingDate,
|
||||
},
|
||||
{ signatureImagePng: signatureBuffer },
|
||||
);
|
||||
this.logger.log(`Signed PDF generated: ${pdfBuffer.length} bytes`);
|
||||
|
||||
// 7. 上传签署后的 PDF 到 MinIO
|
||||
|
|
|
|||
|
|
@ -104,6 +104,9 @@ export class PdfGeneratorService {
|
|||
if (this.hasFormFields(pdfDoc)) {
|
||||
this.logger.log('Using form fields mode');
|
||||
await this.fillFormFields(pdfDoc, data, customFont);
|
||||
// 预览PDF时扁平化所有字段(包括签名字段)
|
||||
const form = pdfDoc.getForm();
|
||||
form.flatten();
|
||||
} else {
|
||||
this.logger.log('Using coordinate mode (no form fields found)');
|
||||
this.fillByCoordinates(pdfDoc, data, customFont);
|
||||
|
|
@ -157,8 +160,8 @@ export class PdfGeneratorService {
|
|||
}
|
||||
}
|
||||
|
||||
// 扁平化表单(将字段转换为静态文本,不可再编辑)
|
||||
form.flatten();
|
||||
// 注意:不在这里调用 form.flatten()
|
||||
// 签名字段需要保留,待签名嵌入后再统一扁平化
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -339,6 +342,7 @@ export class PdfGeneratorService {
|
|||
|
||||
/**
|
||||
* 生成带签名的完整合同 PDF
|
||||
* 在同一个 PDFDocument 实例上完成填充和签名,最后统一扁平化
|
||||
* @param data 合同数据
|
||||
* @param signature 签名数据(可选,如果没有签名则只生成填充数据的 PDF)
|
||||
* @returns PDF Buffer
|
||||
|
|
@ -347,15 +351,63 @@ export class PdfGeneratorService {
|
|||
data: ContractPdfData,
|
||||
signature?: SignatureData,
|
||||
): Promise<Buffer> {
|
||||
// 先生成填充数据的 PDF
|
||||
let pdfBuffer = await this.generateContractPdf(data);
|
||||
this.logger.log(`Generating signed PDF for contract: ${data.contractNo}`);
|
||||
|
||||
// 如果有签名,嵌入签名
|
||||
if (signature) {
|
||||
pdfBuffer = await this.embedSignature(pdfBuffer, signature);
|
||||
try {
|
||||
// 1. 加载 PDF 模板
|
||||
const templateBytes = fs.readFileSync(this.templatePath);
|
||||
const pdfDoc = await PDFDocument.load(templateBytes);
|
||||
|
||||
// 2. 注册 fontkit 以支持自定义字体
|
||||
pdfDoc.registerFontkit(fontkit);
|
||||
|
||||
// 3. 加载中文字体
|
||||
const fontBytes = fs.readFileSync(this.fontPath);
|
||||
const customFont = await pdfDoc.embedFont(fontBytes);
|
||||
|
||||
// 4. 填充表单字段(不扁平化)
|
||||
if (this.hasFormFields(pdfDoc)) {
|
||||
this.logger.log('Using form fields mode');
|
||||
await this.fillFormFields(pdfDoc, data, customFont);
|
||||
} else {
|
||||
this.logger.log('Using coordinate mode (no form fields found)');
|
||||
this.fillByCoordinates(pdfDoc, data, customFont);
|
||||
}
|
||||
|
||||
// 5. 如果有签名,嵌入签名图片到 signature 按钮字段
|
||||
if (signature) {
|
||||
const signatureImage = await pdfDoc.embedPng(signature.signatureImagePng);
|
||||
|
||||
try {
|
||||
const form = pdfDoc.getForm();
|
||||
const signatureButton = form.getButton(FORM_FIELDS.SIGNATURE);
|
||||
signatureButton.setImage(signatureImage);
|
||||
this.logger.log('Signature embedded using form field');
|
||||
} catch {
|
||||
// 如果没有签名按钮字段,回退到坐标方式
|
||||
this.logger.log('Signature button field not found, using coordinates');
|
||||
this.embedSignatureByCoordinates(pdfDoc, signatureImage);
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 最后统一扁平化所有表单字段
|
||||
if (this.hasFormFields(pdfDoc)) {
|
||||
const form = pdfDoc.getForm();
|
||||
form.flatten();
|
||||
this.logger.log('Form fields flattened');
|
||||
}
|
||||
|
||||
// 7. 保存 PDF
|
||||
const pdfBytes = await pdfDoc.save();
|
||||
this.logger.log(`Signed PDF generated successfully for contract: ${data.contractNo}`);
|
||||
return Buffer.from(pdfBytes);
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Failed to generate signed PDF for contract ${data.contractNo}:`,
|
||||
error,
|
||||
);
|
||||
throw new Error(`Signed PDF generation failed: ${error.message}`);
|
||||
}
|
||||
|
||||
return pdfBuffer;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue