diff --git a/resources/views/menu_v1/reports/vehicle_trips.blade.php b/resources/views/menu_v1/reports/vehicle_trips.blade.php index 9819973..6b65fdb 100644 --- a/resources/views/menu_v1/reports/vehicle_trips.blade.php +++ b/resources/views/menu_v1/reports/vehicle_trips.blade.php @@ -31,8 +31,8 @@ @if ($user_role == \App\Models\Users::ROLE_VENDOR || $user_role == \App\Models\Users::ROLE_ADMIN) @can('vehicle.create') + + --> @endcan {{--
@@ -314,6 +314,20 @@ // Timestamp 'start' TIDAK diubah karena itu waktu kejadian sebenarnya — // yang disamakan hanya titik lokasi & odometer supaya laporan terlihat // kontinu (finish trip N == start trip N+1). + // + // PENTING: perbandingan koordinat dilakukan secara NUMERIK (parseFloat + + // hitung jarak meter via getDistanceMeters), BUKAN string mentah. + // Ini supaya kasus seperti berikut tetap terdeteksi & diperbaiki: + // - km/odometer sudah nyambung (finish trip N == start trip N+1) + // TAPI titik GPS-nya beda beberapa meter (drift GPS), sehingga + // salah satu titik "masuk" radius site tertentu (mis. "Bank BNU") + // dan yang lain tidak -> teks lokasi jadi kelihatan "loncat" + // walau kendaraan sebenarnya di tempat yang sama. + // Toleransi 0 meter berarti: kalau titiknya tidak PERSIS sama, + // selalu disamakan dengan finish sebelumnya. Ini paling aman untuk + // laporan (finish trip N akan selalu == start trip N+1). + const TRIP_CONTINUITY_TOLERANCE_METERS = 0; // >>> ADDED + function normalizeTripContinuity(rows) { // >>> ADDED if (!Array.isArray(rows) || rows.length === 0) return rows; @@ -333,14 +347,54 @@ const prev = groupRows[i - 1]; const curr = groupRows[i]; - const locMismatch = (prev.finish_lat_long || '').trim() !== (curr.start_lat_long || '') - .trim(); + // 1) Cek mismatch km/odometer const kmMismatch = Number(prev.finishMileage) !== Number(curr.startMileage); - if (locMismatch || kmMismatch) { + // 2) Cek mismatch koordinat GPS -- HANYA kalau datanya tersedia, + // karena banyak data (seperti contoh Anda) punya + // start_lat_long/finish_lat_long = null untuk semua baris. + const prevCoord = parseLatLong(prev.finish_lat_long); + const currCoord = parseLatLong(curr.start_lat_long); + let coordMismatch = false; + if (prevCoord.lat !== null && currCoord.lat !== null) { + const distMeters = getDistanceMeters( + prevCoord.lat, prevCoord.lng, + currCoord.lat, currCoord.lng + ); + coordMismatch = distMeters > TRIP_CONTINUITY_TOLERANCE_METERS; + } + + // 3) Cek mismatch TEKS lokasi (startLoc vs finishLoc). Ini penting + // karena saat lat_long null, satu-satunya sumber lokasi yang + // dipakai untuk render adalah field teks ini (lihat kolom + // Start/Finish -> prioritas 2: row.startLoc / row.finishLoc). + // Tanpa cek ini, kasus seperti "Estrada Suai - Maliana, Cendana" + // vs "Bank BNU, Estrada Suai - Maliana, Cendana" tidak akan + // terdeteksi sebagai mismatch walau km-nya sudah nyambung. + const prevFinishText = (prev.finishLoc || '').trim(); + const currStartText = (curr.startLoc || '').trim(); + const textMismatch = prevFinishText !== currStartText; + + if (kmMismatch || coordMismatch || textMismatch) { curr.start_lat_long = prev.finish_lat_long; curr.startLoc = prev.finishLoc; curr.startMileage = prev.finishMileage; + + // Hitung ulang Distance (kolom 'mileage') dari selisih odometer + // yang baru (finishMileage - startMileage yang sudah disinkronkan), + // supaya kolom Distance tetap konsisten dengan kolom Start(km)/Finish(km) + // yang tampil di tabel. Sebelumnya 'mileage' berasal dari GPS track + // mentah, yang bisa beda dengan selisih odometer setelah normalisasi ini. + // + // - Kalau hasilnya minus (finishMileage < startMileage, misal odometer + // sempat reset/kebalik), jadikan positif (absolute value). + // - Lalu bulatkan ke bilangan bulat terdekat: desimal >= 0.5 dibulatkan + // ke atas, desimal < 0.5 dibulatkan ke bawah (standard round-half-up; + // Math.round sudah berlaku demikian untuk angka positif). + const rawDiff = Number(curr.finishMileage) - Number(curr.startMileage); + if (!isNaN(rawDiff)) { + curr.mileage = Math.round(Math.abs(rawDiff)); + } } } }); @@ -600,7 +654,8 @@ locationLabel = formatSiteLabel(nearestSite); } else if (row.startLoc) { // Prioritas 2: startLoc sudah ada dari API - locationLabel = Helper.shortenText(decodeURIComponent(row.startLoc), 255); + locationLabel = Helper.shortenText(decodeURIComponent(row + .startLoc), 255); } else if (type === 'export') { // Export bersifat sinkron, tidak bisa menunggu hasil geocode background locationLabel = '-'; @@ -630,7 +685,8 @@ if (nearestSite) { locationLabel = formatSiteLabel(nearestSite); } else if (row.finishLoc) { - locationLabel = Helper.shortenText(decodeURIComponent(row.finishLoc), 255); + locationLabel = Helper.shortenText(decodeURIComponent(row + .finishLoc), 255); } else if (type === 'export') { locationLabel = '-'; } else {