From acb084475d4708d42217ddbe7345f8a0daf54f4d Mon Sep 17 00:00:00 2001 From: Fachba Date: Sun, 3 May 2026 19:26:51 +0700 Subject: [PATCH] fix: update notification error handling to include new update method and improve reminder deletion logic --- config/index.js | 2 +- db/notification_error.db.js | 19 +- services/device.service.js | 13 ++ services/notification_error.service.js | 2 +- services/notifikasi-wa.service.js | 245 +++++++++++++++++-------- 5 files changed, 198 insertions(+), 83 deletions(-) diff --git a/config/index.js b/config/index.js index 30e7a9b..ac0cae2 100644 --- a/config/index.js +++ b/config/index.js @@ -57,7 +57,7 @@ async function query(text, params = []) { // Ubah $1, $2 jadi @p1, @p2 const sqlText = text.replace(/\$(\d+)/g, (_, num) => `@p${num}`); - console.log(sqlText, params); + // console.log(sqlText, params); return request.query(sqlText); } diff --git a/db/notification_error.db.js b/db/notification_error.db.js index 102d17f..3e7505f 100644 --- a/db/notification_error.db.js +++ b/db/notification_error.db.js @@ -20,7 +20,7 @@ const getNotificationByIdDb = async (id) => { COALESCE(d.device_code, f.device_code) AS device_code, COALESCE(d.device_name, f.device_name) AS device_name, COALESCE(d.device_location, f.device_location) AS device_location, - COALESCE(d.listen_channel, f.listen_channel_reminder) AS listen_channel + COALESCE(d.listen_channel, f.listen_channel_reminder) AS listen_channel, e.brand_name, e.brand_id @@ -199,6 +199,19 @@ const updateNotificationErrorDb = async (id, data) => { return getNotificationByIdDb(id); }; +const updateNotificationErrorByChanelReminderDb = async (id, data) => { + const store = { ...data }; + const whereData = { error_chanel: id }; + + const { query: queryText, values } = pool.buildDynamicUpdate( + "notification_error", + store, + whereData + ); + + return await pool.query(`${queryText} AND deleted_at IS NULL`, values); +}; + const getUsersNotificationErrorDb = async (id) => { const queryText = ` SELECT @@ -234,6 +247,7 @@ const getReminderNotificationErrorByYearlyDb = async (errorCode, chanel, year) = AND a.error_chanel = $2 AND YEAR(a.created_at) = $3 AND a.message_error_issue LIKE 'reminder%' + AND a.is_active = 1 AND a.deleted_at IS NULL `; @@ -249,6 +263,7 @@ module.exports = { updateNotificationErrorDb, getUsersNotificationErrorDb, getDeviceChannelReminder, - getReminderNotificationErrorByYearlyDb + getReminderNotificationErrorByYearlyDb, + updateNotificationErrorByChanelReminderDb }; diff --git a/services/device.service.js b/services/device.service.js index 978b336..9fe5bc6 100644 --- a/services/device.service.js +++ b/services/device.service.js @@ -5,7 +5,9 @@ const { updateDeviceDb, deleteDeviceDb } = require('../db/device.db'); +const { updateNotificationErrorByChanelReminderDb } = require('../db/notification_error.db'); const { ErrorHandler } = require('../helpers/error'); +const notifikasiWaService = require('./notifikasi-wa.service'); class DeviceService { // Get all devices @@ -59,6 +61,17 @@ class DeviceService { throw new ErrorHandler(404, 'Device not found'); } + const updateStatus = await updateNotificationErrorByChanelReminderDb( + Number(dataExist[0].listen_channel_reminder), + { is_active: 0, userId: data.userId } + ); + + await notifikasiWaService.deleteReminderByChanelReminder(Number(dataExist[0].listen_channel_reminder)); + + if (!updateStatus) { + throw new ErrorHandler(500, 'Failed to update notification'); + } + const result = await updateDeviceDb(id, data); return result; diff --git a/services/notification_error.service.js b/services/notification_error.service.js index a6a4409..5ee8686 100644 --- a/services/notification_error.service.js +++ b/services/notification_error.service.js @@ -172,7 +172,7 @@ class NotificationService { if (!notification.is_read) { const updateStatus = await updateNotificationErrorDb( notification_error_id, - { is_read: true } + { is_read: true, is_active: 0 } ); if (!updateStatus) { diff --git a/services/notifikasi-wa.service.js b/services/notifikasi-wa.service.js index f4cd541..afa2e74 100644 --- a/services/notifikasi-wa.service.js +++ b/services/notifikasi-wa.service.js @@ -4,6 +4,7 @@ const { updateNotificationErrorDb, getDeviceChannelReminder, getReminderNotificationErrorByYearlyDb, + updateNotificationErrorByChanelReminderDb, } = require("../db/notification_error.db"); const { createNotificationErrorUserDb, @@ -23,11 +24,11 @@ const util = require("util"); const execPromise = util.promisify(exec); const fs = require('fs').promises; const path = require("path"); -const { getSparepartsByYearlyDb } = require("../db/sparepart.db"); const { getDeviceReminderChanelDb } = require("../db/device.db"); -const filePath = path.join(process.cwd(), 'scheduler', 'reminder.json'); -const filePathLog = path.join(process.cwd(), 'scheduler', 'log.json'); +const baseDir = path.resolve(__dirname, '../scheduler'); +const filePath = path.join(baseDir, 'reminder.json'); +const filePathLog = path.join(baseDir, 'log.json'); class NotifikasiWaService { @@ -51,7 +52,6 @@ class NotifikasiWaService { existing = []; } - // 👉 selalu tambahkan waktu saat insert const now = new Date(); const newData = { @@ -64,19 +64,33 @@ class NotifikasiWaService { created_at: now.toISOString() // opsional (full timestamp) }; - if (!isReset) { - existing.push(newData); + 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 = newData; + existing.push(newData); + finalData = existing; } - - await fs.writeFile(filePath, JSON.stringify(existing, null, 2), 'utf-8'); + await fs.writeFile(filePath, JSON.stringify(finalData, null, 2), 'utf-8'); await this.saveLogReminder({ message: `Reminder berhasil disimpan`, - data - }) + data, + }); console.log(`Reminder berhasil disimpan`); } catch (error) { @@ -85,7 +99,7 @@ class NotifikasiWaService { } async saveLogReminder(data) { - console.log(`Reminder proses dibuat`); + console.log(`Reminder Log dibuat`); try { let existing = []; @@ -117,11 +131,11 @@ class NotifikasiWaService { created_at: now.toISOString() // opsional (full timestamp) }; - existing.push(newData); + await existing.push(newData); await fs.writeFile(filePathLog, JSON.stringify(existing, null, 2), 'utf-8'); - console.log(`Reminder berhasil disimpan`); + console.log(`Reminder Log berhasil disimpan`); } catch (error) { console.log('Error saveReminder:', error); } @@ -265,7 +279,7 @@ class NotifikasiWaService { async deleteReminder(id) { let data = await this.loadReminder(); - const newData = data?.filter(item => item.notification_log !== id); + const newData = await data?.filter(item => item.notification_log !== id); // cek apakah ada yang terhapus if (data.length === newData.length) { @@ -273,7 +287,42 @@ class NotifikasiWaService { return false; } - this.saveReminder(newData, true); + await this.saveReminder(newData, true); + + await updateNotificationErrorDb( + id, + { + is_active: false, + } + ); + + await this.saveLogReminder({ + 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.saveLogReminder({ message: `Reminder ${id} berhasil dihapus` @@ -285,71 +334,97 @@ class NotifikasiWaService { async runReminder() { setInterval(async () => { - const now = new Date(); let data = await this.loadReminder(); - data = data?.filter(async (item) => { + if (Array.isArray(data) && data.length > 0) { + const now = new Date(); + const newData = []; + for (const item of data) { - // ❌ jika non aktif → hapus - if (!item.active) return false; + // skip kalau tidak aktif + if (!item.active) continue; - // ❌ jika sudah max → stop & hapus - if (item.count >= item.max) return false; + // skip kalau sudah max + if (item.count >= item.max) continue; - const [hour, minute] = item.start_at.split('.'); + const [hour, minute] = item.start_at.split(':'); - const start = new Date(); - start.setHours(parseInt(hour), parseInt(minute), 0, 0); + const start = new Date(); + start.setHours(parseInt(hour), parseInt(minute), 0, 0); - // delay logic sederhana (first run or interval run) - if (!item.last_run) { - item.last_run = start.toISOString(); - } + const lastRunDate = new Date(item.last_run); - // 🔥 interval dalam JAM - const nextRun = new Date(lastRun.getTime() + (item.interval * 60 * 60 * 1000)); - - // eksekusi kalau sudah waktunya - if (now >= 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.saveLogReminder(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, - } - ); - } + if (isNaN(lastRunDate.getTime())) { + throw new Error(`Invalid last_run: ${item.last_run}`); } - item.last_run = now.toISOString(); - item.count += 1; + const nextRun = new Date( + lastRunDate.getTime() + (item.interval * 60 * 60 * 1000) + ); + + // format ke WIB (Jakarta) + item.next_run = nextRun; + item.next_run_indo = nextRun.toLocaleString('id-ID', { + timeZone: 'Asia/Jakarta', + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false + }); + + if (now >= 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.saveLogReminder(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.toLocaleString('id-ID', { + timeZone: 'Asia/Jakarta', + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false + }); + item.count += 1; + } + + newData.push(item); } - return true; - }); - - await this.saveReminder(data, true); + await this.saveReminder(newData, true); + } }, 60 * 1000); } @@ -445,15 +520,13 @@ class NotifikasiWaService { if (!checkNotifExist) { - // const deviceNotification = await getSparepartsByYearlyDb(yearly); - const data = { error_code_id: yearly ?? chanel.value, error_chanel: chanel.chanel_id, - is_send: false, - is_delivered: false, - is_read: false, - is_active: true, + is_send: 0, + is_delivered: 0, + is_read: 0, + is_active: 1, message_error_issue: `reminder ${chanel.value} in ${year}`, }; @@ -466,9 +539,22 @@ class NotifikasiWaService { start_at: timeReminderString, interval: 1, max: 3, - active: true, - count: 0 - }); + active: 1, + count: 0, + last_run: now.toISOString(), + last_run_indo: now.toLocaleString('id-ID', { + timeZone: 'Asia/Jakarta', + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false + }), + next_run: null, + next_run_indo: null + }, false); const results = await getAllContactDb(paramDb); @@ -550,7 +636,8 @@ class NotifikasiWaService { } } } catch (err) { - return err; + console.log('Error onNotificationReminder:', err); + throw err; } } -- 2.49.1