UNPKG

@tasolutions/express-core

Version:
243 lines (217 loc) 11.1 kB
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' }; } } };