const { getAllContactDb } = require("../db/contact.db"); const { InsertNotificationErrorDb, updateNotificationErrorDb, getReminderNotificationErrorByYearlyDb, getDeviceNotificationByIdDb, getReminderNotificationErrorByMonthlyDb, getReminderNotificationErrorByCustomDb, updateNotificationErrorByChanelReminderDb, } = require("../db/notification_error.db"); const { createNotificationErrorUserDb, updateNotificationErrorUserDb, getNotificationErrorByIdDb, } = require("../db/notification_error_user.db"); const { generateTokenRedirect, shortUrltiny, sendNotifikasi, } = require("../db/notification_wa.db"); const { getErrorCodeByBrandAndCodeDb } = require("../db/brand_code.db"); const { exec } = require("child_process"); const util = require("util"); const execPromise = util.promisify(exec); const fs = require('fs').promises; const path = require("path"); const { getDeviceReminderChanelDb, getDeviceReminderMonthlyDb, getDeviceReminderCustomDb, updateDeviceReminderCustomDb } = require("../db/device.db"); const { getSparepartsByMonthlyDb, getSparepartsByCustomDb } = require("../db/sparepart.db"); const baseDir = path.resolve(__dirname, '../scheduler'); const filePath = path.join(baseDir, 'reminder.json'); // const filePathLog = path.join(baseDir, 'log.json'); const cron = require('node-cron'); const dayjs = require('dayjs'); const utc = require('dayjs/plugin/utc'); const timezone = require('dayjs/plugin/timezone'); const timeZone = 'Asia/Jakarta'; dayjs.extend(utc); dayjs.extend(timezone); let reminderCronJob = null; let isCronInitialized = false; class NotifikasiWaService { constructor() { this.timeZone = 'Asia/Jakarta'; this.cronJobs = new Map(); } async saveReminder(data, isReset = false) { console.log(`Reminder dibuat`); try { let existing = []; // pastikan folder ada await fs.mkdir(path.dirname(filePath), { recursive: true }); try { const raw = await fs.readFile(filePath, 'utf-8'); if (raw) { existing = JSON.parse(raw); } } catch (err) { // file belum ada → buat file kosong await fs.writeFile(filePath, '[]', 'utf-8'); existing = []; } const now = dayjs().tz(timeZone); const newData = { ...data, // 🔹 jam (HH:mm) WIB time: now.format('HH:mm'), // 🔹 timestamp UTC (untuk DB) created_at: now.toISOString() }; let finalData; if (isReset) { // kalau reset → replace semua if ( data && ( (Array.isArray(data) && data.length > 0) || (typeof data === 'object' && !Array.isArray(data) && Object.keys(data).length > 0) ) ) { finalData = data; } else { finalData = []; } } else { existing.push(newData); finalData = existing; } await fs.writeFile(filePath, JSON.stringify(finalData, null, 2), 'utf-8'); // await this.saveReminder({ // message: `Reminder berhasil disimpan`, // data, // }); } catch (error) { next(error); } } // async saveLogReminder(data) { // console.log(`Reminder Log dibuat`); // try { // let existing = []; // // pastikan folder ada // await fs.mkdir(path.dirname(filePathLog), { recursive: true }); // try { // const raw = await fs.readFile(filePathLog, 'utf-8'); // if (raw) { // existing = JSON.parse(raw); // } // } catch (err) { // // file belum ada → buat file kosong // await fs.writeFile(filePathLog, '[]', 'utf-8'); // existing = []; // } // const now = dayjs().tz(timeZone); // const newData = { // ...data, // // 🔹 jam (HH:mm) WIB // time: now.format('HH:mm'), // // 🔹 timestamp UTC (untuk DB) // created_at: now.toISOString() // }; // await existing.push(newData); // await fs.writeFile(filePathLog, JSON.stringify(existing, null, 2), 'utf-8'); // console.log(`Reminder Log berhasil disimpan`); // } catch (error) { // console.log('Error saveReminder:', error); // } // } async onNotification(topic, message) { try { const paramDb = { limit: 100, page: 1, criteria: "", active: 1, }; // const chanel = { // "time": "2025-12-11 11:10:58", // "c_4501": 4, // "c_5501": 3, // "c_6501": 0 // } if (topic === process.env.TOPIC_COD ?? "morek") { const dataMqtt = JSON.parse(message); const resultChanel = []; Object.entries(dataMqtt).forEach(([key, value]) => { if (key.startsWith("c_")) { resultChanel.push({ chanel_id: Number(key.slice(2)), value, }); } }); const results = await getAllContactDb(paramDb); const dataUsers = results.data; for (const chanel of resultChanel) { const deviceNotification = await getDeviceNotificationByIdDb( Number(chanel.chanel_id) ); const errorCode = await getErrorCodeByBrandAndCodeDb( deviceNotification?.brand_id ?? 0, chanel.value ); const data = { error_code_id: chanel.value, error_chanel: chanel.chanel_id, is_send: false, is_delivered: false, is_read: false, is_active: true, }; const resultNotificationError = await InsertNotificationErrorDb(data); let isSendNotification = false; for (const dataUser of dataUsers) { if (dataUser.is_active) { const tokenRedirect = await generateTokenRedirect( dataUser.contact_phone, dataUser.contact_name, resultNotificationError.notification_error_id ); const encodedToken = encodeURIComponent(tokenRedirect); const shortUrl = await shortUrltiny(encodedToken); const bodyMessage = `Hai ${dataUser.contact_name || "-"},\n` + `Terjadi peringatan dengan kode ${chanel?.value ?? "-"} "${errorCode?.error_code_name ?? "" }", Chanel ${chanel?.chanel_id ?? "-"} ` + `pada device ${deviceNotification?.device_name ?? "berikut"},` + `\nSilahkan cek detail pada link :` + `${shortUrl}`; const param = { idData: resultNotificationError.notification_error_id, userPhone: dataUser.contact_phone, userName: dataUser.contact_name, bodyMessage: bodyMessage, }; const resultNotificationErrorUser = await createNotificationErrorUserDb({ notification_error_id: param.idData, contact_phone: param.userPhone, contact_name: param.userName, message_error_issue: param.bodyMessage, is_send: false, }); const resultSend = await sendNotifikasi( param.userPhone, param.bodyMessage ); await updateNotificationErrorUserDb( resultNotificationErrorUser[0].notification_error_user_id, { is_send: resultSend.success, } ); if (resultSend.success) { isSendNotification = resultSend.success; } } } await updateNotificationErrorDb( resultNotificationError.notification_error_id, { is_send: isSendNotification, is_delivered: isSendNotification, } ); } } } catch (err) { return err; } } async loadReminder() { try { const raw = await fs.readFile(filePath, 'utf-8'); return raw ? JSON.parse(raw) : []; } catch (err) { return []; // file belum ada } } async deleteReminder(id) { const data = await this.loadReminder(); id = Number(id); const newData = data?.filter(item => item.notification_log !== id); // cek apakah ada yang terhapus if (data.length === newData.length) { console.log(`Reminder ${id} tidak ditemukan`); return false; } await this.saveReminder(newData, true); await this.saveReminder({ message: `Reminder ${id} berhasil dihapus` }) console.log(`Reminder ${id} berhasil dihapus`); return true; } async deleteReminderByChanelReminder(id) { let data = await this.loadReminder(); const newData = await data?.filter(item => item.error_chanel !== id); // cek apakah ada yang terhapus if (data.length === newData.length) { console.log(`Reminder ${id} tidak ditemukan`); return false; } await this.saveReminder(newData, true); await updateNotificationErrorByChanelReminderDb( Number(id), { is_active: false, } ); await this.saveReminder({ message: `Reminder ${id} berhasil dihapus` }) console.log(`Reminder ${id} berhasil dihapus`); return true; } async runReminder() { setInterval(async () => { let data = await this.loadReminder(); if (Array.isArray(data) && data.length > 0) { // waktu sekarang (WIB) const now = dayjs().tz(timeZone); const newData = []; for (const item of data) { // skip kalau tidak aktif if (!item.active) continue; // skip kalau sudah max if (item.count >= item.max) continue; // 🔹 parse last_run (ISO dari DB) const lastRunDate = dayjs(item.last_run); if (!lastRunDate.isValid()) { throw new Error(`Invalid last_run: ${item.last_run}`); } // 🔹 hitung next_run berdasarkan interval (jam) const nextRun = lastRunDate.add(item.interval, 'hour'); // 🔹 assign hasil item.next_run = nextRun.toISOString(); // UTC (untuk DB) item.next_run_indo = nextRun.tz(timeZone).format('DD-MM-YYYY HH:mm:ss'); // 🔥 trigger check if (now.isAfter(nextRun) || now.isSame(nextRun)) { const results = await getNotificationErrorByIdDb(item.notification_log); const dataUsers = results?.data ?? []; for (const dataUser of dataUsers) { const resultSend = await sendNotifikasi( dataUser.userPhone, dataUser.bodyMessage ); await this.saveReminder(resultSend); await updateNotificationErrorUserDb( dataUser.notification_error_user_id, { is_send: resultSend.success, } ); if (resultSend.success) { await updateNotificationErrorDb( item.notification_log, { is_send: true, is_delivered: true, } ); } } item.last_run = now.toISOString(); item.last_run_indo = now.format('DD-MM-YYYY HH:mm:ss'); // WIB item.count += 1; } newData.push(item); } await this.saveReminder(newData, true); } }, 60 * 1000); } async onNotificationReminder(topic, message) { try { const paramDb = { limit: 100, page: 1, criteria: "", active: 1, }; // const chanel = { // "time": "2025-12-11 11:10:58", // "c_4501": 4, // "c_5501": 3, // "c_6501": 0 // } if (topic === process.env.TOPIC_COD_REMINDER ?? "morek") { const dataMqtt = JSON.parse(message); const resultChanel = []; Object.entries(dataMqtt).forEach(([key, value]) => { if (key.startsWith("c_")) { resultChanel.push({ chanel_id: Number(key.slice(2)), value, }); } }); // console.log('Reminder triggered A',resultChanel); for (const chanel of resultChanel) { const deviceReminder = await getDeviceReminderChanelDb( Number(chanel.chanel_id) ); if (deviceReminder?.[0]?.reminder_at) { // waktu sekarang (WIB) const now = dayjs().tz(timeZone); const currentYear = dayjs().tz(timeZone).year(); // parse dari DB const reminderAt = dayjs.tz( deviceReminder[0].reminder_at, 'YYYY-MM-DD HH:mm:ss.SSS', timeZone ); const deviceName = deviceReminder[0].device_name ?? '-'; // 🔥 compare sampai menit (tanpa tahun) const nowKey = now.format('MM-DD HH:mm'); const reminderKey = reminderAt.format('MM-DD HH:mm'); const isTriggered = nowKey >= reminderKey; if (isTriggered) { const valMqtt = chanel.value; const yearly = parseInt(valMqtt.match(/\d+/)?.[0], 10); if (!isNaN(yearly)) { const checkNotifExist = await getReminderNotificationErrorByYearlyDb(yearly ?? chanel.value, chanel.chanel_id, currentYear); if (!checkNotifExist) { const data = { error_code_id: yearly ?? chanel.value, error_chanel: chanel.chanel_id, is_send: 0, is_delivered: 0, is_read: 0, is_active: 1, message_error_issue: `reminder ${chanel.value} in ${currentYear}`, }; const resultNotificationError = await InsertNotificationErrorDb(data); const results = await getAllContactDb(paramDb); const dataUsers = results.data; let isSendNotification = false; for (const dataUser of dataUsers) { if (dataUser.is_active) { const tokenRedirect = await generateTokenRedirect( dataUser.contact_phone, dataUser.contact_name, resultNotificationError.notification_error_id ); const encodedToken = encodeURIComponent(tokenRedirect); const shortUrl = await shortUrltiny(encodedToken); const bodyMessage = `Hai ${dataUser.contact_name || "-"},\n` + `Diberitahukan bahwa terdapat sparepart pada device "${deviceName ?? "-"}" ` + `yang telah memasuki jadwal perawatan tahunan.\n` + `\nSilakan segera lakukan pengecekan dan perawatan untuk memastikan kinerja tetap optimal.`; const param = { idData: resultNotificationError.notification_error_id, userPhone: dataUser.contact_phone, userName: dataUser.contact_name, bodyMessage: bodyMessage, }; const resultNotificationErrorUser = await createNotificationErrorUserDb({ notification_error_id: param.idData, contact_phone: param.userPhone, contact_name: param.userName, message_error_issue: param.bodyMessage, is_send: false, }); const resultSend = await sendNotifikasi( param.userPhone, param.bodyMessage ); // await this.saveLogReminder({ // message: `Reminder dijalankan`, // resultSend // }) await updateNotificationErrorUserDb( resultNotificationErrorUser[0].notification_error_user_id, { is_send: resultSend.success, } ); await this.saveReminder({ notification_log: resultNotificationError.notification_error_id, error_code_id: data['error_code_id'], error_chanel: data['error_chanel'], start_at: reminderAt.format('HH:mm'), interval: 1, max: 3, active: 1, count: 0, last_run: now.toISOString(), last_run_indo: now.format('DD-MM-YYYY HH:mm:ss'), next_run: null, next_run_indo: null, message: `Reminder untuk ${deviceName} dengan reminder ${chanel.value} telah dijalankan pada ${now.format('DD-MM-YYYY HH:mm:ss')}}`, }, false); if (resultSend.success) { isSendNotification = resultSend.success; } } } await updateNotificationErrorDb( resultNotificationError.notification_error_id, { is_send: isSendNotification, is_delivered: isSendNotification, } ); } } } } } } } catch (err) { console.log('Error onNotificationReminder:', err); throw err; } } async onMonthlyReminder(deviceId) { try { const paramDb = { limit: 100, page: 1, criteria: "", active: true, }; const allDeviceReminders = await getDeviceReminderMonthlyDb(deviceId); if (!allDeviceReminders || allDeviceReminders.length === 0) { return { success: false, message: 'Tidak ada data device.' }; } const now = dayjs().tz(timeZone); const currentYear = dayjs().tz(timeZone).year(); const currentMonth = dayjs().tz(timeZone).month() + 1; for (const deviceReminder of allDeviceReminders) { if (deviceReminder?.reminder_at_monthly) { const deviceName = deviceReminder.device_name ?? '-'; const currentDeviceId = deviceReminder.device_id; const reminderAt = dayjs.tz( deviceReminder.reminder_at_monthly, timeZone ); const nowKey = now.format('MM-DD HH:mm'); const reminderKey = reminderAt.format('MM-DD HH:mm'); const isTriggered = nowKey >= reminderKey; if (isTriggered) { const monthlyCode = parseInt(`${currentMonth}`); const checkNotifExist = await getReminderNotificationErrorByMonthlyDb( monthlyCode, currentMonth, currentDeviceId ); if (checkNotifExist) { throw new Error(`Reminder untuk device ${deviceName} device id ${currentDeviceId} bulan ${currentMonth} sudah pernah dibuat.`); continue; } const spareparts = await getSparepartsByMonthlyDb(currentMonth); let sparepartList = ""; if (spareparts && spareparts.length > 0) { sparepartList = "\n\nDaftar sparepart yang perlu diperiksa:\n" + spareparts.map((sp, idx) => `${idx + 1}. ${sp.sparepart_name || 'Sparepart'}`).join('\n'); } const data = { error_code_id: monthlyCode, error_chanel: 0, is_send: 0, is_delivered: 0, is_read: 0, is_active: 1, message_error_issue: `reminder device ${deviceName} device_id: ${currentDeviceId} in month ${currentMonth}`, }; const resultNotificationError = await InsertNotificationErrorDb(data); const results = await getAllContactDb(paramDb); const dataUsers = results.data; let isSendNotification = false; for (const dataUser of dataUsers) { if (dataUser.is_active) { const tokenRedirect = await generateTokenRedirect( dataUser.contact_phone, dataUser.contact_name, resultNotificationError.notification_error_id ); const encodedToken = encodeURIComponent(tokenRedirect); const shortUrl = await shortUrltiny(encodedToken); const bodyMessage = `Hai ${dataUser.contact_name || "-"},\n\n` + `Diberitahukan bahwa terdapat sparepart pada device "${deviceName}" ` + `yang telah memasuki jadwal perawatan bulanan untuk bulan ${currentMonth} pada tahun ${currentYear}.${sparepartList}\n\n` + `Silakan segera lakukan pengecekan dan perawatan untuk memastikan kinerja tetap optimal.`; const param = { idData: resultNotificationError.notification_error_id, userPhone: dataUser.contact_phone, userName: dataUser.contact_name, bodyMessage: bodyMessage, }; const resultNotificationErrorUser = await createNotificationErrorUserDb({ notification_error_id: param.idData, contact_phone: param.userPhone, contact_name: param.userName, message_error_issue: param.bodyMessage, is_send: false, }); const resultSend = await sendNotifikasi( param.userPhone, param.bodyMessage ); await updateNotificationErrorUserDb( resultNotificationErrorUser[0].notification_error_user_id, { is_send: resultSend.success, } ); if (resultSend.success) { isSendNotification = resultSend.success; } } } await updateNotificationErrorDb( resultNotificationError.notification_error_id, { is_send: isSendNotification, is_delivered: isSendNotification, } ); await this.saveReminder({ notification_log: resultNotificationError.notification_error_id, error_code_id: data['error_code_id'], error_chanel: data['error_chanel'], start_at: reminderAt.format('HH:mm'), interval: 1, max: 3, active: 1, count: 0, last_run: now.toISOString(), last_run_indo: now.format('DD-MM-YYYY HH:mm:ss'), next_run: null, next_run_indo: null, message: `Reminder untuk ${deviceName} bulan ${currentMonth} telah dijalankan.`, }, false); } } } return { success: true, message: "Reminder has been sent." }; } catch (err) { console.log('Error on Monthly Reminder:', err); throw err; } } async onCustomReminder(deviceId, inputStartDate, inputEndDate) { try { const paramDb = { limit: 100, page: 1, criteria: "", active: true, }; if (!inputStartDate || !inputEndDate) { return { success: false, message: 'start_date dan end_date harus diisi. Format: YYYY-MM-DD HH:mm:ss' }; } const startDateObj = dayjs.tz(inputStartDate, this.timeZone); const endDateObj = dayjs.tz(inputEndDate, this.timeZone); if (!startDateObj.isValid() || !endDateObj.isValid()) { return { success: false, message: 'Format tanggal tidak valid. Gunakan format: YYYY-MM-DD HH:mm:ss' }; } if (!endDateObj.isAfter(startDateObj)) { return { success: false, message: 'end_date harus lebih besar dari start_date' }; } const deviceData = await getDeviceReminderCustomDb(deviceId); if (!deviceData || deviceData.length === 0) { return { success: false, message: 'Device tidak ditemukan' }; } const device = deviceData[0]; const deviceName = device.device_name ?? '-'; const currentDeviceId = device.device_id; const updateResult = await updateDeviceReminderCustomDb( currentDeviceId, inputStartDate, inputEndDate ); if (!updateResult || updateResult.length === 0) { return { success: false, message: 'Gagal menyimpan start_date dan end_date' }; } console.log(`Berhasil update start_date dan end_date untuk device ${deviceName}`); this.stopCronJob(currentDeviceId); const scheduleResult = await this.scheduleReminderJob( currentDeviceId, deviceName, startDateObj, endDateObj ); const now = dayjs().tz(this.timeZone); const isEndDatePassed = now.isAfter(endDateObj) || now.isSame(endDateObj); if (isEndDatePassed) { console.log(`⏰ End_date sudah terlewati, mengirim reminder sekarang untuk ${deviceName}`); const sendResult = await this.sendReminderNow( currentDeviceId, deviceName, startDateObj, endDateObj ); return { success: true, message: 'Reminder berhasil dijadwalkan dan dikirim sekarang', schedule: scheduleResult, sendResult: sendResult }; } return { success: true, message: `Reminder berhasil dijadwalkan untuk device ${deviceName}`, schedule: scheduleResult, nextRun: scheduleResult.nextRun }; } catch (err) { console.error('Error on Custom Reminder:', err); throw err; } } async scheduleReminderJob(deviceId, deviceName, startDateObj, endDateObj) { try { const now = dayjs().tz(this.timeZone); const targetTime = endDateObj.clone() .set('second', 0) .set('millisecond', 0); const nextRun = targetTime.format('YYYY-MM-DD HH:mm:ss'); console.log(`Reminder untuk ${deviceName} dijadwalkan pada ${nextRun}`); console.log(`Start Date: ${startDateObj.format('YYYY-MM-DD HH:mm:ss')}`); console.log(`End Date: ${endDateObj.format('YYYY-MM-DD HH:mm:ss')}`); const timeoutId = setTimeout(async () => { console.log(`Waktu pengiriman tiba untuk ${deviceName} pada ${dayjs().tz(this.timeZone).format('YYYY-MM-DD HH:mm:ss')}`); try { const now = dayjs().tz(this.timeZone); const isEndDatePassed = now.isAfter(endDateObj) || now.isSame(endDateObj); if (isEndDatePassed) { await this.sendReminderNow(deviceId, deviceName, startDateObj, endDateObj); } else { console.log(`End_date untuk ${deviceName} belum terlewati, menunggu...`); this.rescheduleReminderCheck(deviceId, deviceName, startDateObj, endDateObj); } } catch (error) { console.error(`Gagal mengirim reminder untuk ${deviceName}:`, error); } }); this.cronJobs.set(deviceId, { timeoutId, nextRun, startDate: startDateObj.format('YYYY-MM-DD HH:mm:ss'), endDate: endDateObj.format('YYYY-MM-DD HH:mm:ss') }); const endHour = endDateObj.hour(); const endMinute = endDateObj.minute(); const cronExpression = `${endMinute} ${endHour} * * *`; console.log(`Backup cron akan berjalan setiap hari pada ${endHour}:${endMinute}`); const cronTask = cron.schedule(cronExpression, async () => { console.log(`Backup cron job berjalan untuk ${deviceName} pada ${dayjs().tz(this.timeZone).format('YYYY-MM-DD HH:mm:ss')}`); try { const now = dayjs().tz(this.timeZone); const isEndDatePassed = now.isAfter(endDateObj) || now.isSame(endDateObj); if (isEndDatePassed) { const currentMonth = now.month() + 1; const checkNotifExist = await getReminderNotificationErrorByCustomDb( parseInt(`${currentMonth}`), currentMonth, deviceId ); if (!checkNotifExist) { await this.sendReminderNow(deviceId, deviceName, startDateObj, endDateObj); } else { console.log(`Reminder untuk ${deviceName} sudah pernah dikirim`); } cronTask.stop(); this.cronJobs.delete(deviceId); this.cronJobs.delete(`cron_${deviceId}`); } else { console.log(`Menunggu end_date untuk ${deviceName}: ${endDateObj.format('YYYY-MM-DD HH:mm:ss')}`); } } catch (error) { console.error(`Backup cron job gagal untuk ${deviceName}:`, error); } }, { timezone: this.timeZone }); this.cronJobs.set(`cron_${deviceId}`, { cronTask, nextRun, startDate: startDateObj.format('YYYY-MM-DD HH:mm:ss'), endDate: endDateObj.format('YYYY-MM-DD HH:mm:ss') }); return { nextRun, targetDate: targetTime.format('YYYY-MM-DD HH:mm:ss'), endDate: endDateObj.format('YYYY-MM-DD HH:mm:ss'), cronExpression: cronExpression }; } catch (error) { console.error('Error scheduling reminder:', error); throw error; } } async rescheduleReminderCheck(deviceId, deviceName, startDateObj, endDateObj) { const checkInterval = setInterval(async () => { const now = dayjs().tz(this.timeZone); const isEndDatePassed = now.isAfter(endDateObj) || now.isSame(endDateObj); if (isEndDatePassed) { clearInterval(checkInterval); console.log(`End_date terlewati untuk ${deviceName}, mengirim reminder...`); try { await this.sendReminderNow(deviceId, deviceName, startDateObj, endDateObj); } catch (error) { console.error(`Gagal mengirim reminder untuk ${deviceName}:`, error); } } }, 60000); this.cronJobs.set(`interval_${deviceId}`, { intervalId: checkInterval }); } async sendReminderNow(deviceId, deviceName, startDateObj, endDateObj) { try { console.log(`Mengirim reminder untuk ${deviceName}`); const now = dayjs().tz(this.timeZone); const currentMonth = now.month() + 1; const monthlyCode = parseInt(`${currentMonth}`); const checkNotifExist = await getReminderNotificationErrorByCustomDb( monthlyCode, currentMonth, deviceId ); if (checkNotifExist) { console.log(`Reminder untuk ${deviceName} sudah pernah dibuat`); return { success: false, message: `Reminder untuk device ${deviceName} sudah pernah dibuat.` }; } const data = { error_code_id: monthlyCode, error_chanel: 0, is_send: 0, is_delivered: 0, is_read: 0, is_active: 1, message_error_issue: `reminder custom device ${deviceName} in month ${currentMonth}`, }; const resultNotificationError = await InsertNotificationErrorDb(data); const paramDb = { limit: 100, page: 1, criteria: "", active: true, }; const results = await getAllContactDb(paramDb); const dataUsers = results.data; let isSendNotification = false; for (const dataUser of dataUsers) { if (dataUser.is_active) { const tokenRedirect = await generateTokenRedirect( dataUser.contact_phone, dataUser.contact_name, resultNotificationError.notification_error_id ); const encodedToken = encodeURIComponent(tokenRedirect); const shortUrl = await shortUrltiny(encodedToken); const bodyMessage = `Hai ${dataUser.contact_name || "-"},\n\n` + `Diberitahukan bahwa terdapat sparepart pada device "${deviceName}" ` + `yang telah memasuki jadwal perawatan.\n\n` + `Silakan segera lakukan pengecekan dan perawatan untuk memastikan kinerja tetap optimal.\n\n` + `Detail Periode Perawatan:\n` + `Start Date: ${startDateObj.format('DD-MM-YYYY HH:mm')}\n` + `End Date: ${endDateObj.format('DD-MM-YYYY HH:mm')}\n` const param = { idData: resultNotificationError.notification_error_id, userPhone: dataUser.contact_phone, userName: dataUser.contact_name, bodyMessage: bodyMessage, }; const resultNotificationErrorUser = await createNotificationErrorUserDb({ notification_error_id: param.idData, contact_phone: param.userPhone, contact_name: param.userName, message_error_issue: param.bodyMessage, is_send: false, }); const resultSend = await sendNotifikasi( param.userPhone, param.bodyMessage ); if (resultNotificationErrorUser && resultNotificationErrorUser.length > 0) { await updateNotificationErrorUserDb( resultNotificationErrorUser[0].notification_error_user_id, { is_send: resultSend.success, } ); } if (resultSend.success) { isSendNotification = resultSend.success; } } } await updateNotificationErrorDb( resultNotificationError.notification_error_id, { is_send: isSendNotification, is_delivered: isSendNotification, } ); await this.saveReminder({ notification_log: resultNotificationError.notification_error_id, error_code_id: data['error_code_id'], error_chanel: data['error_chanel'], start_at: now.format('HH:mm'), interval: 1, max: 3, active: 1, count: 0, last_run: now.toISOString(), last_run_indo: now.format('DD-MM-YYYY HH:mm:ss'), next_run: null, next_run_indo: null, message: `Reminder untuk ${deviceName} bulan ${currentMonth} telah dijalankan.`, }, false); this.stopCronJob(deviceId); console.log(`Reminder berhasil dikirim untuk ${deviceName}`); return { success: true, message: "Reminder has been sent." }; } catch (error) { console.error('Error sending reminder now:', error); throw error; } } stopCronJob(deviceId) { const jobData = this.cronJobs.get(deviceId); if (jobData && jobData.timeoutId) { clearTimeout(jobData.timeoutId); console.log(`Timeout dihentikan untuk device ${deviceId}`); } const intervalData = this.cronJobs.get(`interval_${deviceId}`); if (intervalData && intervalData.intervalId) { clearInterval(intervalData.intervalId); console.log(`Interval dihentikan untuk device ${deviceId}`); } const cronData = this.cronJobs.get(`cron_${deviceId}`); if (cronData && cronData.cronTask) { cronData.cronTask.stop(); console.log(`Cron job dihentikan untuk device ${deviceId}`); } this.cronJobs.delete(deviceId); this.cronJobs.delete(`cron_${deviceId}`); this.cronJobs.delete(`interval_${deviceId}`); } getActiveCronJobs() { const activeJobs = []; for (const [key, value] of this.cronJobs) { if (!key.startsWith('cron_') && !key.startsWith('interval_')) { activeJobs.push({ deviceId: key, nextRun: value.nextRun, startDate: value.startDate, endDate: value.endDate }); } } return activeJobs; } stopAllCronJobs() { for (const [key, value] of this.cronJobs) { if (value.timeoutId) { clearTimeout(value.timeoutId); } if (value.intervalId) { clearInterval(value.intervalId); } if (value.cronTask) { value.cronTask.stop(); } } this.cronJobs.clear(); console.log('Semua cron job dihentikan'); } async restartWhatsapp() { try { const processName = "cod-whatsapp-notif"; const { stdout } = await execPromise("pm2 jlist"); const cleanJSON = stdout.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, ''); const startJSON = cleanJSON.indexOf('['); const endJSON = cleanJSON.lastIndexOf(']') + 1; if (startJSON === -1 || endJSON === 0) { throw new Error(`Terminal tidak mengembalikan JSON yang valid. Output: ${stdout}`); } const index = cleanJSON.slice(startJSON, endJSON); const processes = JSON.parse(index); const waProcess = processes.find((p) => p.name === processName); if (!waProcess) throw new Error(`PM2 proses dengan nama "${processName}" tidak ditemukan!`); const waProcessId = waProcess.pm_id; const pathDelete = waProcess.pm2_env.pm_cwd; // console.log(`stop proses id: ${waProcessId}`); await execPromise(`pm2 stop ${waProcessId}`); const pathFolderDelete = [ path.join(pathDelete, ".wwebjs_auth"), path.join(pathDelete, ".wwebjs_cache"), ]; // console.log(`path: ${pathDelete}`); for (const dir of pathFolderDelete) { try { await fs.access(dir); console.log(`path folder ditemukan, menghapus: ${dir}`); await fs.rm(dir, { recursive: true, force: true }); } catch (fsErr) { if (fsErr.code !== 'ENOENT') { console.error(`Gagal memproses folder ${dir}:`, fsErr.message); } } } // console.log(`start proses id: ${waProcessId}`); await execPromise(`pm2 start ${waProcessId}`); return { success: true, message: "WhatsApp has been restarted." }; } catch (err) { return { success: false, error: err.message || err }; } } } module.exports = new NotifikasiWaService();