@tasolutions/express-core
Version:
All libs for express
354 lines (303 loc) • 15.3 kB
JavaScript
const { exec } = require('child_process');
const JsBarcode = require('jsbarcode');
const { createCanvas } = require('canvas');
module.exports = {
/**
* Chuyển đổi file DOCX sang PDF bằng LibreOffice
* @param {String} docxFilePath - Đường dẫn file DOCX tạm thời
* @param {String} pdfFilePath - Đường dẫn save file PDF
* @returns {Promise<String>} - Đường dẫn file PDF đã chuyển đổi
*/
convertDocxToPdf: (docxFilePath, pdfFilePath) => {
return new Promise((resolve, reject) => {
// Sử dụng LibreOffice trực tiếp thay vì unoconv
const outputDir = require('path').dirname(pdfFilePath);
const command = `soffice --headless --convert-to pdf --outdir "${outputDir}" "${docxFilePath}"`;
console.log(`Executing command: ${command}`);
exec(command, (error, stdout, stderr) => {
if (error) {
console.error(`Lỗi chuyển đổi PDF: ${error.message}`);
console.error(`stderr: ${stderr}`);
// Thử fallback với unoconv nếu LibreOffice không hoạt động
console.log('Thử fallback với unoconv...');
const unoconvCommand = `unoconv -f pdf -o "${pdfFilePath}" "${docxFilePath}"`;
exec(unoconvCommand, (unoconvError, unoconvStdout, unoconvStderr) => {
if (unoconvError) {
console.error(`Lỗi unoconv fallback: ${unoconvError.message}`);
console.error(`unoconv stderr: ${unoconvStderr}`);
return reject(unoconvError);
}
console.log(`Unoconv fallback thành công: ${unoconvStdout}`);
resolve(pdfFilePath);
});
return;
}
if (stderr) {
console.error(`LibreOffice stderr: ${stderr}`);
}
console.log(`LibreOffice chuyển đổi thành công: ${stdout}`);
// LibreOffice tạo file với tên gốc + .pdf, cần rename
const originalPdfPath = docxFilePath.replace('.docx', '.pdf');
const fs = require('fs');
if (fs.existsSync(originalPdfPath)) {
fs.renameSync(originalPdfPath, pdfFilePath);
console.log(`Đã rename file từ ${originalPdfPath} sang ${pdfFilePath}`);
}
resolve(pdfFilePath);
});
});
},
/**
* Function backup đơn giản để đảm bảo các field cơ bản có mặt
* @param {Object} data - Dữ liệu gốc
* @returns {Object} - Dữ liệu đã được bổ sung field cơ bản
*/
ensureBasicFieldsPresent: (data) => {
const result = { ...data };
// Thêm các field cơ bản thường gặp nếu chưa có
const basicFields = [
'name', 'email', 'phone', 'address', 'description', 'title', 'content',
'status', 'type', 'category', 'tags', 'notes', 'comments',
'start_date', 'end_date', 'created_date', 'updated_date',
'is_active', 'is_deleted', 'is_public', 'is_private'
];
for (const fieldName of basicFields) {
if (!(fieldName in result)) {
result[fieldName] = "";
}
}
return result;
},
/**
* Đảm bảo tất cả field schema của Collection đều có mặt trong data
* @param {Object} data - Dữ liệu gốc
* @param {Object} Collection - Model của Collection
* @returns {Object} - Dữ liệu đã được bổ sung đầy đủ field
*/
ensureAllFieldsPresent: (data, Collection) => {
const result = { ...data };
try {
// Lấy schema của Collection
const schema = Collection.schema.obj;
// Thêm tất cả field từ schema vào data nếu chưa có
for (const [fieldName, fieldSchema] of Object.entries(schema)) {
if (!(fieldName in result)) {
// Xử lý an toàn cho tất cả loại field
try {
// Kiểm tra loại field để xử lý phù hợp
if (fieldSchema && typeof fieldSchema === 'object') {
// Nếu là ObjectId ref hoặc schema phức tạp
if (fieldSchema.type === 'ObjectId' || fieldSchema.ref) {
result[fieldName] = ""; // ObjectId ref - để rỗng
} else if (fieldSchema.type === 'Array') {
result[fieldName] = []; // Array - để mảng rỗng
} else if (fieldSchema.type === 'Boolean') {
result[fieldName] = false; // Boolean - để false
} else if (fieldSchema.type === 'Number') {
result[fieldName] = 0; // Number - để 0
} else if (fieldSchema.type === 'Date') {
result[fieldName] = ""; // Date - để rỗng
} else {
result[fieldName] = ""; // Mặc định - để rỗng
}
} else {
// Nếu là string đơn giản (ví dụ: String, Number, Boolean)
result[fieldName] = "";
}
} catch (fieldError) {
// Nếu có lỗi khi xử lý field cụ thể, để rỗng
console.warn(`[WARNING] Lỗi xử lý field ${fieldName}: ${fieldError.message}`);
result[fieldName] = "";
}
}
}
// Thêm các field đặc biệt của MongoDB nếu chưa có
const specialFields = ['_id', 'created_at', 'updated_at', 'is_deleted', 'is_active'];
for (const fieldName of specialFields) {
if (!(fieldName in result)) {
result[fieldName] = "";
}
}
console.log(`[DEBUG] Đã đảm bảo tất cả field schema có mặt. Tổng số field: ${Object.keys(result).length}`);
} catch (error) {
console.warn(`[WARNING] Không thể lấy schema của Collection: ${error.message}`);
// Nếu không lấy được schema, vẫn trả về data gốc
}
return result;
},
/**
* Xử lý dữ liệu để đảm bảo không có field undefined/null khi render template
* @param {Object} data - Dữ liệu gốc
* @returns {Object} - Dữ liệu đã được xử lý
*/
sanitizeDataForTemplate: (data) => {
const result = { ...data };
for (const [key, value] of Object.entries(result)) {
// Xử lý các giá trị null, undefined, NaN
if (value === null || value === undefined || (typeof value === 'number' && isNaN(value))) {
result[key] = "";
}
// Convert các giá trị khác thành string
else if (typeof value !== 'string') {
result[key] = String(value);
}
}
return result;
},
flattenObject: (obj, parentKey = '', separator = '__') => {
const result = {};
for (let [key, value] of Object.entries(obj)) {
const newKey = parentKey ? `${parentKey}${separator}${key}` : key;
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
Object.assign(result, module.exports.flattenObject(value, newKey, separator));
} else {
result[newKey] = value;
// Kiểm tra nếu key là gender và dịch giá trị
if (key === 'gender' && genderMapping[value]) {
result[`${newKey}_vi`] = genderMapping[value];
}
}
}
return result;
},
calculateYearDifferenceForFlattenedData: (data, separator = '_') => {
const result = { ...data }; // Sao chép dữ liệu gốc để tránh thay đổi trực tiếp
// Xử lý các giá trị null hoặc undefined trước khi tính toán
for (const key in result) {
if (result[key] === null || result[key] === undefined || result[key] === '') {
result[key] = "";
}
}
for (const [key, value] of Object.entries(result)) {
// Chỉ xử lý nếu value không rỗng và có thể parse thành Date
if (value && value !== "" && typeof value === 'string' && !isNaN(Date.parse(value))) {
// Nếu giá trị là ngày tháng hợp lệ, tính khoảng cách năm
const date = new Date(value);
const currentDate = new Date();
const yearDifference = currentDate.getFullYear() - date.getFullYear();
const monthDiff = currentDate.getMonth() - date.getMonth();
const dayDiff = currentDate.getDate() - date.getDate();
// Điều chỉnh nếu tháng hoặc ngày chưa đến
const adjustedYearDifference = monthDiff < 0 || (monthDiff === 0 && dayDiff < 0) ? yearDifference - 1 : yearDifference;
// Thêm trường mới với hậu tố `__year` - convert thành string
result[`${key}${separator}year`] = currentDate.getFullYear().toString();
// Thêm trường mới với hậu tố `__month` - convert thành string
result[`${key}${separator}month`] = (currentDate.getMonth() + 1).toString(); // +1 vì getMonth() trả về 0-11
// Thêm trường mới với hậu tố `__day` - convert thành string
result[`${key}${separator}day`] = currentDate.getDate().toString();
// Thêm trường mới với hậu tố `__year_diff` - convert thành string
result[`${key}${separator}year_diff`] = adjustedYearDifference.toString();
// Thêm các field bổ sung cho date gốc - convert thành string
result[`${key}${separator}original_year`] = date.getFullYear().toString();
result[`${key}${separator}original_month`] = (date.getMonth() + 1).toString();
result[`${key}${separator}original_day`] = date.getDate().toString();
}
}
return result;
},
/**
* Chuyển đổi base64 thành ArrayBuffer (hỗ trợ xử lý hình ảnh QR code và barcode)
* @param {String} base64 - Chuỗi base64 của hình ảnh
* @returns {ArrayBuffer} - ArrayBuffer để sử dụng trong docxtemplater
*/
base64ToArrayBuffer: (base64) => {
var binaryString = Buffer.from(base64, 'base64').toString('binary');
var len = binaryString.length;
var bytes = new Uint8Array(len);
for (var i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes.buffer;
},
/**
* Tạo barcode từ chuỗi đầu vào
* @param {String} text - Chuỗi cần tạo barcode
* @param {Object} options - Tùy chọn cho barcode (loại, kích thước, v.v.)
* @returns {String} - Chuỗi base64 của hình ảnh barcode
*/
generateBarcode: (text, options = {}) => {
// Tạo canvas để vẽ barcode
const canvas = createCanvas(300, 100);
// Cấu hình mặc định cho barcode
const defaultOptions = {
format: 'CODE128', // Loại barcode
width: 2, // Độ rộng của thanh
height: 100, // Chiều cao của barcode
displayValue: false, // Không hiển thị giá trị bên dưới barcode
fontSize: 20, // Kích thước font chữ
margin: 10, // Lề
background: '#ffffff', // Màu nền
lineColor: '#000000' // Màu thanh
};
// Kết hợp tùy chọn mặc định với tùy chọn được cung cấp
const barcodeOptions = { ...defaultOptions, ...options };
try {
// Tạo barcode
JsBarcode(canvas, text, barcodeOptions);
// Chuyển đổi canvas thành chuỗi base64
return canvas.toDataURL('image/png');
} catch (error) {
console.error(`Lỗi khi tạo barcode: ${error.message}`);
// Trả về một hình ảnh lỗi hoặc null
return null;
}
},
/**
* Lấy danh sách field boolean từ Collection schema hoặc từ data
*/
getBooleanFields: (data, Collection) => {
let fields = [];
if (Collection && Collection.schema && Collection.schema.obj) {
for (const [field, schema] of Object.entries(Collection.schema.obj)) {
if (schema === Boolean || (schema && schema.type === Boolean)) {
fields.push(field);
}
}
}
// Nếu không có Collection hoặc không tìm thấy, lấy từ data
if (fields.length === 0) {
fields = Object.keys(data).filter(key => typeof data[key] === 'boolean');
}
return fields;
},
/**
* Tự động sinh field tick_yes/tick_no cho tất cả field boolean hoặc field không có giá trị
* @param {Object} data - Dữ liệu đã flatten/sanitize
* @param {Object} [Collection] - Model của Collection (tùy chọn, để lấy danh sách field boolean nếu cần)
* @returns {Object} - Dữ liệu đã bổ sung field tick
*/
addBooleanTicks: (data, Collection) => {
const result = { ...data };
const booleanFields = module.exports.getBooleanFields(data, Collection);
for (const field of booleanFields) {
const value = data[field];
if (typeof value === 'boolean') {
result[`tick_${field}_yes`] = value ? "☑" : "☐";
result[`tick_${field}_no`] = value ? "☐" : "☑";
} else {
// Nếu không có giá trị, cả hai đều là tick trống
result[`tick_${field}_yes`] = "☐";
result[`tick_${field}_no`] = "☐";
}
}
return result;
},
/**
* Chuyển các field string 'true'/'false' về boolean thực sự
* @param {Object} data
* @returns {Object}
*/
convertStringBooleans: (data) => {
const result = { ...data };
for (const [key, value] of Object.entries(result)) {
if (value === "true") result[key] = true;
else if (value === "false") result[key] = false;
}
return result;
}
}
const genderMapping = {
MALE: "Nam",
FEMALE: "Nữ",
OTHER: "Khác"
};