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();