diff --git a/source/packages/com_mokosuitefield/admin/access.xml b/source/packages/com_mokosuitefield/admin/access.xml new file mode 100644 index 0000000..6f4f869 --- /dev/null +++ b/source/packages/com_mokosuitefield/admin/access.xml @@ -0,0 +1,11 @@ + + +
+ + + + + + +
+
diff --git a/source/packages/com_mokosuitefield/admin/config.xml b/source/packages/com_mokosuitefield/admin/config.xml new file mode 100644 index 0000000..0c4186f --- /dev/null +++ b/source/packages/com_mokosuitefield/admin/config.xml @@ -0,0 +1,22 @@ + + +
+ + + + + + + + +
+
+ + +
+
+ + + +
+
diff --git a/source/packages/com_mokosuitefield/site/src/Service/Router.php b/source/packages/com_mokosuitefield/site/src/Service/Router.php new file mode 100644 index 0000000..408c728 --- /dev/null +++ b/source/packages/com_mokosuitefield/site/src/Service/Router.php @@ -0,0 +1,21 @@ + [ + '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; + } +} diff --git a/source/packages/plg_webservices_mokosuitefield/src/Extension/MokoSuiteFieldApi.php b/source/packages/plg_webservices_mokosuitefield/src/Extension/MokoSuiteFieldApi.php new file mode 100644 index 0000000..4dfb1fa --- /dev/null +++ b/source/packages/plg_webservices_mokosuitefield/src/Extension/MokoSuiteFieldApi.php @@ -0,0 +1,27 @@ + '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']); + } +} diff --git a/source/updates.xml b/source/updates.xml new file mode 100644 index 0000000..39bccee --- /dev/null +++ b/source/updates.xml @@ -0,0 +1,9 @@ + + +Package - MokoSuite Field +pkg_mokosuitefield +package +01.01.00 + +8.3 +