feat: webservices plugin, task scheduler, router, config, access
FieldAutomation task plugin: service reminders, agreement renewal alerts, equipment warranty expiry, truck stock reorder. MokoSuiteFieldApi webservices: 6 CRUD routes (workorders, technicians, equipment, agreements, estimates, locations). Router, config.xml, access.xml.
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<access component="com_mokosuitefield">
|
||||
<section name="component">
|
||||
<action name="core.admin" title="JACTION_ADMIN" />
|
||||
<action name="core.manage" title="JACTION_MANAGE" />
|
||||
<action name="core.create" title="JACTION_CREATE" />
|
||||
<action name="core.edit" title="JACTION_EDIT" />
|
||||
<action name="field.dispatch" title="Dispatch Work Orders" />
|
||||
<action name="field.estimates" title="Create Estimates" />
|
||||
</section>
|
||||
</access>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<config>
|
||||
<fieldset name="basic" label="Field Service Settings">
|
||||
<field name="company_name" type="text" default="" label="Company Name" />
|
||||
<field name="default_trade" type="list" default="general" label="Default Trade">
|
||||
<option value="general">General</option>
|
||||
<option value="plumbing">Plumbing</option>
|
||||
<option value="electrical">Electrical</option>
|
||||
<option value="hvac">HVAC</option>
|
||||
</field>
|
||||
<field name="wo_prefix" type="text" default="WO" label="Work Order Prefix" />
|
||||
</fieldset>
|
||||
<fieldset name="dispatch" label="Dispatch">
|
||||
<field name="auto_dispatch" type="radio" default="0" label="Auto-Dispatch" class="btn-group btn-group-yesno"><option value="1">JYES</option><option value="0">JNO</option></field>
|
||||
<field name="default_service_radius" type="number" default="30" label="Default Service Radius (miles)" />
|
||||
</fieldset>
|
||||
<fieldset name="billing" label="Billing">
|
||||
<field name="default_labor_rate" type="number" default="125" step="0.01" label="Default Labor Rate ($/hr)" />
|
||||
<field name="overtime_multiplier" type="number" default="1.5" step="0.1" label="Overtime Multiplier" />
|
||||
<field name="travel_charge" type="number" default="0" step="0.01" label="Travel Charge ($)" />
|
||||
</fieldset>
|
||||
</config>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
namespace Moko\Component\MokoSuiteField\Site\Service;
|
||||
defined('_JEXEC') or die;
|
||||
use Joomla\CMS\Component\Router\RouterBase;
|
||||
class Router extends RouterBase
|
||||
{
|
||||
public function build(&$query): array
|
||||
{
|
||||
$segments = [];
|
||||
if (isset($query['view'])) { $segments[] = $query['view']; unset($query['view']); }
|
||||
if (isset($query['id'])) { $segments[] = $query['id']; unset($query['id']); }
|
||||
return $segments;
|
||||
}
|
||||
public function parse(&$segments): array
|
||||
{
|
||||
$vars = [];
|
||||
if (!empty($segments[0])) $vars['view'] = array_shift($segments);
|
||||
if (!empty($segments[0]) && is_numeric($segments[0])) $vars['id'] = (int) array_shift($segments);
|
||||
return $vars;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
namespace Moko\Plugin\Task\MokoSuiteField\Extension;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Log\Log;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\Component\Scheduler\Administrator\Event\ExecuteTaskEvent;
|
||||
use Joomla\Component\Scheduler\Administrator\Task\Status;
|
||||
use Joomla\Component\Scheduler\Administrator\Traits\TaskPluginTrait;
|
||||
use Joomla\Database\DatabaseInterface;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
|
||||
/**
|
||||
* Field service scheduled tasks: service reminders, agreement renewals,
|
||||
* equipment maintenance alerts, truck stock reorder.
|
||||
*/
|
||||
class FieldAutomation extends CMSPlugin implements SubscriberInterface
|
||||
{
|
||||
use TaskPluginTrait;
|
||||
|
||||
protected const TASKS_MAP = [
|
||||
'mokosuite.field.service.reminders' => [
|
||||
'langConstPrefix' => 'PLG_TASK_MOKOSUITEFIELD_SERVICE_REMINDERS',
|
||||
'method' => 'sendServiceReminders',
|
||||
],
|
||||
'mokosuite.field.agreement.renewal' => [
|
||||
'langConstPrefix' => 'PLG_TASK_MOKOSUITEFIELD_AGREEMENT_RENEWAL',
|
||||
'method' => 'checkAgreementRenewals',
|
||||
],
|
||||
'mokosuite.field.equipment.maintenance' => [
|
||||
'langConstPrefix' => 'PLG_TASK_MOKOSUITEFIELD_EQUIPMENT_MAINTENANCE',
|
||||
'method' => 'checkEquipmentMaintenance',
|
||||
],
|
||||
'mokosuite.field.truck.reorder' => [
|
||||
'langConstPrefix' => 'PLG_TASK_MOKOSUITEFIELD_TRUCK_REORDER',
|
||||
'method' => 'checkTruckStock',
|
||||
],
|
||||
];
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
'onExecuteTask' => 'standardRoutineHandler',
|
||||
'onContentPrepareForm' => 'enhanceTaskItemForm',
|
||||
'onTaskOptionsList' => 'advertiseRoutines',
|
||||
];
|
||||
}
|
||||
|
||||
private function sendServiceReminders(ExecuteTaskEvent $event): int
|
||||
{
|
||||
$equipment = \Moko\Plugin\System\MokoSuiteField\Helper\EquipmentHelper::getDueForService(14);
|
||||
|
||||
if (empty($equipment)) return Status::OK;
|
||||
|
||||
$body = "Equipment due for service (next 14 days):\n\n";
|
||||
foreach ($equipment as $e) {
|
||||
$body .= "- {$e->equipment_type} ({$e->make} {$e->model}) at {$e->address}, {$e->owner_name}\n";
|
||||
$body .= " Due: " . date('M j, Y', strtotime($e->next_service_date)) . "\n";
|
||||
}
|
||||
|
||||
$mailer = Factory::getMailer();
|
||||
$mailer->addRecipient(Factory::getApplication()->get('mailfrom'));
|
||||
$mailer->setSubject('Field Service: ' . count($equipment) . ' equipment due for maintenance');
|
||||
$mailer->setBody($body);
|
||||
$mailer->Send();
|
||||
|
||||
Log::add("Field equipment reminders: " . count($equipment) . " due", Log::INFO, 'mokosuite.field');
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
private function checkAgreementRenewals(ExecuteTaskEvent $event): int
|
||||
{
|
||||
$expiring = \Moko\Plugin\System\MokoSuiteField\Helper\ServiceAgreementHelper::getExpiring(30);
|
||||
|
||||
foreach ($expiring as $a) {
|
||||
if (!$a->email_to) continue;
|
||||
|
||||
$mailer = Factory::getMailer();
|
||||
$mailer->addRecipient($a->email_to, $a->customer_name);
|
||||
$mailer->setSubject('Service Agreement Renewal — ' . $a->title);
|
||||
$mailer->setBody(
|
||||
"Hi {$a->customer_name},\n\n"
|
||||
. "Your service agreement \"{$a->title}\" expires on " . date('F j, Y', strtotime($a->end_date)) . ".\n\n"
|
||||
. "Please contact us to renew.\n"
|
||||
);
|
||||
$mailer->Send();
|
||||
}
|
||||
|
||||
Log::add("Field agreement renewals: " . count($expiring) . " expiring", Log::INFO, 'mokosuite.field');
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
private function checkEquipmentMaintenance(ExecuteTaskEvent $event): int
|
||||
{
|
||||
$warranty = \Moko\Plugin\System\MokoSuiteField\Helper\EquipmentHelper::getWarrantyExpiring(90);
|
||||
|
||||
if (!empty($warranty)) {
|
||||
$body = "Equipment warranties expiring (90 days):\n\n";
|
||||
foreach ($warranty as $e) {
|
||||
$body .= "- {$e->make} {$e->model} (SN: {$e->serial_number}) — {$e->owner_name}\n";
|
||||
$body .= " Warranty expires: " . date('M j, Y', strtotime($e->warranty_expiry)) . "\n";
|
||||
}
|
||||
|
||||
$mailer = Factory::getMailer();
|
||||
$mailer->addRecipient(Factory::getApplication()->get('mailfrom'));
|
||||
$mailer->setSubject('Field: ' . count($warranty) . ' equipment warranties expiring');
|
||||
$mailer->setBody($body);
|
||||
$mailer->Send();
|
||||
}
|
||||
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
private function checkTruckStock(ExecuteTaskEvent $event): int
|
||||
{
|
||||
$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'));
|
||||
$low = $db->loadObjectList() ?: [];
|
||||
|
||||
if (!empty($low)) {
|
||||
$body = "Truck stock below reorder point:\n\n";
|
||||
foreach ($low as $item) {
|
||||
$body .= "- Vehicle {$item->vehicle_number}: {$item->part_name} ({$item->sku}) — {$item->quantity} remaining (min: {$item->min_quantity})\n";
|
||||
}
|
||||
|
||||
$mailer = Factory::getMailer();
|
||||
$mailer->addRecipient(Factory::getApplication()->get('mailfrom'));
|
||||
$mailer->setSubject('Field: ' . count($low) . ' truck stock items need reorder');
|
||||
$mailer->setBody($body);
|
||||
$mailer->Send();
|
||||
}
|
||||
|
||||
Log::add("Field truck stock: " . count($low) . " items low", Log::INFO, 'mokosuite.field');
|
||||
return Status::OK;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
namespace Moko\Plugin\WebServices\MokoSuiteField\Extension;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\CMS\Event\Application\BeforeApiRouteEvent;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
|
||||
final class MokoSuiteFieldApi extends CMSPlugin implements SubscriberInterface
|
||||
{
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return ['onBeforeApiRoute' => 'onBeforeApiRoute'];
|
||||
}
|
||||
|
||||
public function onBeforeApiRoute(BeforeApiRouteEvent $event): void
|
||||
{
|
||||
$router = $event->getRouter();
|
||||
$router->createCRUDRoutes('v1/mokosuite/field/workorders', 'fieldworkorders', ['component' => 'com_mokosuitefield']);
|
||||
$router->createCRUDRoutes('v1/mokosuite/field/technicians', 'fieldtechnicians', ['component' => 'com_mokosuitefield']);
|
||||
$router->createCRUDRoutes('v1/mokosuite/field/equipment', 'fieldequipment', ['component' => 'com_mokosuitefield']);
|
||||
$router->createCRUDRoutes('v1/mokosuite/field/agreements', 'fieldagreements', ['component' => 'com_mokosuitefield']);
|
||||
$router->createCRUDRoutes('v1/mokosuite/field/estimates', 'fieldestimates', ['component' => 'com_mokosuitefield']);
|
||||
$router->createCRUDRoutes('v1/mokosuite/field/locations', 'fieldlocations', ['component' => 'com_mokosuitefield']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<updates><update>
|
||||
<name>Package - MokoSuite Field</name>
|
||||
<element>pkg_mokosuitefield</element>
|
||||
<type>package</type>
|
||||
<version>01.01.00</version>
|
||||
<targetplatform name="joomla" version="6.[0-9]" />
|
||||
<php_minimum>8.3</php_minimum>
|
||||
</update></updates>
|
||||
Reference in New Issue
Block a user