diff --git a/config/dbMysqlConn.js b/config/dbMysqlConn.js index d5ce1fb..e706052 100755 --- a/config/dbMysqlConn.js +++ b/config/dbMysqlConn.js @@ -1,31 +1,32 @@ -const mysql = require('mysql'); +require("dotenv").config({ path: require("path").resolve(__dirname, "../.env") }); +const mysql = require("mysql"); const pool = mysql.createPool({ - connectionLimit: process.env.CONNECTIONLIMIT, - host: process.env.DBHOST, - port: process.env.DBPORT, - user: process.env.DBUSER, - password: process.env.DBPASSWORD, - database: process.env.DATABASE, - acquireTimeout: Number(process.env.ACQRTIMEOUT), // in ms + connectionLimit: process.env.CONNECTIONLIMIT, + host: process.env.DBHOST, + port: process.env.DBPORT, + user: process.env.DBUSER, + password: process.env.DBPASSWORD, + database: process.env.DATABASE, + acquireTimeout: Number(process.env.ACQRTIMEOUT), // in ms }); pool.getConnection((err, conn) => { - if (err) { - if (err.code === 'PROTOCOL_CONNECTION_LOST') { - console.error('Koneksi database ditutup.'); - } - if (err.code === 'ER_CON_COUNT_ERROR') { - console.error('Basis data memiliki terlalu banyak koneksi.'); - } - if (err.code === 'ECONNREFUSED') { - console.error('Koneksi database ditolak.'); - } - console.error(err); - } - if (conn) conn.release(); - return; -}) + if (err) { + if (err.code === "PROTOCOL_CONNECTION_LOST") { + console.error("Koneksi database ditutup."); + } + if (err.code === "ER_CON_COUNT_ERROR") { + console.error("Basis data memiliki terlalu banyak koneksi."); + } + if (err.code === "ECONNREFUSED") { + console.error("Koneksi database ditolak."); + } + console.error(err); + } + if (conn) conn.release(); + return; +}); // pool.on('acquire', function (connection) { // console.log('Connection %d acquired', connection.threadId); diff --git a/cron/UpdateJobStatusWorker.js b/cron/UpdateJobStatusWorker.js new file mode 100644 index 0000000..f7d292f --- /dev/null +++ b/cron/UpdateJobStatusWorker.js @@ -0,0 +1,142 @@ +const path = require("path"); +require("dotenv").config({ path: path.resolve(__dirname, "../.env") }); + +const LibWinston = require("../library/LibWinston"); +const OrderStatusModels = require("../models/OrderStatusModels"); +const db = require("../config/dbMysqlConn"); +const moment = require("moment"); + +const schedulerName = "UPDATE JOB STATUS"; +const Logger = LibWinston.initialize(schedulerName); + +// ✅ Status constants +const STATUS = { + AWAL: 22, + DI_PICKUP: 2, + OTW: 3, + SAMPAI: 4, +}; + +// 🔍 Fungsi Haversine untuk jarak circle +function haversine(lat1, lon1, lat2, lon2) { + const toRad = (x) => (x * Math.PI) / 180; + const R = 6371 * 1000; // radius bumi meter + + const dLat = toRad(lat2 - lat1); + const dLon = toRad(lon2 - lon1); + + const a = Math.sin(dLat / 2) ** 2 + Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon / 2) ** 2; + + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + return R * c; // hasil meter +} + +// 🔍 Fungsi cek point di dalam square (bounding box) +function isPointInSquare(point, boundaryLatLngs) { + const lats = boundaryLatLngs.map((p) => parseFloat(p.lat)); + const lngs = boundaryLatLngs.map((p) => parseFloat(p.lng)); + const minLat = Math.min(...lats); + const maxLat = Math.max(...lats); + const minLng = Math.min(...lngs); + const maxLng = Math.max(...lngs); + + return point.lat >= minLat && point.lat <= maxLat && point.lng >= minLng && point.lng <= maxLng; +} + +const go = async () => { + Logger.log("info", `${schedulerName}: ${moment().format("YYYY-MM-DD HH:mm:ss")}`); + + try { + const result = await OrderStatusModels.GetList(); + const rows = result.rows; + if (!rows || rows.length === 0) return; + console.log("rows: ", rows); + + for (let row of rows) { + if (!row.pck_latlng || !row.drp_latlng || !row.latitude || !row.longitude) continue; + + const lat = parseFloat(row.latitude); + const lng = parseFloat(row.longitude); + if (isNaN(lat) || isNaN(lng)) continue; + + let pckBoundary, drpBoundary; + try { + pckBoundary = JSON.parse(row.pck_latlng); + drpBoundary = JSON.parse(row.drp_latlng); + } catch (e) { + Logger.log("error", `Invalid JSON for order ${row.ord_code}`); + continue; + } + + // Ambil center titik untuk perhitungan jarak circle + const pckCenter = pckBoundary[0]; + const drpCenter = drpBoundary[0]; + + const distToPickup = haversine(lat, lng, parseFloat(pckCenter.lat), parseFloat(pckCenter.lng)); + const distToDrop = haversine(lat, lng, parseFloat(drpCenter.lat), parseFloat(drpCenter.lng)); + + // Tentukan apakah di pickup zone + let inPickupZone = false; + if (row.pck_type === "circle") { + inPickupZone = distToPickup <= row.pck_radius; + } else if (row.pck_type === "square") { + inPickupZone = isPointInSquare({ lat, lng }, pckBoundary); + } + + // Tentukan apakah di drop zone + let inDropZone = false; + if (row.drp_type === "circle") { + inDropZone = distToDrop <= row.drp_radius; + } else if (row.drp_type === "square") { + inDropZone = isPointInSquare({ lat, lng }, drpBoundary); + } + + let newStatus = row.status; + if (inPickupZone) { + newStatus = STATUS.DI_PICKUP; + } else if (!inPickupZone && !inDropZone) { + newStatus = STATUS.OTW; + } else if (inDropZone) { + newStatus = STATUS.SAMPAI; + } + + if (newStatus !== row.status) { + await new Promise((resolve, reject) => { + db.query(`UPDATE t_orders SET status = ? WHERE code = ?`, [newStatus, row.ord_code], (err) => { + if (err) { + Logger.log("error", `Gagal update status order ${row.ord_code}: ${err.message}`); + return reject(err); + } + console.log(`Order ${row.ord_code}: status updated from ${row.status} to ${newStatus}`); + resolve(); + }); + }); + + // Jika sudah sampai (status SAMPAI), reset data kendaraan + if (newStatus === STATUS.SAMPAI) { + await new Promise((resolve, reject) => { + db.query(`UPDATE t_vehicles SET is_in_ord = 2, ord_id = 0, ord_code = '0' WHERE id = ?`, [row.id], (err) => { + if (err) { + Logger.log("error", `Gagal reset kendaraan ID ${row.id}: ${err.message}`); + return reject(err); + } + console.log(`Vehicle ${row.id} reset: is_in_ord=2, ord_id=0, ord_code='0'`); + resolve(); + }); + }); + } + } + } + } catch (error) { + Logger.log("error", `Job error: ${error.message}`); + } +}; + +const index = async () => { + while (true) { + await go(); + await new Promise((r) => setTimeout(r, 3000)); + } +}; + +index(); diff --git a/models/OrderStatusModels.js b/models/OrderStatusModels.js new file mode 100755 index 0000000..ed821b1 --- /dev/null +++ b/models/OrderStatusModels.js @@ -0,0 +1,59 @@ +const db = require(`../config/dbMysqlConn`); + +const OrderStatusModels = (module.exports = { + GetList: async function () { + return new Promise((resolve, reject) => { + const query = ` + SELECT + tv.id, + tv.name, + tv.is_in_ord, + tv.ord_id, + tv.ord_code, + tv.device_id, + tz.boundary_latlngs AS pck_latlng, + tz.boundary_type AS pck_type, + tz.boundary_radius AS pck_radius, + tz2.boundary_latlngs AS drp_latlng, + tz2.boundary_type AS drp_type, + tz2.boundary_radius AS drp_radius, + to2.status, + tgt.latitude, + tgt.longitude, + tgt.crt_format + FROM + t_vehicles tv + LEFT JOIN t_orders to2 ON to2.code = tv.ord_code + INNER JOIN ( + SELECT tg1.device_id, tg1.latitude, tg1.longitude, tg1.crt_format + FROM t_gps_tracks tg1 + INNER JOIN ( + SELECT device_id, MAX(crt_format) AS max_crt + FROM t_gps_tracks + WHERE action = 'location' + GROUP BY device_id + ) tg2 ON tg1.device_id = tg2.device_id AND tg1.crt_format = tg2.max_crt + WHERE tg1.action = 'location' + ) tgt ON tgt.device_id = tv.device_id + LEFT JOIN t_orders_pck_drop topd ON topd.ord_code = tv.ord_code + LEFT JOIN t_zones tz ON tz.id = topd.pck_id + LEFT JOIN t_zones tz2 ON tz2.id = topd.drop_id + WHERE + tv.is_in_ord = 1 + AND tv.ord_id != 0 + AND tv.ord_code IS NOT NULL + ORDER BY tgt.crt_format DESC; + `; + + db.query(query, (err, result) => { + if (err) { + return reject(err); + } + resolve({ + rowCount: result.length, + rows: result, + }); + }); + }); + }, +}); diff --git a/models/UsersModels.js b/models/UsersModels.js index 14b61a6..2228d69 100755 --- a/models/UsersModels.js +++ b/models/UsersModels.js @@ -1,121 +1,119 @@ const db = require(`../config/dbMysqlConn`); class UsersModels { + static ROLE_SU = 1; // unused + static ROLE_SUPERADMIN = 7; + static ROLE_ADMIN = 2; + static ROLE_VENDOR = 8; + static ROLE_CHECKER = 9; + static ROLE_USER = 3; // unused + static ROLE_CLIENT_ADMIN = 4; + static ROLE_CLIENT_STAFF = 5; // unused + static ROLE_CLIENT_USER = 6; // unused + static ROLE_SPECIAL_TRACKING = 10; + static ROLE_FINANCE = 11; - static ROLE_SU = 1; // unused - static ROLE_SUPERADMIN = 7; - static ROLE_ADMIN = 2; - static ROLE_VENDOR = 8; - static ROLE_CHECKER = 9; - static ROLE_USER = 3; // unused - static ROLE_CLIENT_ADMIN = 4; - static ROLE_CLIENT_STAFF = 5; // unused - static ROLE_CLIENT_USER = 6; // unused - static ROLE_SPECIAL_TRACKING = 10; - static ROLE_FINANCE = 11; + static STATUS_ACTIVE = 1; + static STATUS_NOT_ACTIVE = 2; + static STATUS_SUSPEND = 3; - static STATUS_ACTIVE = 1; - static STATUS_NOT_ACTIVE = 2; - static STATUS_SUSPEND = 3; + static CHK_TYPE_PICKUP = 1; + static CHK_TYPE_DROP = 2; - static CHK_TYPE_PICKUP = 1; - static CHK_TYPE_DROP = 2; + static DEFAULT_UID = 1; // swanusa account - static DEFAULT_UID = 1; // swanusa account + static DEFAULT_PHONE_CODE = 62; - static DEFAULT_PHONE_CODE = 62; + static async all() { + return new Promise((resolve, reject) => { + const query = `SELECT * FROM t_users;`; + db.query(query, (err, results) => { + if (err) { + reject(err); + return false; + } + resolve(results); + }); + }); + } - static async all() { - return new Promise((resolve, reject) => { - const query = `SELECT * FROM t_users;`; - db.query(query, (err, results) => { - if (err) { - reject(err); - return false; - } - resolve(results); - }); - }); - } + static async getUsersActiveByRole(role) { + return new Promise((resolve, reject) => { + const query = `SELECT * FROM t_users WHERE dlt is null AND status = ${UsersModels.STATUS_ACTIVE} AND role = ?;`; + db.query(query, [role], (err, results) => { + if (err) { + reject(err); + return false; + } + resolve(results); + }); + }); + } - static async getUsersActiveByRole(role) { - return new Promise((resolve, reject) => { - const query = `SELECT * FROM t_users WHERE dlt is null AND status = ${UsersModels.STATUS_ACTIVE} AND role = ?;`; - db.query(query, [role], (err, results) => { - if (err) { - reject(err); - return false; - } - resolve(results); - }); - }); - } + static async find(id) { + return new Promise((resolve, reject) => { + const query = `SELECT * FROM t_users WHERE id = ? LIMIT 1;`; + db.query(query, [id], (err, results) => { + if (err) { + reject(err); + return false; + } + resolve(results); + }); + }); + } - static async find(id) { - return new Promise((resolve, reject) => { - const query = `SELECT * FROM t_users WHERE id = ? LIMIT 1;`; - db.query(query, [id], (err, results) => { - if (err) { - reject(err); - return false; - } - resolve(results); - }); - }); - } + static async findEmail(email) { + return new Promise((resolve, reject) => { + const query = `SELECT * FROM t_users WHERE email = ? LIMIT 1;`; + db.query(query, [email], (err, results) => { + if (err) { + reject(err); + return false; + } + resolve(results); + }); + }); + } - static async findEmail(email) { - return new Promise((resolve, reject) => { - const query = `SELECT * FROM t_users WHERE email = ? LIMIT 1;`; - db.query(query, [email], (err, results) => { - if (err) { - reject(err); - return false; - } - resolve(results); - }); - }); - } + static async create(data) { + return new Promise((resolve, reject) => { + const query = `INSERT INTO t_users SET ?;`; + db.query(query, data, (err, results) => { + if (err) { + reject(err); + return false; + } + resolve(results); + }); + }); + } - static async create(data) { - return new Promise((resolve, reject) => { - const query = `INSERT INTO t_users SET ?;`; - db.query(query, data, (err, results) => { - if (err) { - reject(err); - return false; - } - resolve(results); - }); - }); - } - - static async update(data, id) { - return new Promise((resolve, reject) => { - const query = `UPDATE t_users SET ? WHERE id = ?;`; - db.query(query, [data, id], (err, results) => { - if (err) { - reject(err); - return false; - } - resolve(results); - }); - }); - } - - static async delete(id) { - return new Promise((resolve, reject) => { - const query = `DELETE FROM t_users WHERE id = ?;`; - db.query(query, [id], (err, results) => { - if (err) { - reject(err); - return false; - } - resolve(results); - }); - }); - } + static async update(data, id) { + return new Promise((resolve, reject) => { + const query = `UPDATE t_users SET ? WHERE id = ?;`; + db.query(query, [data, id], (err, results) => { + if (err) { + reject(err); + return false; + } + resolve(results); + }); + }); + } + static async delete(id) { + return new Promise((resolve, reject) => { + const query = `DELETE FROM t_users WHERE id = ?;`; + db.query(query, [id], (err, results) => { + if (err) { + reject(err); + return false; + } + resolve(results); + }); + }); + } } -module.exports = UsersModels; \ No newline at end of file +module.exports = UsersModels; diff --git a/package-lock.json b/package-lock.json index 452e6be..6934edf 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1635,6 +1635,7 @@ "version": "10.0.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "license": "BSD-2-Clause", "engines": { "node": ">=10" }