From 73b05c5d0d511a52b4b93cb760968a1c23e7dd50 Mon Sep 17 00:00:00 2001 From: Muhammad Afif Date: Mon, 1 Jun 2026 14:50:35 +0700 Subject: [PATCH 1/5] repair api resend wa & delete log.json --- scheduler/log.json | 1 - scheduler/reminder.json | 20 +++- services/notifikasi-wa.service.js | 170 ++++++++++++++++-------------- 3 files changed, 109 insertions(+), 82 deletions(-) delete mode 100644 scheduler/log.json diff --git a/scheduler/log.json b/scheduler/log.json deleted file mode 100644 index 0637a08..0000000 --- a/scheduler/log.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/scheduler/reminder.json b/scheduler/reminder.json index 0637a08..7f82023 100644 --- a/scheduler/reminder.json +++ b/scheduler/reminder.json @@ -1 +1,19 @@ -[] \ No newline at end of file +[ + { + "notification_log": 851, + "error_code_id": 1, + "error_chanel": 2025, + "start_at": "02:10", + "interval": 1, + "max": 3, + "active": 1, + "count": 0, + "last_run": "2026-05-31T17:41:09.191Z", + "last_run_indo": "01-06-2026 00:41:09", + "next_run": "2026-05-31T18:41:09.191Z", + "next_run_indo": "01-06-2026 01:41:09", + "message": "Reminder untuk PLC Compressor B dengan reminder 1YEAR telah dijalankan pada 01-06-2026 00:41:09, hasil pengiriman: sukses", + "time": "00:41", + "created_at": "2026-05-31T17:41:18.802Z" + } +] \ No newline at end of file diff --git a/services/notifikasi-wa.service.js b/services/notifikasi-wa.service.js index d66e778..f50183b 100644 --- a/services/notifikasi-wa.service.js +++ b/services/notifikasi-wa.service.js @@ -27,7 +27,7 @@ const { getDeviceReminderChanelDb } = require("../db/device.db"); const baseDir = path.resolve(__dirname, '../scheduler'); const filePath = path.join(baseDir, 'reminder.json'); -const filePathLog = path.join(baseDir, 'log.json'); +// const filePathLog = path.join(baseDir, 'log.json'); const dayjs = require('dayjs'); const utc = require('dayjs/plugin/utc'); @@ -41,7 +41,7 @@ dayjs.extend(timezone); class NotifikasiWaService { async saveReminder(data, isReset = false) { - console.log(`Reminder proses dibuat`); + console.log(`Reminder dibuat`); try { let existing = []; @@ -95,58 +95,57 @@ class NotifikasiWaService { await fs.writeFile(filePath, JSON.stringify(finalData, null, 2), 'utf-8'); - await this.saveLogReminder({ - message: `Reminder berhasil disimpan`, - data, - }); + // await this.saveReminder({ + // message: `Reminder berhasil disimpan`, + // data, + // }); - console.log(`Reminder berhasil disimpan`); } catch (error) { - console.log('Error saveReminder:', error); + next(error); } } - async saveLogReminder(data) { - console.log(`Reminder Log dibuat`); + // async saveLogReminder(data) { + // console.log(`Reminder Log dibuat`); - try { - let existing = []; + // try { + // let existing = []; - // pastikan folder ada - await fs.mkdir(path.dirname(filePathLog), { recursive: true }); + // // 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 = []; - } + // 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 now = dayjs().tz(timeZone); - const newData = { - ...data, + // const newData = { + // ...data, - // 🔹 jam (HH:mm) WIB - time: now.format('HH:mm'), + // // 🔹 jam (HH:mm) WIB + // time: now.format('HH:mm'), - // 🔹 timestamp UTC (untuk DB) - created_at: now.toISOString() - }; + // // 🔹 timestamp UTC (untuk DB) + // created_at: now.toISOString() + // }; - await existing.push(newData); + // await existing.push(newData); - await fs.writeFile(filePathLog, JSON.stringify(existing, null, 2), 'utf-8'); + // await fs.writeFile(filePathLog, JSON.stringify(existing, null, 2), 'utf-8'); - console.log(`Reminder Log berhasil disimpan`); - } catch (error) { - console.log('Error saveReminder:', error); - } - } + // console.log(`Reminder Log berhasil disimpan`); + // } catch (error) { + // console.log('Error saveReminder:', error); + // } + // } async onNotification(topic, message) { try { @@ -298,7 +297,7 @@ class NotifikasiWaService { await this.saveReminder(newData, true); - await this.saveLogReminder({ + await this.saveReminder({ message: `Reminder ${id} berhasil dihapus` }) @@ -326,7 +325,7 @@ class NotifikasiWaService { } ); - await this.saveLogReminder({ + await this.saveReminder({ message: `Reminder ${id} berhasil dihapus` }) @@ -378,7 +377,7 @@ class NotifikasiWaService { dataUser.bodyMessage ); - await this.saveLogReminder(resultSend); + await this.saveReminder(resultSend); await updateNotificationErrorUserDb( dataUser.notification_error_user_id, @@ -491,21 +490,6 @@ class NotifikasiWaService { const resultNotificationError = await InsertNotificationErrorDb(data); - 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 - }, false); - const results = await getAllContactDb(paramDb); const dataUsers = results.data; @@ -554,10 +538,26 @@ class NotifikasiWaService { param.bodyMessage ); - await this.saveLogReminder({ - message: `Reminder dijalankan`, - resultSend - }) + 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')}, hasil pengiriman: ${resultSend.success ? 'sukses' : 'gagal'}`, + }, false); + + // await this.saveLogReminder({ + // message: `Reminder dijalankan`, + // resultSend + // }) await updateNotificationErrorUserDb( resultNotificationErrorUser[0].notification_error_user_id, @@ -593,25 +593,29 @@ class NotifikasiWaService { async restartWhatsapp() { try { - const processName = "Whatsapp-API-notification" && "cod-whatsapp-notif"; + + const processName = "cod-whatsapp-notif"; const { stdout } = await execPromise("pm2 jlist"); - const processes = JSON.parse(stdout); + 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 ${processName} not found`); + if (!waProcess) throw new Error(`PM2 proses dengan nama "${processName}" tidak ditemukan!`); const waProcessId = waProcess.pm_id; const pathDelete = waProcess.pm2_env.pm_cwd; - // const execLogs = (cmd) => { - // const p = exec(cmd); - // p.stdout.pipe(process.stdout); - // p.stderr.pipe(process.stderr); - // return new Promise((res) => p.on("exit", res)); - // }; - - // console.log(`stop proses id: ${waProcessId}`) - await execPromise(`start powershell -Command "pm2 stop ${waProcessId}"`); + // console.log(`stop proses id: ${waProcessId}`); + await execPromise(`pm2 stop ${waProcessId}`); const pathFolderDelete = [ path.join(pathDelete, ".wwebjs_auth"), @@ -620,19 +624,25 @@ class NotifikasiWaService { // console.log(`path: ${pathDelete}`); - pathFolderDelete.forEach((dir) => { - if (fs.existsSync(dir)) { - // console.log(`path folder: ${dir}`); - fs.rmSync(dir, { recursive: true, force: true }); + 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(`start powershell -Command "pm2 start ${waProcessId}"`); + await execPromise(`pm2 start ${waProcessId}`); return { success: true, message: "WhatsApp has been restarted." }; } catch (err) { - return err; + return { success: false, error: err.message || err }; } } } -- 2.49.1 From 3d53bf47020ebfcce024a6549dbc0142ba97b829 Mon Sep 17 00:00:00 2001 From: Muhammad Afif Date: Wed, 3 Jun 2026 15:08:55 +0700 Subject: [PATCH 2/5] fix: is_send & is_delivered in onNotificationReminder --- scheduler/reminder.json | 20 +------------------- services/notifikasi-wa.service.js | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 31 deletions(-) diff --git a/scheduler/reminder.json b/scheduler/reminder.json index 7f82023..0637a08 100644 --- a/scheduler/reminder.json +++ b/scheduler/reminder.json @@ -1,19 +1 @@ -[ - { - "notification_log": 851, - "error_code_id": 1, - "error_chanel": 2025, - "start_at": "02:10", - "interval": 1, - "max": 3, - "active": 1, - "count": 0, - "last_run": "2026-05-31T17:41:09.191Z", - "last_run_indo": "01-06-2026 00:41:09", - "next_run": "2026-05-31T18:41:09.191Z", - "next_run_indo": "01-06-2026 01:41:09", - "message": "Reminder untuk PLC Compressor B dengan reminder 1YEAR telah dijalankan pada 01-06-2026 00:41:09, hasil pengiriman: sukses", - "time": "00:41", - "created_at": "2026-05-31T17:41:18.802Z" - } -] \ No newline at end of file +[] \ No newline at end of file diff --git a/services/notifikasi-wa.service.js b/services/notifikasi-wa.service.js index f50183b..6f567c4 100644 --- a/services/notifikasi-wa.service.js +++ b/services/notifikasi-wa.service.js @@ -538,6 +538,18 @@ class NotifikasiWaService { 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'], @@ -554,18 +566,6 @@ class NotifikasiWaService { message: `Reminder untuk ${deviceName} dengan reminder ${chanel.value} telah dijalankan pada ${now.format('DD-MM-YYYY HH:mm:ss')}, hasil pengiriman: ${resultSend.success ? 'sukses' : 'gagal'}`, }, false); - // await this.saveLogReminder({ - // message: `Reminder dijalankan`, - // resultSend - // }) - - await updateNotificationErrorUserDb( - resultNotificationErrorUser[0].notification_error_user_id, - { - is_send: resultSend.success, - } - ); - if (resultSend.success) { isSendNotification = resultSend.success; } -- 2.49.1 From 40de826da9c46cdc1464e8e3814fbb258140a584 Mon Sep 17 00:00:00 2001 From: Muhammad Afif Date: Fri, 5 Jun 2026 09:17:45 +0700 Subject: [PATCH 3/5] add: reminder whatsapp sparepart monthly --- db/device.db.js | 17 ++- db/notification_error.db.js | 18 +++ db/sparepart.db.js | 21 ++- routes/notifikasi-wa.route.js | 10 ++ services/notification_error.service.js | 17 ++- services/notifikasi-wa.service.js | 179 ++++++++++++++++++++++++- 6 files changed, 249 insertions(+), 13 deletions(-) diff --git a/db/device.db.js b/db/device.db.js index b2b9968..390e80f 100644 --- a/db/device.db.js +++ b/db/device.db.js @@ -139,11 +139,26 @@ const getDeviceReminderChanelDb = async (id) => { return result.recordset; }; +const getDeviceReminderMonthlyDb = async (id) => { + const queryText = ` + SELECT + a.*, + b.brand_name, + COALESCE(a.device_code, '') + ' - ' + COALESCE(a.device_name, '') AS device_code_name + FROM m_device a + LEFT JOIN m_brands b ON a.brand_id = b.brand_id + WHERE a.device_id = $1 AND a.deleted_at IS NULL AND a.reminder_at_monthly IS NOT NULL + `; + const result = await pool.query(queryText, [id]); + return result.recordset; +}; + module.exports = { getAllDevicesDb, getDeviceByIdDb, createDeviceDb, updateDeviceDb, deleteDeviceDb, - getDeviceReminderChanelDb + getDeviceReminderChanelDb, + getDeviceReminderMonthlyDb }; diff --git a/db/notification_error.db.js b/db/notification_error.db.js index 3e7505f..3c52b5f 100644 --- a/db/notification_error.db.js +++ b/db/notification_error.db.js @@ -255,6 +255,23 @@ const getReminderNotificationErrorByYearlyDb = async (errorCode, chanel, year) = return result.recordset[0]; }; +const getReminderNotificationErrorByMonthlyDb = async (errorCode, monthly) => { + const queryText = ` + SELECT a.* + + FROM notification_error a + + WHERE a.error_code_id = $1 + AND MONTH(a.created_at) = $2 + AND a.message_error_issue LIKE 'reminder%' + AND a.is_active = 1 + AND a.deleted_at IS NULL + `; + + const result = await pool.query(queryText, [errorCode, monthly]); + return result.recordset[0]; +}; + module.exports = { getNotificationByIdDb, getDeviceNotificationByIdDb, @@ -264,6 +281,7 @@ module.exports = { getUsersNotificationErrorDb, getDeviceChannelReminder, getReminderNotificationErrorByYearlyDb, + getReminderNotificationErrorByMonthlyDb, updateNotificationErrorByChanelReminderDb }; diff --git a/db/sparepart.db.js b/db/sparepart.db.js index d6d50f0..f27b038 100644 --- a/db/sparepart.db.js +++ b/db/sparepart.db.js @@ -197,6 +197,24 @@ const getSparepartsByYearlyDb = async (yearly) => { return result.recordset; }; +const getSparepartsByMonthlyDb = async (monthly) => { + + const queryText = ` + SELECT * + FROM m_sparepart + WHERE deleted_at IS NULL + AND EXISTS ( + SELECT 1 + FROM OPENJSON(sparepart_monthly) + WHERE value = $1 + ) + `; + + const result = await pool.query(queryText, [monthly]); + + return result.recordset; +}; + module.exports = { getAllSparepartDb, getSparepartByIdDb, @@ -205,5 +223,6 @@ module.exports = { createSparepartDb, updateSparepartDb, deleteSparepartDb, - getSparepartsByYearlyDb + getSparepartsByYearlyDb, + getSparepartsByMonthlyDb }; diff --git a/routes/notifikasi-wa.route.js b/routes/notifikasi-wa.route.js index 8fd7b3a..3c7ce46 100644 --- a/routes/notifikasi-wa.route.js +++ b/routes/notifikasi-wa.route.js @@ -11,4 +11,14 @@ router.post('/restart-wa', async (req, res) => { } }); +router.post('/reminder-sparepart/:id', async (req, res) => { + try { + const { id } = req.params; + const result = await NotifikasiWaService.onMonthlySparepartReminder(id); + return res.status(200).json(result); + } catch (error) { + return res.status(500).json(error); + } +}); + module.exports = router; \ No newline at end of file diff --git a/services/notification_error.service.js b/services/notification_error.service.js index 03f423a..638ab42 100644 --- a/services/notification_error.service.js +++ b/services/notification_error.service.js @@ -31,7 +31,7 @@ const { } = require("../db/notification_wa.db"); const { ErrorHandler } = require("../helpers/error"); -const { getSparepartsByYearlyDb } = require("../db/sparepart.db"); +const { getSparepartsByYearlyDb, getSparepartsByMonthlyDb } = require("../db/sparepart.db"); const notifikasiWaService = require("./notifikasi-wa.service"); @@ -140,11 +140,18 @@ class NotificationService { notification.is_reminder = false; notification.spareparts_reminder = []; if (notification.message_error_issue) { - const sparepartsReminder = await getSparepartsByYearlyDb(notification.error_code_id); - notification.spareparts_reminder = sparepartsReminder ?? []; + const [sparepartsYearly, sparepartsMonthly] = await Promise.all([ + getSparepartsByYearlyDb(notification.error_code_id), + getSparepartsByMonthlyDb(notification.error_code_id) + ]); + + notification.spareparts_reminder = [ + ...(sparepartsYearly ?? []), + ...(sparepartsMonthly ?? []) + ]; + notification.is_reminder = true; - } return notification; @@ -172,7 +179,7 @@ class NotificationService { if (!notification.is_read) { const updateStatus = await updateNotificationErrorDb( notification_error_id, - { is_read: true} + { is_read: true } ); if (!updateStatus) { diff --git a/services/notifikasi-wa.service.js b/services/notifikasi-wa.service.js index 6f567c4..35f1828 100644 --- a/services/notifikasi-wa.service.js +++ b/services/notifikasi-wa.service.js @@ -3,6 +3,8 @@ const { InsertNotificationErrorDb, updateNotificationErrorDb, getReminderNotificationErrorByYearlyDb, + getDeviceNotificationByIdDb, + getReminderNotificationErrorByMonthlyDb, updateNotificationErrorByChanelReminderDb, } = require("../db/notification_error.db"); const { @@ -16,15 +18,14 @@ const { sendNotifikasi, } = require("../db/notification_wa.db"); const { getErrorCodeByBrandAndCodeDb } = require("../db/brand_code.db"); -const { getDeviceNotificationByIdDb } = require("../db/notification_error.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 } = require("../db/device.db"); - +const { getDeviceReminderChanelDb, getDeviceReminderMonthlyDb } = require("../db/device.db"); +const { getSparepartsByMonthlyDb } = require("../db/sparepart.db"); const baseDir = path.resolve(__dirname, '../scheduler'); const filePath = path.join(baseDir, 'reminder.json'); // const filePathLog = path.join(baseDir, 'log.json'); @@ -497,7 +498,7 @@ class NotifikasiWaService { let isSendNotification = false; for (const dataUser of dataUsers) { - if (dataUser.is_active) { + if (dataUser.is_active || dataUser.is_active === 1) { const tokenRedirect = await generateTokenRedirect( dataUser.contact_phone, @@ -513,8 +514,7 @@ class NotifikasiWaService { `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.` + - `\nDetail sparepart dapat dilihat pada link berikut:\n${shortUrl}`; + `\nSilakan segera lakukan pengecekan dan perawatan untuk memastikan kinerja tetap optimal.`; const param = { idData: resultNotificationError.notification_error_id, @@ -591,6 +591,173 @@ class NotifikasiWaService { } } + async onMonthlySparepartReminder(deviceId) { + try { + const paramDb = { + limit: 100, + page: 1, + criteria: "", + active: 1, + }; + + 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 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 + ); + + if (!checkNotifExist) { + + 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} 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 || dataUser.is_active === 1) { + + return { success: false, message: 'Tidak ada data user yang aktif.' }; + + 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 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 bulan ${currentMonth} telah dijalankan pada ${now.format('DD-MM-YYYY HH:mm:ss')}, hasil pengiriman: ${resultSend.success ? 'sukses' : 'gagal'}`, + }, 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 restartWhatsapp() { try { -- 2.49.1 From ab5fe3e9c43359ff08b158fc0cbf46dd80ddd9c0 Mon Sep 17 00:00:00 2001 From: Muhammad Afif Date: Fri, 5 Jun 2026 13:42:30 +0700 Subject: [PATCH 4/5] add: columns / field email in contact --- db/contact.db.js | 1 + validate/contact.schema.js | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/db/contact.db.js b/db/contact.db.js index 2b1cff5..1b653ee 100644 --- a/db/contact.db.js +++ b/db/contact.db.js @@ -25,6 +25,7 @@ const getAllContactDb = async (searchParams = {}) => { { column: "a.contact_name", param: searchParams.name, type: "string" }, { column: "a.contact_type", param: searchParams.code, type: "string" }, { column: "a.is_active", param: searchParams.active, type: "boolean" }, + { column: "a.email", param: searchParams.email, type: "string" }, ], queryParams ); diff --git a/validate/contact.schema.js b/validate/contact.schema.js index 04aaa0a..1f17a59 100644 --- a/validate/contact.schema.js +++ b/validate/contact.schema.js @@ -13,7 +13,8 @@ const insertContactSchema = Joi.object({ "Phone number must be a valid Indonesian number in format +628XXXXXXXXX", }), is_active: Joi.boolean().required(), - contact_type: Joi.string().max(255).optional().allow(null) + contact_type: Joi.string().max(255).optional().allow(null), + email: Joi.string().email().optional() }); const updateContactSchema = Joi.object({ @@ -26,7 +27,8 @@ const updateContactSchema = Joi.object({ "Phone number must be a valid Indonesian number in format +628XXXXXXXXX", }), is_active: Joi.boolean().optional(), - contact_type: Joi.string().max(255).optional().allow(null) + contact_type: Joi.string().max(255).optional().allow(null), + email: Joi.string().email().required() }); module.exports = { -- 2.49.1 From 69ba363c14e286f423179171fcfc5722b5c60e70 Mon Sep 17 00:00:00 2001 From: Muhammad Afif Date: Fri, 5 Jun 2026 14:09:29 +0700 Subject: [PATCH 5/5] repair: change email schema optional to required --- validate/contact.schema.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/validate/contact.schema.js b/validate/contact.schema.js index 1f17a59..6671330 100644 --- a/validate/contact.schema.js +++ b/validate/contact.schema.js @@ -14,7 +14,7 @@ const insertContactSchema = Joi.object({ }), is_active: Joi.boolean().required(), contact_type: Joi.string().max(255).optional().allow(null), - email: Joi.string().email().optional() + email: Joi.string().email().required() }); const updateContactSchema = Joi.object({ -- 2.49.1