feat(wa): notifikasi pelanggan tiket + broadcast status layanan
All checks were successful
CI / test (push) Successful in 2m3s

- WA konfirmasi ke pelanggan saat tiket dibuat (web & APK)
- checkbox + target (semua/area/router) di Status Layanan
- WA gangguan/perbaikan saat mode berubah, WA layanan pulih saat NORMAL
- broadcast latar belakang dengan jeda acak, dedupe nomor, log [NoticeWA]
- versi 0.5.4
This commit is contained in:
oxpoe
2026-11-05 09:28:48 +07:00
parent 8231058f79
commit bc7f2bdb09
8 changed files with 233 additions and 5 deletions

View File

@@ -4015,13 +4015,16 @@ router.post('/sidebar-settings', requireAdminSession, restrictToAdmin, express.u
});
router.get('/service-notice', requireAdminSession, requireSidebarMenuAccess('service_notice'), (req, res) => {
let routers = [];
try { routers = db.prepare('SELECT id, name FROM routers WHERE is_active = 1 ORDER BY name ASC').all(); } catch (_) {}
res.render('admin/service_notice', {
title: 'Status Layanan & Pengumuman', company: company(), activePage: 'service_notice',
settings: getSettings(), msg: flashMsg(req)
settings: getSettings(), msg: flashMsg(req),
areas: customerSvc.getAllCustomerAreas(), routers
});
});
router.post('/service-notice', requireAdminSession, requireSidebarMenuAccess('service_notice'), express.urlencoded({ extended: true }), (req, res) => {
router.post('/service-notice', requireAdminSession, requireSidebarMenuAccess('service_notice'), express.urlencoded({ extended: true }), async (req, res) => {
try {
const allowed = ['NORMAL', 'GANGGUAN_MASAL', 'PERBAIKAN'];
let mode = String(req.body.notice_mode || 'NORMAL').trim().toUpperCase();
@@ -4029,6 +4032,7 @@ router.post('/service-notice', requireAdminSession, requireSidebarMenuAccess('se
let interval = parseInt(req.body.notice_interval_hours, 10);
if (!interval || interval < 1) interval = 5;
if (interval > 168) interval = 168;
const prevMode = String(getSetting('notice_mode', 'NORMAL') || 'NORMAL').toUpperCase();
saveSettings({
notice_mode: mode,
notice_start: String(req.body.notice_start || '').trim(),
@@ -4038,7 +4042,27 @@ router.post('/service-notice', requireAdminSession, requireSidebarMenuAccess('se
const noticeSvc = require('../services/serviceNoticeService');
const n = noticeSvc.computeNotice();
const detail = n.active ? (n.label + (n.estimateText ? ' — estimasi ' + n.estimateText : '')) : 'Normal (tanpa pengumuman)';
req.session._msg = { type: 'success', text: 'Status layanan diperbarui: ' + detail };
let waInfo = '';
if (req.body.notice_wa === '1') {
if (mode === prevMode) {
waInfo = ' Notifikasi WA dilewati (mode tidak berubah).';
} else {
try {
const r = await require('../services/serviceNoticeNotifyService').broadcastNotice({
mode,
target: String(req.body.notice_wa_target || 'all'),
area: String(req.body.notice_wa_area || ''),
routerId: parseInt(req.body.notice_wa_router, 10) || 0,
notice: n
});
if (r.ok) waInfo = ` Notifikasi WA dikirim ke ${r.total} pelanggan (latar belakang).`;
else waInfo = ` Notifikasi WA tidak dikirim: ${r.reason === 'empty' ? 'tidak ada nomor valid' : r.reason === 'busy' ? 'proses lain masih berjalan' : 'WhatsApp belum aktif'}.`;
} catch (waErr) {
waInfo = ' Notifikasi WA gagal: ' + waErr.message;
}
}
}
req.session._msg = { type: 'success', text: 'Status layanan diperbarui: ' + detail + '.' + waInfo };
} catch (e) {
req.session._msg = { type: 'error', text: 'Gagal menyimpan status layanan: ' + e.message };
}

View File

@@ -3509,6 +3509,11 @@ router.post('/tickets/create', requireCustomerApiAuth, async (req, res) => {
for (const digits of recipients) {
try { await waNotify.sendText(digits, waMsg); } catch (_) {}
}
// WA konfirmasi ke pelanggan yang membuat tiket
try {
await require('../services/serviceNoticeNotifyService').notifyTicketCreated(customer, ticketId, subject);
} catch (_) {}
}
} catch (waErr) {
logger.error(`[CustomerAPI] Ticket WA Notification Error: ${waErr.message}`);

View File

@@ -3002,6 +3002,11 @@ router.post('/tickets/create', uploadCustomer.array('photos', 5), async (req, re
if (!shouldSendWa(key)) continue;
await waNotify.sendText(digits, waMsg);
}
// WA konfirmasi ke pelanggan yang membuat tiket
try {
await require('../services/serviceNoticeNotifyService').notifyTicketCreated(customer, ticketId, subject);
} catch (_) {}
}
} catch (waErr) {
logger.error(`[Ticket] WA Notification Error: ${waErr.message}`);