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.
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
namespace Moko\Component\MokoSuiteField\Administrator\View\Dispatch;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
|
||||
use Joomla\CMS\Toolbar\ToolbarHelper;
|
||||
|
||||
class HtmlView extends BaseHtmlView
|
||||
{
|
||||
public array $board = [];
|
||||
public array $unassigned = [];
|
||||
public object $stats;
|
||||
public string $date;
|
||||
|
||||
public function display($tpl = null): void
|
||||
{
|
||||
$this->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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
namespace Moko\Component\MokoSuiteField\Administrator\View\Equipment;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
|
||||
use Joomla\CMS\Toolbar\ToolbarHelper;
|
||||
use Joomla\Database\DatabaseInterface;
|
||||
|
||||
class HtmlView extends BaseHtmlView
|
||||
{
|
||||
public array $equipment = [];
|
||||
public array $serviceDue = [];
|
||||
|
||||
public function display($tpl = null): void
|
||||
{
|
||||
$db = Factory::getContainer()->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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
namespace Moko\Component\MokoSuiteField\Administrator\View\Vehicles;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
|
||||
use Joomla\CMS\Toolbar\ToolbarHelper;
|
||||
|
||||
class HtmlView extends BaseHtmlView
|
||||
{
|
||||
public array $vehicles = [];
|
||||
public array $inspectionsDue = [];
|
||||
|
||||
public function display($tpl = null): void
|
||||
{
|
||||
$this->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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
defined('_JEXEC') or die;
|
||||
$board=$this->board;$s=$this->stats;
|
||||
?>
|
||||
<div class="row g-3 mb-4"><div class="col-md-3"><div class="card shadow-sm"><div class="card-body text-center"><div class="fs-3 fw-bold"><?php echo (int)$s->total_today; ?></div><small>Today</small></div></div></div><div class="col-md-3"><div class="card shadow-sm"><div class="card-body text-center"><div class="fs-3 fw-bold text-danger"><?php echo (int)$s->urgent; ?></div><small>Urgent</small></div></div></div><div class="col-md-3"><div class="card shadow-sm"><div class="card-body text-center"><div class="fs-3 fw-bold text-warning"><?php echo (int)$s->en_route; ?></div><small>En Route</small></div></div></div><div class="col-md-3"><div class="card shadow-sm"><div class="card-body text-center"><div class="fs-3 fw-bold text-success"><?php echo (int)$s->completed; ?></div><small>Done</small></div></div></div></div>
|
||||
<?php foreach($board as $tech): ?>
|
||||
<div class="card shadow-sm mb-2"><div class="card-body p-2"><strong><?php echo $this->escape($tech->tech_name); ?></strong>
|
||||
<?php foreach($tech->jobs as $job): ?><div class="ms-3 small"><?php echo $this->escape($job->wo_number); ?> <?php echo $this->escape($job->customer_name); ?></div><?php endforeach; ?>
|
||||
</div></div>
|
||||
<?php endforeach; ?>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
defined('_JEXEC') or die;
|
||||
$equip=$this->equipment;$due=$this->serviceDue;
|
||||
?>
|
||||
<?php if(!empty($due)): ?><div class="alert alert-warning"><?php echo count($due); ?> equipment due</div><?php endif; ?>
|
||||
<table class="table table-striped"><thead><tr><th>Type</th><th>Make/Model</th><th>Serial</th><th>Owner</th><th>Last Service</th></tr></thead><tbody>
|
||||
<?php foreach($equip as $e): ?>
|
||||
<tr><td><?php echo ucfirst(str_replace("_"," ",$e->equipment_type)); ?></td><td><?php echo $this->escape($e->make." ".$e->model); ?></td><td><code><?php echo $this->escape($e->serial_number); ?></code></td><td><?php echo $this->escape($e->owner_name); ?></td><td><?php echo $e->last_service_date?date("M j",strtotime($e->last_service_date)):"Never"; ?></td></tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody></table>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
defined('_JEXEC') or die;
|
||||
$vehicles=$this->vehicles;
|
||||
?>
|
||||
<table class="table table-striped"><thead><tr><th>Vehicle</th><th>Make/Model</th><th>Assigned To</th><th>Mileage</th><th>Status</th></tr></thead><tbody>
|
||||
<?php foreach($vehicles as $v): ?>
|
||||
<tr><td><strong><?php echo $this->escape($v->vehicle_number); ?></strong></td><td><?php echo $this->escape($v->make." ".$v->model); ?></td><td><?php echo $this->escape($v->assigned_tech_name); ?></td><td><?php echo $v->mileage?number_format((int)$v->mileage):"—"; ?></td><td><?php echo ucfirst($v->status); ?></td></tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody></table>
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
namespace Moko\Plugin\System\MokoSuiteField\Helper;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\Database\DatabaseInterface;
|
||||
|
||||
/**
|
||||
* Truck stock management — per-vehicle parts inventory.
|
||||
*/
|
||||
class TruckStockHelper
|
||||
{
|
||||
public static function getVehicleInventory(int $vehicleId): array
|
||||
{
|
||||
$db = Factory::getContainer()->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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
namespace Moko\Plugin\System\MokoSuiteField\Helper;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\Database\DatabaseInterface;
|
||||
|
||||
/**
|
||||
* Fleet/vehicle management.
|
||||
*/
|
||||
class VehicleHelper
|
||||
{
|
||||
public static function getFleet(): array
|
||||
{
|
||||
$db = Factory::getContainer()->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() ?: [];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user