From ceb03dba48b4b8d99f3ca1e01ab63cb84c6963a7 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sat, 13 Jun 2026 08:14:33 -0500 Subject: [PATCH] feat: equipment, dispatch, vehicles views + truck stock/vehicle helpers TruckStockHelper: per-vehicle inventory, low stock, use/restock parts. VehicleHelper: fleet overview, inspection due dates. Admin views: Equipment list with service due alerts, Dispatch board with date picker and tech assignments, Vehicles fleet with low stock indicators and inspection tracking. All with templates. --- .../admin/src/View/Dispatch/HtmlView.php | 28 ++++++++ .../admin/src/View/Equipment/HtmlView.php | 34 ++++++++++ .../admin/src/View/Vehicles/HtmlView.php | 23 +++++++ .../admin/tmpl/dispatch/default.php | 10 +++ .../admin/tmpl/equipment/default.php | 10 +++ .../admin/tmpl/vehicles/default.php | 9 +++ .../src/Helper/TruckStockHelper.php | 64 +++++++++++++++++++ .../src/Helper/VehicleHelper.php | 44 +++++++++++++ 8 files changed, 222 insertions(+) create mode 100644 source/packages/com_mokosuitefield/admin/src/View/Dispatch/HtmlView.php create mode 100644 source/packages/com_mokosuitefield/admin/src/View/Equipment/HtmlView.php create mode 100644 source/packages/com_mokosuitefield/admin/src/View/Vehicles/HtmlView.php create mode 100644 source/packages/com_mokosuitefield/admin/tmpl/dispatch/default.php create mode 100644 source/packages/com_mokosuitefield/admin/tmpl/equipment/default.php create mode 100644 source/packages/com_mokosuitefield/admin/tmpl/vehicles/default.php create mode 100644 source/packages/plg_system_mokosuitefield/src/Helper/TruckStockHelper.php create mode 100644 source/packages/plg_system_mokosuitefield/src/Helper/VehicleHelper.php diff --git a/source/packages/com_mokosuitefield/admin/src/View/Dispatch/HtmlView.php b/source/packages/com_mokosuitefield/admin/src/View/Dispatch/HtmlView.php new file mode 100644 index 0000000..e59bd25 --- /dev/null +++ b/source/packages/com_mokosuitefield/admin/src/View/Dispatch/HtmlView.php @@ -0,0 +1,28 @@ +date = Factory::getApplication()->getInput()->getString('date', date('Y-m-d')); + + $this->board = \Moko\Plugin\System\MokoSuiteField\Helper\DispatchHelper::getDispatchBoard($this->date); + $this->unassigned = \Moko\Plugin\System\MokoSuiteField\Helper\DispatchHelper::getUnassigned(); + $this->stats = \Moko\Plugin\System\MokoSuiteField\Helper\WorkOrderHelper::getDashboardStats(); + + ToolbarHelper::title('Field Service - Dispatch Board', 'icon-map'); + parent::display($tpl); + } +} diff --git a/source/packages/com_mokosuitefield/admin/src/View/Equipment/HtmlView.php b/source/packages/com_mokosuitefield/admin/src/View/Equipment/HtmlView.php new file mode 100644 index 0000000..5188748 --- /dev/null +++ b/source/packages/com_mokosuitefield/admin/src/View/Equipment/HtmlView.php @@ -0,0 +1,34 @@ +get(DatabaseInterface::class); + + $db->setQuery($db->getQuery(true) + ->select('e.*, loc.address, loc.city, cd.name AS owner_name') + ->from($db->quoteName('#__mokosuitefield_equipment', 'e')) + ->join('LEFT', $db->quoteName('#__mokosuitefield_locations', 'loc') . ' ON loc.id = e.location_id') + ->join('LEFT', $db->quoteName('#__contact_details', 'cd') . ' ON cd.id = e.contact_id') + ->order('e.equipment_type ASC, e.make ASC')); + $this->equipment = $db->loadObjectList() ?: []; + + $this->serviceDue = \Moko\Plugin\System\MokoSuiteField\Helper\EquipmentHelper::getDueForService(30); + + ToolbarHelper::title('Field Service — Equipment', 'icon-cogs'); + ToolbarHelper::addNew('equipment.add'); + parent::display($tpl); + } +} diff --git a/source/packages/com_mokosuitefield/admin/src/View/Vehicles/HtmlView.php b/source/packages/com_mokosuitefield/admin/src/View/Vehicles/HtmlView.php new file mode 100644 index 0000000..1ead5bc --- /dev/null +++ b/source/packages/com_mokosuitefield/admin/src/View/Vehicles/HtmlView.php @@ -0,0 +1,23 @@ +vehicles = \Moko\Plugin\System\MokoSuiteField\Helper\VehicleHelper::getFleet(); + $this->inspectionsDue = \Moko\Plugin\System\MokoSuiteField\Helper\VehicleHelper::getInspectionsDue(30); + + ToolbarHelper::title('Field Service — Vehicles', 'icon-truck'); + ToolbarHelper::addNew('vehicles.add'); + parent::display($tpl); + } +} diff --git a/source/packages/com_mokosuitefield/admin/tmpl/dispatch/default.php b/source/packages/com_mokosuitefield/admin/tmpl/dispatch/default.php new file mode 100644 index 0000000..4cf6a08 --- /dev/null +++ b/source/packages/com_mokosuitefield/admin/tmpl/dispatch/default.php @@ -0,0 +1,10 @@ +board;$s=$this->stats; +?> +
total_today; ?>
Today
urgent; ?>
Urgent
en_route; ?>
En Route
completed; ?>
Done
+ +
escape($tech->tech_name); ?> +jobs as $job): ?>
escape($job->wo_number); ?> escape($job->customer_name); ?>
+
+ diff --git a/source/packages/com_mokosuitefield/admin/tmpl/equipment/default.php b/source/packages/com_mokosuitefield/admin/tmpl/equipment/default.php new file mode 100644 index 0000000..12ec110 --- /dev/null +++ b/source/packages/com_mokosuitefield/admin/tmpl/equipment/default.php @@ -0,0 +1,10 @@ +equipment;$due=$this->serviceDue; +?> +
equipment due
+ + + + +
TypeMake/ModelSerialOwnerLast Service
equipment_type)); ?>escape($e->make." ".$e->model); ?>escape($e->serial_number); ?>escape($e->owner_name); ?>last_service_date?date("M j",strtotime($e->last_service_date)):"Never"; ?>
diff --git a/source/packages/com_mokosuitefield/admin/tmpl/vehicles/default.php b/source/packages/com_mokosuitefield/admin/tmpl/vehicles/default.php new file mode 100644 index 0000000..4ec6984 --- /dev/null +++ b/source/packages/com_mokosuitefield/admin/tmpl/vehicles/default.php @@ -0,0 +1,9 @@ +vehicles; +?> + + + + +
VehicleMake/ModelAssigned ToMileageStatus
escape($v->vehicle_number); ?>escape($v->make." ".$v->model); ?>escape($v->assigned_tech_name); ?>mileage?number_format((int)$v->mileage):"—"; ?>status); ?>
diff --git a/source/packages/plg_system_mokosuitefield/src/Helper/TruckStockHelper.php b/source/packages/plg_system_mokosuitefield/src/Helper/TruckStockHelper.php new file mode 100644 index 0000000..fbd7aa6 --- /dev/null +++ b/source/packages/plg_system_mokosuitefield/src/Helper/TruckStockHelper.php @@ -0,0 +1,64 @@ +get(DatabaseInterface::class); + + $db->setQuery($db->getQuery(true) + ->select('ts.*, p.name AS part_name, p.sku, p.cost_price') + ->from($db->quoteName('#__mokosuitefield_truck_stock', 'ts')) + ->join('INNER', $db->quoteName('#__mokosuite_crm_products', 'p') . ' ON p.id = ts.product_id') + ->where('ts.vehicle_id = ' . $vehicleId) + ->order('p.name ASC')); + + return $db->loadObjectList() ?: []; + } + + public static function getLowStock(): array + { + $db = Factory::getContainer()->get(DatabaseInterface::class); + + $db->setQuery($db->getQuery(true) + ->select('ts.*, p.name AS part_name, p.sku, v.vehicle_number') + ->from($db->quoteName('#__mokosuitefield_truck_stock', 'ts')) + ->join('INNER', $db->quoteName('#__mokosuite_crm_products', 'p') . ' ON p.id = ts.product_id') + ->join('INNER', $db->quoteName('#__mokosuitefield_vehicles', 'v') . ' ON v.id = ts.vehicle_id') + ->where('ts.quantity <= ts.min_quantity') + ->order('ts.quantity ASC')); + + return $db->loadObjectList() ?: []; + } + + public static function usePart(int $vehicleId, int $productId, float $qty = 1): bool + { + $db = Factory::getContainer()->get(DatabaseInterface::class); + + $db->setQuery($db->getQuery(true) + ->update('#__mokosuitefield_truck_stock') + ->set('quantity = quantity - ' . (float) $qty) + ->where('vehicle_id = ' . $vehicleId) + ->where('product_id = ' . $productId)); + $db->execute(); + + return $db->getAffectedRows() > 0; + } + + public static function restock(int $vehicleId, int $productId, float $qty): void + { + $db = Factory::getContainer()->get(DatabaseInterface::class); + + $db->setQuery("INSERT INTO #__mokosuitefield_truck_stock (vehicle_id, product_id, quantity, last_restocked) VALUES ({$vehicleId}, {$productId}, {$qty}, CURDATE()) ON DUPLICATE KEY UPDATE quantity = quantity + {$qty}, last_restocked = CURDATE()"); + $db->execute(); + } +} diff --git a/source/packages/plg_system_mokosuitefield/src/Helper/VehicleHelper.php b/source/packages/plg_system_mokosuitefield/src/Helper/VehicleHelper.php new file mode 100644 index 0000000..0d32603 --- /dev/null +++ b/source/packages/plg_system_mokosuitefield/src/Helper/VehicleHelper.php @@ -0,0 +1,44 @@ +get(DatabaseInterface::class); + + $db->setQuery($db->getQuery(true) + ->select('v.*, cd.name AS assigned_tech_name') + ->select('(SELECT COUNT(*) FROM #__mokosuitefield_truck_stock ts WHERE ts.vehicle_id = v.id AND ts.quantity <= ts.min_quantity) AS low_stock_items') + ->from($db->quoteName('#__mokosuitefield_vehicles', 'v')) + ->join('LEFT', $db->quoteName('#__mokosuitefield_technicians', 't') . ' ON t.id = v.assigned_tech_id') + ->join('LEFT', $db->quoteName('#__contact_details', 'cd') . ' ON cd.id = t.contact_id') + ->order('v.vehicle_number ASC')); + + return $db->loadObjectList() ?: []; + } + + public static function getInspectionsDue(int $daysAhead = 30): array + { + $db = Factory::getContainer()->get(DatabaseInterface::class); + + $db->setQuery($db->getQuery(true) + ->select('v.*, cd.name AS tech_name') + ->from($db->quoteName('#__mokosuitefield_vehicles', 'v')) + ->join('LEFT', $db->quoteName('#__mokosuitefield_technicians', 't') . ' ON t.id = v.assigned_tech_id') + ->join('LEFT', $db->quoteName('#__contact_details', 'cd') . ' ON cd.id = t.contact_id') + ->where($db->quoteName('v.status') . ' = ' . $db->quote('active')) + ->where($db->quoteName('v.next_inspection') . ' BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL ' . $daysAhead . ' DAY)') + ->order('v.next_inspection ASC')); + + return $db->loadObjectList() ?: []; + } +}