@tasolutions/express-core
Version:
All libs for express
243 lines (217 loc) • 11.1 kB
JavaScript
const fs = require('fs');
const path = require('path');
const axios = require('axios');
const PizZip = require('pizzip');
const Docxtemplater = require('docxtemplater');
const QRCode = require('qrcode');
const ImageModule = require('docxtemplater-image-module-free');
const { DocumentMedia } = require('../models');
const { upload } = require('../../../../utils/s3Upload');
const { awsInfo } = require('../../../../config');
const { E_DocumentMedia } = require('../../../../utils/enum.util');
const { convertDocxToPdf, flattenObject, calculateYearDifferenceForFlattenedData, sanitizeDataForTemplate, ensureAllFieldsPresent, ensureBasicFieldsPresent, addBooleanTicks, convertStringBooleans, base64ToArrayBuffer, generateBarcode } = require('../utils/document');
const { sendMessage } = require('../../../../clients/discord');
module.exports = {
/**
* Hàm tạo file Word và PDF từ template
* @param {Object} documentTemplate - Template document
* @param {String} collection_name - Tên collection
* @param {Object} data - Dữ liệu để render
* @param {Array} qrFields - Mảng các trường cần tạo QR code (tùy chọn, ưu tiên hơn qrcode_fields trong template)
* @param {Array} barcodeFields - Mảng các trường cần tạo Barcode (tùy chọn, ưu tiên hơn barcode_fields trong template)
* @param {Object} barcodeOptions - Tùy chọn cho barcode (loại, kích thước, v.v.)
* @param {Object} Collection - Model của Collection (tùy chọn)
* @returns {Object} - URL file Word và PDF đã upload
*/
async genFile(documentTemplate, collection_name = 'model_default', data = {}, qrFields = [], barcodeFields = [], barcodeOptions = {}, Collection = null) {
const refId = data._id;
const fileNameBase = refId.toString();
const payloadDocumentMedia = {
document_template_id: documentTemplate._id,
collection_name,
ref_id: refId,
name: fileNameBase,
};
try {
// Ưu tiên sử dụng qrFields được truyền vào, nếu không có thì sử dụng qrcode_fields từ template
const qrFieldsToProcess = qrFields && qrFields.length > 0
? qrFields
: (documentTemplate.qrcode_fields || []);
// Ưu tiên sử dụng barcodeFields được truyền vào, nếu không có thì sử dụng barcode_fields từ template
const barcodeFieldsToProcess = barcodeFields && barcodeFields.length > 0
? barcodeFields
: (documentTemplate.barcode_fields || []);
// 1. Tải nội dung file template
const response = await axios.get(documentTemplate.file, { responseType: 'arraybuffer' });
const content = response.data;
// 2. Khởi tạo và render tài liệu với API mới của docxtemplater
const zip = new PizZip(content);
// Xử lý QR code và barcode nếu cần
let dataFinal = { ...data };
// Đảm bảo tất cả field schema của Collection đều có mặt (chỉ khi có Collection)
if (Collection) {
try {
dataFinal = ensureAllFieldsPresent(dataFinal, Collection);
} catch (error) {
console.warn(`[WARNING] Lỗi khi đảm bảo field schema, sử dụng backup: ${error.message}`);
dataFinal = ensureBasicFieldsPresent(dataFinal);
}
}
let useImageModule = qrFieldsToProcess.length > 0 || barcodeFieldsToProcess.length > 0;
// Nếu có trường cần tạo QR code
if (qrFieldsToProcess.length > 0) {
// Tạo QR code cho từng trường được chỉ định
for (const field of qrFieldsToProcess) {
if (data[field]) {
// Tạo QR code và thêm vào dữ liệu với tên trường qrcode_{field}
const qrCodeData = await QRCode.toDataURL(data[field].toString());
dataFinal[`qrcode_${field}`] = qrCodeData;
}
}
}
// Nếu có trường cần tạo barcode
if (barcodeFieldsToProcess.length > 0) {
// Tạo barcode cho từng trường được chỉ định
for (const field of barcodeFieldsToProcess) {
if (data[field]) {
// Tạo barcode và thêm vào dữ liệu với tên trường barcode_{field}
const barcodeData = generateBarcode(data[field].toString(), barcodeOptions);
if (barcodeData) {
dataFinal[`barcode_${field}`] = barcodeData;
}
}
}
}
// Cấu hình module xử lý hình ảnh nếu cần
let doc;
if (useImageModule) {
const imageOpts = {
centered: false,
fileType: "docx",
getSize: (img, tagValue, tagName) => {
// Kích thước mặc định cho QR code và barcode
if (tagName && tagName.startsWith('qrcode_')) {
return [100, 100]; // Kích thước QR code
} else if (tagName && tagName.startsWith('barcode_')) {
return [200, 60]; // Kích thước barcode
}
return [100, 100]; // Kích thước mặc định
},
getImage: (tagValue, tagName) => {
if (!tagValue || !tagValue.startsWith('data:image')) return null;
const base64Data = tagValue.replace(/^data:image\/(png|jpg|jpeg);base64,/, '');
return base64ToArrayBuffer(base64Data);
}
};
const imageModule = new ImageModule(imageOpts);
doc = new Docxtemplater(zip, {
paragraphLoop: true,
linebreaks: true,
modules: [imageModule]
});
} else {
doc = new Docxtemplater(zip, {
paragraphLoop: true,
linebreaks: true
});
}
// 3. Render nội dung với dữ liệu
try {
// 1. Sanitize data trước (nếu cần)
dataFinal = sanitizeDataForTemplate(dataFinal);
// 2. Flatten object
const dataRender = flattenObject(dataFinal);
// 3. Calculate date fields
dataFinal = calculateYearDifferenceForFlattenedData(dataRender);
// 4. Convert string 'true'/'false' về boolean thật
dataFinal = convertStringBooleans(dataFinal);
// 5. Thêm field tick cho boolean
dataFinal = addBooleanTicks(dataFinal, Collection);
// 6. Sanitize lại nếu muốn chắc chắn không có undefined/null
dataFinal = sanitizeDataForTemplate(dataFinal);
doc.render(dataFinal);
} catch (renderError) {
console.error('Lỗi khi render document:', renderError);
throw renderError;
}
// 4. Tạo buffer Word
const wordBuffer = doc.getZip().generate({ type: 'nodebuffer' });
const wordFileName = `${fileNameBase}.docx`;
const tempDir = path.resolve(__dirname);
const tempDocxPath = path.join(tempDir, wordFileName);
// 5. Ghi file Word tạm
fs.writeFileSync(tempDocxPath, wordBuffer);
// 6. Chuyển sang PDF
const pdfFileName = `${fileNameBase}.pdf`;
const tempPdfPath = path.join(tempDir, pdfFileName);
await convertDocxToPdf(tempDocxPath, tempPdfPath);
// 7. Đọc buffer PDF
const pdfBuffer = fs.readFileSync(tempPdfPath);
// 8. Upload file Word
const uploadResWord = await upload({
Bucket: awsInfo.s3Info.bucket,
Key: `documents/${collection_name}/${wordFileName}`,
Body: wordBuffer,
ContentType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
});
// 9. Upload file PDF
const uploadResPdf = await upload({
Bucket: awsInfo.s3Info.bucket,
Key: `documents/${collection_name}/${pdfFileName}`,
Body: pdfBuffer,
ContentType: 'application/pdf',
});
// 10. Xóa file tạm
fs.unlinkSync(tempDocxPath);
fs.unlinkSync(tempPdfPath);
// 11. Lưu trữ trong database
const existingCount = await DocumentMedia.countDocuments({
document_template_id: documentTemplate._id,
collection_name,
ref_id: refId,
name: refId,
});
if (existingCount) {
await DocumentMedia.updateMany(
{
document_template_id: documentTemplate._id,
collection_name,
ref_id: refId,
name: refId,
}, {}
);
} else {
await DocumentMedia.create([
{
...payloadDocumentMedia,
file_url: uploadResWord.Location,
file_type: E_DocumentMedia.E_FileType.DOCX,
},
{
...payloadDocumentMedia,
file_url: uploadResPdf.Location,
file_type: E_DocumentMedia.E_FileType.PDF,
},
]);
}
// 12. Trả về URL file đã upload
const result = {
data_template: dataFinal,
name: refId,
doc_url: uploadResWord.Location,
pdf_url: uploadResPdf.Location,
};
console.log('[SUCCESS][MODULE][DOCUMENT][genFile] result:', result);
sendMessage('[SUCCESS][MODULE][DOCUMENT][genFile] result:', JSON.stringify({
name: result.name,
doc_url: result.doc_url,
pdf_url: result.pdf_url,
}, null, 2));
return result;
} catch (error) {
console.error('Lỗi trong quá trình xử lý tài liệu:', error);
sendMessage('[ERROR][MODULE][DOCUMENT][genFile] error:', JSON.stringify(error, null, 2));
throw { message: error.message || 'Lỗi khi tạo tài liệu' };
}
}
};