diff --git a/package.json b/package.json index 3bc2bdb..3f46979 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "cors": "^2.8.5", "crypto": "^1.0.1", "crypto-js": "^4.2.0", + "dayjs": "^1.11.20", "dotenv": "^8.2.0", "exceljs": "^4.4.0", "express": "^4.18.2", diff --git a/services/notifikasi-wa.service.js b/services/notifikasi-wa.service.js index 7d5c704..d66e778 100644 --- a/services/notifikasi-wa.service.js +++ b/services/notifikasi-wa.service.js @@ -2,7 +2,6 @@ const { getAllContactDb } = require("../db/contact.db"); const { InsertNotificationErrorDb, updateNotificationErrorDb, - getDeviceChannelReminder, getReminderNotificationErrorByYearlyDb, updateNotificationErrorByChanelReminderDb, } = require("../db/notification_error.db"); @@ -30,6 +29,15 @@ const baseDir = path.resolve(__dirname, '../scheduler'); const filePath = path.join(baseDir, 'reminder.json'); const filePathLog = path.join(baseDir, 'log.json'); +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); + class NotifikasiWaService { async saveReminder(data, isReset = false) { @@ -52,16 +60,16 @@ class NotifikasiWaService { existing = []; } - const now = new Date(); + const now = dayjs().tz(timeZone); const newData = { ...data, - time: now.toLocaleTimeString('id-ID', { - hour: '2-digit', - minute: '2-digit', - hour12: false, - }), - created_at: now.toISOString() // opsional (full timestamp) + + // 🔹 jam (HH:mm) WIB + time: now.format('HH:mm'), + + // 🔹 timestamp UTC (untuk DB) + created_at: now.toISOString() }; let finalData; @@ -118,17 +126,16 @@ class NotifikasiWaService { existing = []; } - // 👉 selalu tambahkan waktu saat insert - const now = new Date(); + const now = dayjs().tz(timeZone); const newData = { ...data, - time: now.toLocaleTimeString('id-ID', { - hour: '2-digit', - minute: '2-digit', - hour12: false, - }), - created_at: now.toISOString() // opsional (full timestamp) + + // 🔹 jam (HH:mm) WIB + time: now.format('HH:mm'), + + // 🔹 timestamp UTC (untuk DB) + created_at: now.toISOString() }; await existing.push(newData); @@ -332,7 +339,10 @@ class NotifikasiWaService { let data = await this.loadReminder(); if (Array.isArray(data) && data.length > 0) { - const now = new Date(); + + // waktu sekarang (WIB) + const now = dayjs().tz(timeZone); + const newData = []; for (const item of data) { @@ -342,35 +352,22 @@ class NotifikasiWaService { // skip kalau sudah max if (item.count >= item.max) continue; - const [hour, minute] = item.start_at.split(':'); + // 🔹 parse last_run (ISO dari DB) + const lastRunDate = dayjs(item.last_run); - const start = new Date(); - start.setHours(parseInt(hour), parseInt(minute), 0, 0); - - const lastRunDate = new Date(item.last_run); - - if (isNaN(lastRunDate.getTime())) { + if (!lastRunDate.isValid()) { throw new Error(`Invalid last_run: ${item.last_run}`); } - const nextRun = new Date( - lastRunDate.getTime() + (item.interval * 60 * 60 * 1000) - ); + // 🔹 hitung next_run berdasarkan interval (jam) + const nextRun = lastRunDate.add(item.interval, 'hour'); - // 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 - }); + // 🔹 assign hasil + item.next_run = nextRun.toISOString(); // UTC (untuk DB) + item.next_run_indo = nextRun.tz(timeZone).format('DD-MM-YYYY HH:mm:ss'); - if (now >= nextRun) { + // 🔥 trigger check + if (now.isAfter(nextRun) || now.isSame(nextRun)) { const results = await getNotificationErrorByIdDb(item.notification_log); const dataUsers = results?.data ?? []; @@ -400,18 +397,8 @@ class NotifikasiWaService { ); } } - - 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.last_run = now.toISOString(); // tetap UTC (standar DB) + item.last_run_indo = now.format('DD-MM-YYYY HH:mm:ss'); // WIB item.count += 1; } @@ -463,55 +450,32 @@ class NotifikasiWaService { ); if (deviceReminder?.[0]?.reminder_at) { - const timeZone = 'Asia/Jakarta'; + // 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 now = new Date(); - const year = now.getFullYear(); - const reminderAt = new Date(deviceReminder[0].reminder_at); const deviceName = deviceReminder[0].device_name ?? '-'; - // 🔹 ambil bulan & jam dalam timezone server - const nowParts = new Intl.DateTimeFormat('en-US', { - timeZone, - month: 'numeric', - hour: 'numeric', - hour12: false, - }).formatToParts(now); + // 🔥 compare sampai menit (tanpa tahun) + const nowKey = now.format('MM-DD HH:mm'); + const reminderKey = reminderAt.format('MM-DD HH:mm'); - const reminderParts = new Intl.DateTimeFormat('en-US', { - timeZone, - month: 'numeric', - hour: 'numeric', - hour12: false, - }).formatToParts(reminderAt); - - const timeReminderString = reminderAt.toLocaleTimeString('id-ID', { - timeZone: 'Asia/Jakarta', - hour: '2-digit', - minute: '2-digit', - hour12: false - }).replace('.', ':'); // ganti titik jadi titik dua - - const getPart = (parts, type) => - parseInt(parts.find(p => p.type === type).value, 10); - - const nowMonth = getPart(nowParts, 'month'); - const nowHour = getPart(nowParts, 'hour'); - - const reminderMonth = getPart(reminderParts, 'month'); - const reminderHour = getPart(reminderParts, 'hour'); - - // 🔥 logic utama - const isTriggered = - nowMonth > reminderMonth || - (nowMonth === reminderMonth && nowHour >= reminderHour); + 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, year); + const checkNotifExist = await getReminderNotificationErrorByYearlyDb(yearly ?? chanel.value, chanel.chanel_id, currentYear); if (!checkNotifExist) { @@ -522,7 +486,7 @@ class NotifikasiWaService { is_delivered: 0, is_read: 0, is_active: 1, - message_error_issue: `reminder ${chanel.value} in ${year}`, + message_error_issue: `reminder ${chanel.value} in ${currentYear}`, }; const resultNotificationError = await InsertNotificationErrorDb(data); @@ -531,22 +495,13 @@ class NotifikasiWaService { notification_log: resultNotificationError.notification_error_id, error_code_id: data['error_code_id'], error_chanel: data['error_chanel'], - start_at: timeReminderString, + start_at: reminderAt.format('HH:mm'), interval: 1, max: 3, 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 - }), + last_run_indo: now.format('DD-MM-YYYY HH:mm:ss'), next_run: null, next_run_indo: null }, false);