7 Commits

Author SHA1 Message Date
gitea-actions[bot] 5e815dd945 chore(release): build 01.07.00 [skip ci]
Publish to Composer / Publish Package (release) Failing after 24s
2026-06-21 15:00:40 +00:00
jmiller c9ea9d66a0 Merge pull request 'feat: GpsTrackingHelper + TechnicianSkillHelper fixes' (#23) from dev into main 2026-06-21 15:00:11 +00:00
jmiller d307630e99 chore: sync issue-branch.yml from Template-Generic [skip ci]
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 15s
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Failing after 1m36s
2026-06-21 09:59:59 -05:00
gitea-actions[bot] 1983d1c4ef chore(version): pre-release bump to 01.06.02-dev [skip ci]
Publish to Composer / Publish Package (release) Failing after 4s
2026-06-21 14:22:27 +00:00
gitea-actions[bot] e01a08c664 chore(version): auto-bump patch 01.06.01-dev [skip ci] 2026-06-21 14:22:18 +00:00
Jonathan Miller 3ca55ac09c feat: GpsTrackingHelper — fleet positions, drive history, speed alerts
Universal: Auto Version Bump / Version Bump (push) Successful in 10s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 10s
2026-06-21 09:22:02 -05:00
jmiller 23459570d8 chore: sync issue-branch.yml from Template-Generic [skip ci] 2026-06-21 14:06:48 +00:00
3 changed files with 114 additions and 2 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Automation
# VERSION: 01.06.00
# VERSION: 01.07.00
# BRIEF: Auto-create feature branch when an issue is opened
name: "Universal: Issue Branch"
@@ -0,0 +1,112 @@
<?php
namespace Moko\Plugin\System\MokoSuiteField\Helper;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\Database\DatabaseInterface;
/**
* GPS tracking — vehicle location history, geofence alerts, drive time analysis.
*/
class GpsTrackingHelper
{
/**
* Record a GPS ping for a vehicle.
*/
public static function recordPing(int $vehicleId, float $latitude, float $longitude, float $speed = 0): bool
{
if ($latitude < -90 || $latitude > 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() ?: [];
}
}
+1 -1
View File
@@ -2,7 +2,7 @@
<extension type="package" method="upgrade">
<name>Package - MokoSuite Field</name>
<packagename>mokosuitefield</packagename>
<version>01.06.00</version>
<version>01.07.00</version>
<creationDate>2026-06-12</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>