From a6651c41b9d85469559ef23d23c4f23df76ed25d Mon Sep 17 00:00:00 2001 From: Thanakorn S Date: Wed, 27 May 2026 11:45:48 +0700 Subject: [PATCH] cron low stock - overdue invoice --- app/cron/alert_low_stock.php | 26 ++++++++++++ app/cron/alert_overdue_invoices.php | 25 ++++++++++++ nodejs/scheduler.js | 62 +++++++++++++++++++++++++++-- 3 files changed, 109 insertions(+), 4 deletions(-) create mode 100644 app/cron/alert_low_stock.php create mode 100644 app/cron/alert_overdue_invoices.php diff --git a/app/cron/alert_low_stock.php b/app/cron/alert_low_stock.php new file mode 100644 index 0000000..fc05195 --- /dev/null +++ b/app/cron/alert_low_stock.php @@ -0,0 +1,26 @@ +query( + "SELECT e.company_id, COUNT(*) AS count + FROM etl_stock_summary e + JOIN md_product p + ON p.product_sku = e.product_sku + AND p.company_id = e.company_id + AND p.status = 1 + WHERE e.company_id > 0 + AND p.min_stock > 0 + AND (e.total_in - e.total_out) < p.min_stock + GROUP BY e.company_id + HAVING count > 0" +); + +$answer['success'] = 1; +$answer['alerts'] = $sth->fetchAll(PDO::FETCH_ASSOC); + +exit(json_encode($answer)); diff --git a/app/cron/alert_overdue_invoices.php b/app/cron/alert_overdue_invoices.php new file mode 100644 index 0000000..3752b19 --- /dev/null +++ b/app/cron/alert_overdue_invoices.php @@ -0,0 +1,25 @@ +query( + "SELECT company_id, COUNT(*) AS count + FROM td_invoice + WHERE company_id > 0 + AND doc_type = 'invoice' + AND status = 1 + AND due_date IS NOT NULL + AND due_date < CURDATE() + GROUP BY company_id + HAVING count > 0" +); + +$answer['success'] = 1; +$answer['alerts'] = $sth->fetchAll(PDO::FETCH_ASSOC); + +exit(json_encode($answer)); diff --git a/nodejs/scheduler.js b/nodejs/scheduler.js index 56defb6..166460c 100644 --- a/nodejs/scheduler.js +++ b/nodejs/scheduler.js @@ -3,9 +3,10 @@ require('dotenv').config(); const cron = require('node-cron'); const http = require('http'); -const PHP_BASE = process.env.PHP_BASE_URL || 'http://127.0.0.1'; -const PHP_WEBROOT = process.env.PHP_WEBROOT || '/mn3wms/app'; -const SECRET = process.env.EMIT_SECRET || ''; +const PHP_BASE = process.env.PHP_BASE_URL || 'http://127.0.0.1'; +const PHP_WEBROOT = process.env.PHP_WEBROOT || '/mn3wms/app'; +const SECRET = process.env.EMIT_SECRET || ''; +const NODE_PORT = process.env.PORT || 3000; function callPhp(path, body) { return new Promise((resolve, reject) => { @@ -39,6 +40,25 @@ function callPhp(path, body) { }); } +function callEmit(company_id, event, data) { + const payload = JSON.stringify({ event, data, company_id }); + const options = { + hostname: '127.0.0.1', + port: NODE_PORT, + path: '/emit', + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(payload), + 'x-emit-secret': SECRET, + }, + }; + const req = http.request(options); + req.on('error', (e) => console.error(`[alert] emit failed company=${company_id}:`, e.message)); + req.write(payload); + req.end(); +} + // ── ETL GL Summary maintenance — runs every hour ───────────────────────────── // Checks and repairs etl_gl_summary (GL aggregation table). // Each company has a fixed slot = company_id % 24 — load is spread across 24 hours. @@ -88,4 +108,38 @@ cron.schedule('30 * * * *', async () => { } }); -console.log('[scheduler] running — etl_gl_maintenance + etl_stock_maintenance wired (hourly, slot-based)'); +// ── Low stock alert — runs daily at 08:00 ──────────────────────────────────── +cron.schedule('0 8 * * *', async () => { + console.log('[scheduler] alert_low_stock running'); + try { + const result = await callPhp('/cron/alert_low_stock.php', {}); + for (const { company_id, count } of (result.alerts || [])) { + const msg = count === 1 + ? '1 product is below minimum stock level.' + : `${count} products are below minimum stock level.`; + callEmit(parseInt(company_id), 'notification', { message: msg, type: 'warning' }); + console.log(`[alert] low_stock company=${company_id} count=${count}`); + } + } catch (e) { + console.error('[scheduler] alert_low_stock failed:', e.message); + } +}); + +// ── Overdue invoice alert — runs daily at 09:00 ─────────────────────────────── +cron.schedule('0 9 * * *', async () => { + console.log('[scheduler] alert_overdue_invoices running'); + try { + const result = await callPhp('/cron/alert_overdue_invoices.php', {}); + for (const { company_id, count } of (result.alerts || [])) { + const msg = count === 1 + ? '1 invoice is overdue and awaiting payment.' + : `${count} invoices are overdue and awaiting payment.`; + callEmit(parseInt(company_id), 'notification', { message: msg, type: 'danger' }); + console.log(`[alert] overdue_invoices company=${company_id} count=${count}`); + } + } catch (e) { + console.error('[scheduler] alert_overdue_invoices failed:', e.message); + } +}); + +console.log('[scheduler] running — etl_gl + etl_stock (hourly) | low_stock + overdue_invoices alerts (daily)');