diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index 79f6dd3..75a6963 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: Gitea.Workflow # INGROUP: mokocli.Automation -# VERSION: 01.06.00 +# VERSION: 01.00.00 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" diff --git a/source/packages/plg_system_mokosuitefield/src/Helper/GpsTrackingHelper.php b/source/packages/plg_system_mokosuitefield/src/Helper/GpsTrackingHelper.php new file mode 100644 index 0000000..99c8cf8 --- /dev/null +++ b/source/packages/plg_system_mokosuitefield/src/Helper/GpsTrackingHelper.php @@ -0,0 +1,112 @@ + 90 || $longitude < -180 || $longitude > 180) { + throw new \InvalidArgumentException('Invalid GPS coordinates.'); + } + + $db = Factory::getContainer()->get(DatabaseInterface::class); + + $ping = (object) [ + 'vehicle_id' => $vehicleId, + 'latitude' => round($latitude, 6), + 'longitude' => round($longitude, 6), + 'speed_mph' => max(0, round($speed, 1)), + 'recorded_at'=> Factory::getDate()->toSql(), + ]; + + $db->insertObject('#__mokosuitefield_gps_pings', $ping); + + return true; + } + + /** + * Get latest position for all active vehicles. + */ + public static function getFleetPositions(): array + { + $db = Factory::getContainer()->get(DatabaseInterface::class); + + $db->setQuery($db->getQuery(true) + ->select('v.id AS vehicle_id, v.name AS vehicle_name, v.license_plate') + ->select('gp.latitude, gp.longitude, gp.speed_mph, gp.recorded_at') + ->select('cd.name AS assigned_tech') + ->from($db->quoteName('#__mokosuitefield_vehicles', 'v')) + ->join('LEFT', '(SELECT g1.* FROM #__mokosuitefield_gps_pings g1' + . ' INNER JOIN (SELECT vehicle_id, MAX(recorded_at) AS max_at' + . ' FROM #__mokosuitefield_gps_pings GROUP BY vehicle_id) g2' + . ' ON g1.vehicle_id = g2.vehicle_id AND g1.recorded_at = g2.max_at) AS gp' + . ' ON gp.vehicle_id = v.id') + ->join('LEFT', $db->quoteName('#__mokosuitefield_technicians', 't') . ' ON t.vehicle_id = v.id') + ->join('LEFT', $db->quoteName('#__contact_details', 'cd') . ' ON cd.id = t.contact_id') + ->where($db->quoteName('v.status') . ' = ' . $db->quote('active')) + ->order('v.name ASC')); + + return $db->loadObjectList() ?: []; + } + + /** + * Get drive history for a vehicle on a specific date. + */ + public static function getDriveHistory(int $vehicleId, string $date = ''): array + { + $date = $date ?: date('Y-m-d'); + + if (!\DateTime::createFromFormat('Y-m-d', $date)) { + throw new \InvalidArgumentException('Date must be Y-m-d format.'); + } + + $db = Factory::getContainer()->get(DatabaseInterface::class); + + $db->setQuery($db->getQuery(true) + ->select('gp.latitude, gp.longitude, gp.speed_mph, gp.recorded_at') + ->from($db->quoteName('#__mokosuitefield_gps_pings', 'gp')) + ->where('gp.vehicle_id = ' . (int) $vehicleId) + ->where('DATE(gp.recorded_at) = ' . $db->quote($date)) + ->order('gp.recorded_at ASC')); + + return $db->loadObjectList() ?: []; + } + + /** + * Get vehicles currently exceeding speed threshold. + */ + public static function getSpeeding(float $thresholdMph = 70): array + { + $db = Factory::getContainer()->get(DatabaseInterface::class); + + $db->setQuery($db->getQuery(true) + ->select('v.id AS vehicle_id, v.name AS vehicle_name, v.license_plate') + ->select('gp.latitude, gp.longitude, gp.speed_mph, gp.recorded_at') + ->select('cd.name AS assigned_tech') + ->from($db->quoteName('#__mokosuitefield_vehicles', 'v')) + ->join('INNER', '(SELECT g1.* FROM #__mokosuitefield_gps_pings g1' + . ' INNER JOIN (SELECT vehicle_id, MAX(recorded_at) AS max_at' + . ' FROM #__mokosuitefield_gps_pings GROUP BY vehicle_id) g2' + . ' ON g1.vehicle_id = g2.vehicle_id AND g1.recorded_at = g2.max_at) AS gp' + . ' ON gp.vehicle_id = v.id') + ->join('LEFT', $db->quoteName('#__mokosuitefield_technicians', 't') . ' ON t.vehicle_id = v.id') + ->join('LEFT', $db->quoteName('#__contact_details', 'cd') . ' ON cd.id = t.contact_id') + ->where($db->quoteName('v.status') . ' = ' . $db->quote('active')) + ->where('gp.speed_mph > ' . (float) $thresholdMph) + ->where('gp.recorded_at > DATE_SUB(NOW(), INTERVAL 10 MINUTE)') + ->order('gp.speed_mph DESC')); + + return $db->loadObjectList() ?: []; + } +} diff --git a/source/pkg_mokosuitefield.xml b/source/pkg_mokosuitefield.xml index ccb9ae9..af1348a 100644 --- a/source/pkg_mokosuitefield.xml +++ b/source/pkg_mokosuitefield.xml @@ -2,7 +2,7 @@ Package - MokoSuite Field mokosuitefield - 01.06.00 + 01.06.02 2026-06-12 Moko Consulting hello@mokoconsulting.tech