import { render } from '@vue-email/render'; import Template1 from '@/lib/email/templates/Template1.vue'; import { forEachAsync } from 'es-toolkit'; export default function useSendEmail(filter, templateId) { const paymentSchedules = ref(null); const contents = ref(null); const isSending = ref(false); const emailTemplate = ref(null); const { $dayjs, $getdata, $insertapi, $numtoString, $numberToVietnamese, $numberToVietnameseCurrency, $formatDateVN, $getFirstAndLastName, $snackbar, $store, $paymentQR } = useNuxtApp(); const buildQrHtml = (url) => `
VietQR
`; const formatDateVNText = (input, { withTime = false, withSeconds = false } = {}) => { if (!input) return ''; const date = new Date(input); if (isNaN(date.getTime())) return ''; const pad = (n) => String(n).padStart(2, '0'); const day = pad(date.getDate()); const month = pad(date.getMonth() + 1); const year = date.getFullYear(); let result = `${day} tháng ${month} năm ${year}`; if (withTime) { const hours = pad(date.getHours()); const minutes = pad(date.getMinutes()); result += ` ${hours}:${minutes}`; if (withSeconds) { const seconds = pad(date.getSeconds()); result += `:${seconds}`; } } return result; }; function buildContentPayment(data) { const { txn_detail__transaction__customer__type__code: customerType, txn_detail__transaction__customer__code: customerCode, txn_detail__transaction__customer__fullname: customerName, txn_detail__transaction__product__trade_code: productCode, cycle, } = data; if (customerType.toLowerCase() === 'cn') { if (customerName.length < 14) { return `${productCode} ${customerCode} ${customerName} TT ${cycle == 0 ? 'Dat Coc' : `Dot ${cycle}`}`; } else { return `${productCode} ${customerCode} ${$getFirstAndLastName(customerName)} TT ${cycle == 0 ? 'Dat Coc' : `Dot ${cycle}`}`; } } else { return `${productCode} ${customerCode} TT ${cycle == 0 ? 'Dat Coc' : `Dot ${cycle}`}`; } } function sanitizeContentPayment(text, maxLength = 80) { if (!text) return ''; return text .normalize('NFD') // bỏ dấu tiếng Việt .replace(/[\u0300-\u036f]/g, '') .replace(/[^a-zA-Z0-9 _-]/g, '') // bỏ ký tự lạ .replace(/\s+/g, ' ') .trim() .slice(0, maxLength); } function addDays(days) { const today = new Date(); today.setDate(today.getDate() + days); return today; } function replaceTemplateVars(html, paymentScheduleItem) { const { txn_detail__transaction__product__trade_code, txn_detail__transaction__customer__code, txn_detail__transaction__customer__fullname, txn_detail__transaction__customer__legal_code, txn_detail__transaction__customer__type__code, txn_detail__transaction__customer__contact_address, txn_detail__transaction__customer__address, txn_detail__transaction__customer__phone, txn_detail__transaction__date, from_date, to_date, cycle, amount, paid_amount, penalty_amount, remain_amount, } = paymentScheduleItem; return html .replace(/\[day]/g, String(new Date().getDate()).padStart(2, '0') || '') .replace(/\[month]/g, String(new Date().getMonth() + 1).padStart(2, '0') || '') .replace(/\[year]/g, new Date().getFullYear() || '') .replace(/\[product\.trade_code\]/g, txn_detail__transaction__product__trade_code) .replace(/\[product\.trade_code_payment\]/g, sanitizeContentPayment(txn_detail__transaction__product__trade_code)) .replace(/\[customer\.fullname\]/g, txn_detail__transaction__customer__fullname) .replace( /\[customer\.name\]/g, `${txn_detail__transaction__customer__type__code.toLowerCase() == 'cn' ? (txn_detail__transaction__customer__fullname.length < 14 ? txn_detail__transaction__customer__fullname : $getFirstAndLastName(txn_detail__transaction__customer__fullname)) : ''}` || '', ) .replace(/\[customer\.code\]/g, txn_detail__transaction__customer__code || '') .replace( /\[customer\.legal_code\]/g, txn_detail__transaction__customer__legal_code || '', ) .replace( /\[customer\.contact_address\]/g, txn_detail__transaction__customer__contact_address || txn_detail__transaction__customer__address || '', ) .replace(/\[customer\.phone\]/g, txn_detail__transaction__customer__phone || '') .replace(/\[payment_schedule\.from_date\]/g, $formatDateVN(from_date) || '') .replace(/\[payment_schedule\.to_date\]/g, $formatDateVN(to_date) || '') .replace(/\[payment_schedule\.expiration_date\]/g, $formatDateVN(addDays(3)) || '') .replace(/\[payment_schedule\.amount_remain\]/g, $numtoString(amount) || '') .replace(/\[payment_schedule\.paid_amount\]/g, $numtoString(paid_amount) || '') .replace(/\[payment_schedule\.penalty_amount\]/g, $numtoString(penalty_amount) || '') .replace(/\[payment_schedule\.amount\]/g, $numtoString(remain_amount) || '') .replace( /\[payment_schedule\.amount_in_word\]/g, $numberToVietnameseCurrency(remain_amount) || '', ) .replace(/\[payment_schedule\.cycle\]/g, $numtoString(cycle) || '') .replace( /\[payment_schedule\.cycle-in-words\]/g, `${cycle == 0 ? 'đặt cọc' : `đợt thứ ${$numberToVietnamese(cycle).toLowerCase()}`}` || '', ) .replace( /\[payment_schedule\.note\]/g, `${cycle == 0 ? 'Dat coc' : `Dot ${cycle}`}` || '', ) .replace( /\[transaction\.date\]/g, formatDateVNText(txn_detail__transaction__date) || '', ); } function quillToEmailHtml(html) { return html // ALIGN (chỉ replace align class, không phá class khác) .replace(/class="([^"]*?)ql-align-center([^"]*?)"/g, 'style="text-align:center;"') .replace(/class="([^"]*?)ql-align-right([^"]*?)"/g, 'style="text-align:right;"') .replace(/class="([^"]*?)ql-align-left([^"]*?)"/g, 'style="text-align:left;"') .replace(/class="([^"]*?)ql-align-justify([^"]*?)"/g, 'style="text-align:justify;"') // FONT SIZE (xóa class size) .replace(/ql-size-small/g, '') .replace(/ql-size-large/g, '') .replace(/ql-size-huge/g, '') // REMOVE EMPTY CLASS .replace(/class=""/g, '') // FIX FIGURE MARGIN LEFT + RIGHT .replace( /]*?)style="([^"]*)"/g, (match, before, styles) => { if (!/margin-left\s*:/.test(styles)) { styles += ';margin-left:0'; } if (!/margin-right\s*:/.test(styles)) { styles += ';margin-right:0'; } return `]*>\s*]*img\.vietqr\.io[^>]*>\s*<\/div>/g, '', ); const qr = buildQrHtml($paymentQR(buildContentPayment(paymentSchedule), 'TCB')); message = message.trim() + qr; return { ...emailTemplate.value.content, content: undefined, message: replaceTemplateVars(message, paymentSchedule), }; } async function send() { isSending.value = true; $snackbar(`Đang gửi ${contents.value.length} email thông báo...`); await forEachAsync(contents.value, async (bigContent, i) => { const { imageUrl, keyword, message, subject } = bigContent; const tempEm = { content: toRaw(bigContent), previewMode: true, }; // ===== QUILL → HTML EMAIL (INLINE STYLE) ===== tempEm.content.message = quillToEmailHtml(message); let emailHtml = await render(Template1, tempEm); // If no image URL provided, remove image section from HTML if ((imageUrl ?? '').trim() === '') { emailHtml = emailHtml .replace(/]*>[\s\S]*?<\/tr>/gi, '') .replace(/\n\s*\n\s*\n/g, '\n\n'); } // Replace keywords in HTML let finalEmailHtml = emailHtml; if (keyword && keyword.length > 0) { keyword.forEach(({ keyword, value }) => { if (keyword && value) { const regex = new RegExp(`\\{\\{${keyword}\\}\\}`, 'g'); finalEmailHtml = finalEmailHtml.replace(regex, value); } }); } const response = await $insertapi( 'sendemail', { to: paymentSchedules.value[i].txn_detail__transaction__customer__email, content: finalEmailHtml, subject: replaceTemplateVars(subject, paymentSchedules.value[i]) || 'Thông báo từ Utopia Villas & Resort', }, undefined, false, ); if (response !== null) { await $insertapi('productnote', { ref: paymentSchedules.value[i].txn_detail__transaction__product, user: $store.login.id, detail: `Đã gửi email thông báo nhắc nợ cho sản phẩm ${paymentSchedules.value[i].txn_detail__transaction__product__trade_code} vào lúc ${$dayjs().format('HH:mm ngày DD/MM/YYYY')}.` }, undefined, false); } }); setTimeout(() => { $snackbar('Thông báo đã được gửi thành công đến khách hàng.'); isSending.value = false; }, 1000); } async function populatePaymentSchedules(idFilters = {}) { const paymentScheduleData = await $getdata( 'payment_schedule', undefined, { filter: { ...filter.value, ...idFilters, }, sort: 'to_date', values: 'id,penalty_paid,penalty_remain,penalty_amount,penalty_reduce,batch_date,amount_remain,paid_amount,remain_amount,code,status,txn_detail,txn_detail__transaction__date,txn_detail__transaction__product,txn_detail__transaction__product__trade_code,txn_detail__transaction__code,txn_detail__code,txn_detail__transaction__customer__code,txn_detail__transaction__customer__fullname,txn_detail__transaction__customer__email,txn_detail__transaction__customer__type__code,txn_detail__transaction__customer__legal_code,txn_detail__transaction__customer__contact_address,txn_detail__transaction__customer__address,txn_detail__transaction__customer__phone,txn_detail__transaction__policy__code,txn_detail__phase__name,type__name,from_date,to_date,amount,cycle,cycle_days,status__name,detail,entry', } ); paymentSchedules.value = paymentScheduleData; contents.value = paymentSchedules.value.map(buildContent); } async function populateEmailTemplate() { const emailTemplateData = await $getdata( 'emailtemplate', { id: templateId }, undefined, true, ); emailTemplate.value = emailTemplateData; } onMounted(async () => { await populateEmailTemplate(); populatePaymentSchedules(); }); watch(filter, () => { populatePaymentSchedules(); }, { deep: true }); const storeProp = `selectedPaymentSchedulesForEmailIn${templateId === 13 ? 'Due' : 'Overdue'}`; watch(() => $store[storeProp], (val) => { populatePaymentSchedules({ id__in: val }); }) return { contents, send, isSending }; }