7 Commits

48 changed files with 1616 additions and 2 deletions
+12
View File
@@ -0,0 +1,12 @@
# MokoSuite standard ignores
.claude/
.mcp.json
TODO.md
vendor/
node_modules/
.env
*.min.css
*.min.js
.DS_Store
Thumbs.db
*.ppk
+21
View File
@@ -0,0 +1,21 @@
<!--
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
SPDX-License-Identifier: GPL-3.0-or-later
INGROUP: MokoSuiteLogistics.Documentation
BRIEF: Version history using Keep a Changelog
-->
# Changelog
## [Unreleased]
### Added
- **Repository** -- initial repo creation with scaffolding
- **System Plugin** -- Extension class, service provider, LogisticsHelper
- **SQL Schema** -- 8 tables: orders, deliveries, routes, route_stops, delivery_proofs, pricing_rules, drivers, warehouses
- **Admin Component** -- 8 views with ListModels: dashboard, orders, deliveries, routes, warehouses, pricing rules, drivers, proofs
- **Admin Templates** -- functional Joomla 6 list views with pagination and status badges
- **Webservices Plugin** -- 7 API routes: orders, deliveries, routes, warehouses, pricing rules, drivers, proofs
- **Configuration** -- 11 settings across basic, delivery, pricing, notifications
- **Access Control** -- 15 permissions for granular role-based access
- **Component Language Files** -- en-GB translations for menu, submenu, and extension manager
+31
View File
@@ -0,0 +1,31 @@
# MokoSuiteLogistics
Delivery management, route optimization, courier dispatch, and proof of delivery for Joomla 6.
## Quick Reference
| Field | Value |
|---|---|
| **Package** | `pkg_mokosuitelogistics` |
| **Layer** | 2 (requires: Client, CRM) |
| **Language** | PHP 8.3+ |
| **Branch** | develop on `dev`, merge to `main` (protected) |
| **Wiki** | [MokoSuiteLogistics Wiki](https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteLogistics/wiki) |
## Architecture
Joomla **package** -- Layer 2 add-on. CRM contacts as customers/drivers, route-based dispatch with proof of delivery.
## Rules
- **Never commit** `.claude/`, `.mcp.json`, `TODO.md`, `*.min.css`/`*.min.js`
- **Attribution**: `Authored-by: Moko Consulting`
- **Workflow directory**: `.mokogitea/`
- **Standards**: [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoCLI/wiki)
- **Changelog**: `[Unreleased]` only -- release system assigns versions
## Coding Standards
- PHP 8.3+ / Joomla 6 patterns
- `$this->getDatabase()` in models, `Factory::getContainer()->get(DatabaseInterface::class)` in helpers
- `Factory::getApplication()->getIdentity()` for user
+88 -1
View File
@@ -1,3 +1,90 @@
<!--
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
SPDX-License-Identifier: GPL-3.0-or-later
-->
# MokoSuite Logistics # MokoSuite Logistics
MokoSuite Logistics — delivery management, route optimization, courier dispatch, proof of delivery for Joomla 6 Delivery management, route optimization, courier dispatch, and proof of delivery for MokoSuite on Joomla 6.
## Overview
MokoSuiteLogistics is a **Layer 2** extension in the MokoSuite platform, building on MokoSuiteClient (Layer 0) and MokoSuiteCRM (Layer 1) to provide complete logistics and delivery operations management. Customers and drivers are CRM contacts -- no duplicate user tables.
## Package Contents
| Extension | Type | Description |
|---|---|---|
| `plg_system_mokosuitelogistics` | System Plugin | Core helpers, SQL schema, service classes |
| `com_mokosuitelogistics` | Component | Admin dashboard, list views, configuration |
| `plg_webservices_mokosuitelogistics` | Webservices Plugin | REST API endpoints |
## Features
- **Order Management** -- standard, express, same-day, scheduled, and bulk delivery types with full status lifecycle
- **Delivery Tracking** -- real-time delivery status with attempt tracking and failure reasons
- **Route Optimization** -- planned routes with sequenced stops, distance tracking, and completion status
- **Warehouse Management** -- multiple warehouse locations with manager assignments
- **Driver Management** -- CRM contact-linked profiles, vehicle descriptions, rating aggregation
- **Pricing Rules** -- configurable per order type with base fees, per-km/kg rates, express multipliers
- **Proof of Delivery** -- signature capture, photo proof, geolocation verification
- **REST API** -- full CRUD for orders, deliveries, routes, warehouses, pricing rules, drivers, and proofs
## Database Schema
8 tables covering the full logistics domain:
| Table | Purpose |
|---|---|
| `#__mokosuitelogistics_orders` | Order lifecycle with customer, address, package, payment details |
| `#__mokosuitelogistics_deliveries` | Delivery assignments with driver, route, timestamps, attempts |
| `#__mokosuitelogistics_routes` | Planned delivery routes with distance and stop counts |
| `#__mokosuitelogistics_route_stops` | Sequenced stops within routes with arrival tracking |
| `#__mokosuitelogistics_delivery_proofs` | Signature, photo, and geolocation proof of delivery |
| `#__mokosuitelogistics_pricing_rules` | Fee structures per order type |
| `#__mokosuitelogistics_drivers` | Driver profiles linked to CRM contacts |
| `#__mokosuitelogistics_warehouses` | Warehouse locations with manager contacts |
## Order Status Flow
```
pending --> confirmed --> assigned --> picked_up --> in_transit --> delivered
| | | | |
+--> cancelled +--> cancelled +--> failed +--> failed +--> failed
|
+--> returned
```
## Requirements
- Joomla 6.x
- PHP 8.3+
- MokoSuiteClient (Layer 0)
- MokoSuiteCRM (Layer 1)
## Installation
Install via Joomla Extension Manager using the package file `pkg_mokosuitelogistics.zip`. The package installs all three extensions (system plugin, component, webservices plugin) in the correct order.
Updates are delivered automatically via the [MokoGitea update server](https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteLogistics/updates.xml).
## Configuration
Settings are managed via the system plugin parameters:
| Fieldset | Key Settings |
|---|---|
| **Basic** | Company name, default currency, timezone, distance unit |
| **Delivery** | Max delivery radius, default time window, auto-assign |
| **Pricing** | Base delivery fee, per-distance rate, express multiplier, minimum charge |
| **Notifications** | Order confirmation, dispatch notification, delivery proof |
## License
GNU General Public License v3.0 or later.
## Links
- [Documentation](https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteLogistics/wiki)
- [Issues](https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteLogistics/issues)
- [MokoSuite Platform](https://mokoconsulting.tech)
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<access component="com_mokosuitelogistics">
<section name="component">
<action name="core.admin" title="JACTION_ADMIN" />
<action name="core.options" title="JACTION_OPTIONS" />
<action name="core.manage" title="JACTION_MANAGE" />
<action name="core.create" title="JACTION_CREATE" />
<action name="core.delete" title="JACTION_DELETE" />
<action name="core.edit" title="JACTION_EDIT" />
<action name="logistics.orders.manage" title="Manage Orders" />
<action name="logistics.deliveries.manage" title="Manage Deliveries" />
<action name="logistics.routes.manage" title="Manage Routes" />
<action name="logistics.drivers.manage" title="Manage Drivers" />
<action name="logistics.warehouses.manage" title="Manage Warehouses" />
<action name="logistics.pricing.manage" title="Manage Pricing Rules" />
<action name="logistics.proofs.view" title="View Delivery Proofs" />
<action name="logistics.reports" title="View Reports" />
<action name="logistics.settings" title="Manage Settings" />
</section>
</access>
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<config>
<fieldset name="basic" label="Logistics Defaults">
<field name="company_name" type="text" default="" label="Company Name" />
<field name="default_currency" type="text" default="USD" label="Default Currency" />
<field name="timezone" type="text" default="UTC" label="Operating Timezone" />
<field name="distance_unit" type="list" default="mi" label="Distance Unit">
<option value="mi">Miles</option>
<option value="km">Kilometres</option>
</field>
</fieldset>
<fieldset name="delivery" label="Delivery Settings">
<field name="max_delivery_radius" type="number" default="50" label="Max Delivery Radius" hint="In distance units" />
<field name="default_time_window" type="number" default="4" label="Default Time Window (hours)" />
<field name="auto_assign" type="radio" default="1" label="Auto-Assign Drivers" class="btn-group btn-group-yesno">
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
</fieldset>
<fieldset name="pricing" label="Pricing">
<field name="base_delivery_fee" type="number" default="5.00" step="0.01" label="Base Delivery Fee" />
<field name="per_distance_rate" type="number" default="1.00" step="0.01" label="Per Distance Unit Rate" />
<field name="express_multiplier" type="number" default="1.50" step="0.1" label="Express Multiplier" />
<field name="minimum_charge" type="number" default="5.00" step="0.01" label="Minimum Charge" />
</fieldset>
<fieldset name="notifications" label="Notifications">
<field name="order_confirmation" type="radio" default="1" label="Order Confirmation" class="btn-group btn-group-yesno">
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field name="dispatch_notification" type="radio" default="1" label="Dispatch Notification" class="btn-group btn-group-yesno">
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field name="delivery_proof" type="radio" default="1" label="Delivery Proof" class="btn-group btn-group-yesno">
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
</fieldset>
</config>
@@ -0,0 +1,11 @@
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
; SPDX-License-Identifier: GPL-3.0-or-later
COM_MOKOSUITELOGISTICS="MokoSuite Logistics"
COM_MOKOSUITELOGISTICS_DASHBOARD="Dashboard"
COM_MOKOSUITELOGISTICS_ORDERS="Orders"
COM_MOKOSUITELOGISTICS_DELIVERIES="Deliveries"
COM_MOKOSUITELOGISTICS_ROUTES="Routes"
COM_MOKOSUITELOGISTICS_WAREHOUSES="Warehouses"
COM_MOKOSUITELOGISTICS_PRICING="Pricing"
COM_MOKOSUITELOGISTICS_DRIVERS="Drivers"
COM_MOKOSUITELOGISTICS_PROOFS="Proof of Delivery"
@@ -0,0 +1,4 @@
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
; SPDX-License-Identifier: GPL-3.0-or-later
COM_MOKOSUITELOGISTICS="MokoSuite Logistics"
COM_MOKOSUITELOGISTICS_XML_DESCRIPTION="Delivery management, route optimization, courier dispatch, proof of delivery"
@@ -0,0 +1,28 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
defined('_JEXEC') or die;
use Joomla\CMS\Extension\ComponentInterface;
use Joomla\CMS\Extension\MVCComponent;
use Joomla\CMS\Dispatcher\ComponentDispatcherFactoryInterface;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
use Joomla\CMS\Component\Router\RouterFactoryInterface;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
return new class implements ServiceProviderInterface {
public function register(Container $container): void {
$container->set(ComponentInterface::class, function (Container $container) {
$c = new MVCComponent($container->get(ComponentDispatcherFactoryInterface::class));
$c->setMVCFactory($container->get(MVCFactoryInterface::class));
$c->setRouterFactory($container->get(RouterFactoryInterface::class));
return $c;
});
}
};
@@ -0,0 +1,18 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
namespace Moko\Component\MokoSuiteLogistics\Administrator\Controller;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\Controller\BaseController;
class DisplayController extends BaseController
{
protected $default_view = 'logisticsdashboard';
}
@@ -0,0 +1,35 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
namespace Moko\Component\MokoSuiteLogistics\Administrator\Model;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\Model\ListModel;
use Joomla\Database\QueryInterface;
class DeliveriesModel extends ListModel
{
protected function getListQuery(): QueryInterface
{
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select('a.*')
->from($db->quoteName('#__mokosuitelogistics_deliveries', 'a'))
->order($db->escape($this->getState('list.ordering', 'a.created'))
. ' ' . $db->escape($this->getState('list.direction', 'DESC')));
return $query;
}
protected function populateState($ordering = 'a.created', $direction = 'DESC'): void
{
parent::populateState($ordering, $direction);
}
}
@@ -0,0 +1,35 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
namespace Moko\Component\MokoSuiteLogistics\Administrator\Model;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\Model\ListModel;
use Joomla\Database\QueryInterface;
class DriversModel extends ListModel
{
protected function getListQuery(): QueryInterface
{
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select('a.*')
->from($db->quoteName('#__mokosuitelogistics_drivers', 'a'))
->order($db->escape($this->getState('list.ordering', 'a.name'))
. ' ' . $db->escape($this->getState('list.direction', 'ASC')));
return $query;
}
protected function populateState($ordering = 'a.name', $direction = 'ASC'): void
{
parent::populateState($ordering, $direction);
}
}
@@ -0,0 +1,22 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
namespace Moko\Component\MokoSuiteLogistics\Administrator\Model;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
use Moko\Plugin\System\MokoSuiteLogistics\Helper\LogisticsHelper;
class LogisticsDashboardModel extends BaseDatabaseModel
{
public function getDashboard(): object
{
return LogisticsHelper::getDashboard();
}
}
@@ -0,0 +1,52 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
namespace Moko\Component\MokoSuiteLogistics\Administrator\Model;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\Model\ListModel;
use Joomla\Database\QueryInterface;
class OrdersModel extends ListModel
{
protected function getListQuery(): QueryInterface
{
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select([
'o.*',
'd.name AS driver_name',
])
->from($db->quoteName('#__mokosuitelogistics_orders', 'o'))
->join('LEFT', $db->quoteName('#__mokosuitelogistics_deliveries', 'del') . ' ON del.order_id = o.id')
->join('LEFT', $db->quoteName('#__mokosuitelogistics_drivers', 'd') . ' ON d.id = del.driver_id');
$search = $this->getState('filter.search');
if ($search) {
$search = $db->quote('%' . $db->escape($search, true) . '%');
$query->where('(o.order_ref LIKE ' . $search . ' OR o.customer_name LIKE ' . $search . ')');
}
$status = $this->getState('filter.status');
if ($status) {
$query->where($db->quoteName('o.status') . ' = ' . $db->quote($status));
}
$query->order($db->escape($this->getState('list.ordering', 'o.created'))
. ' ' . $db->escape($this->getState('list.direction', 'DESC')));
return $query;
}
protected function populateState($ordering = 'o.created', $direction = 'DESC'): void
{
parent::populateState($ordering, $direction);
}
}
@@ -0,0 +1,35 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
namespace Moko\Component\MokoSuiteLogistics\Administrator\Model;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\Model\ListModel;
use Joomla\Database\QueryInterface;
class PricingRulesModel extends ListModel
{
protected function getListQuery(): QueryInterface
{
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select('a.*')
->from($db->quoteName('#__mokosuitelogistics_pricing_rules', 'a'))
->order($db->escape($this->getState('list.ordering', 'a.name'))
. ' ' . $db->escape($this->getState('list.direction', 'ASC')));
return $query;
}
protected function populateState($ordering = 'a.name', $direction = 'ASC'): void
{
parent::populateState($ordering, $direction);
}
}
@@ -0,0 +1,35 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
namespace Moko\Component\MokoSuiteLogistics\Administrator\Model;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\Model\ListModel;
use Joomla\Database\QueryInterface;
class ProofsModel extends ListModel
{
protected function getListQuery(): QueryInterface
{
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select('a.*')
->from($db->quoteName('#__mokosuitelogistics_delivery_proofs', 'a'))
->order($db->escape($this->getState('list.ordering', 'a.created'))
. ' ' . $db->escape($this->getState('list.direction', 'DESC')));
return $query;
}
protected function populateState($ordering = 'a.created', $direction = 'DESC'): void
{
parent::populateState($ordering, $direction);
}
}
@@ -0,0 +1,35 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
namespace Moko\Component\MokoSuiteLogistics\Administrator\Model;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\Model\ListModel;
use Joomla\Database\QueryInterface;
class RoutesModel extends ListModel
{
protected function getListQuery(): QueryInterface
{
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select('a.*')
->from($db->quoteName('#__mokosuitelogistics_routes', 'a'))
->order($db->escape($this->getState('list.ordering', 'a.created'))
. ' ' . $db->escape($this->getState('list.direction', 'DESC')));
return $query;
}
protected function populateState($ordering = 'a.created', $direction = 'DESC'): void
{
parent::populateState($ordering, $direction);
}
}
@@ -0,0 +1,35 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
namespace Moko\Component\MokoSuiteLogistics\Administrator\Model;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\Model\ListModel;
use Joomla\Database\QueryInterface;
class WarehousesModel extends ListModel
{
protected function getListQuery(): QueryInterface
{
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select('a.*')
->from($db->quoteName('#__mokosuitelogistics_warehouses', 'a'))
->order($db->escape($this->getState('list.ordering', 'a.name'))
. ' ' . $db->escape($this->getState('list.direction', 'ASC')));
return $query;
}
protected function populateState($ordering = 'a.name', $direction = 'ASC'): void
{
parent::populateState($ordering, $direction);
}
}
@@ -0,0 +1,32 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
namespace Moko\Component\MokoSuiteLogistics\Administrator\View\Deliveries;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Toolbar\ToolbarHelper;
class HtmlView extends BaseHtmlView
{
protected array $items = [];
protected mixed $pagination = null;
protected mixed $state = null;
public function display($tpl = null): void
{
$this->items = $this->get('Items') ?: [];
$this->pagination = $this->get('Pagination');
$this->state = $this->get('State');
ToolbarHelper::title('MokoSuite Logistics - Deliveries');
parent::display($tpl);
}
}
@@ -0,0 +1,32 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
namespace Moko\Component\MokoSuiteLogistics\Administrator\View\Drivers;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Toolbar\ToolbarHelper;
class HtmlView extends BaseHtmlView
{
protected array $items = [];
protected mixed $pagination = null;
protected mixed $state = null;
public function display($tpl = null): void
{
$this->items = $this->get('Items') ?: [];
$this->pagination = $this->get('Pagination');
$this->state = $this->get('State');
ToolbarHelper::title('MokoSuite Logistics - Drivers');
parent::display($tpl);
}
}
@@ -0,0 +1,29 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
namespace Moko\Component\MokoSuiteLogistics\Administrator\View\LogisticsDashboard;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Toolbar\ToolbarHelper;
class HtmlView extends BaseHtmlView
{
protected object $dashboard;
public function display($tpl = null): void
{
$this->dashboard = $this->get('Dashboard');
ToolbarHelper::title('MokoSuite Logistics - Dashboard');
ToolbarHelper::preferences('com_mokosuitelogistics');
parent::display($tpl);
}
}
@@ -0,0 +1,32 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
namespace Moko\Component\MokoSuiteLogistics\Administrator\View\Orders;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Toolbar\ToolbarHelper;
class HtmlView extends BaseHtmlView
{
protected array $items = [];
protected mixed $pagination = null;
protected mixed $state = null;
public function display($tpl = null): void
{
$this->items = $this->get('Items') ?: [];
$this->pagination = $this->get('Pagination');
$this->state = $this->get('State');
ToolbarHelper::title('MokoSuite Logistics - Orders');
parent::display($tpl);
}
}
@@ -0,0 +1,32 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
namespace Moko\Component\MokoSuiteLogistics\Administrator\View\PricingRules;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Toolbar\ToolbarHelper;
class HtmlView extends BaseHtmlView
{
protected array $items = [];
protected mixed $pagination = null;
protected mixed $state = null;
public function display($tpl = null): void
{
$this->items = $this->get('Items') ?: [];
$this->pagination = $this->get('Pagination');
$this->state = $this->get('State');
ToolbarHelper::title('MokoSuite Logistics - Pricing Rules');
parent::display($tpl);
}
}
@@ -0,0 +1,32 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
namespace Moko\Component\MokoSuiteLogistics\Administrator\View\Proofs;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Toolbar\ToolbarHelper;
class HtmlView extends BaseHtmlView
{
protected array $items = [];
protected mixed $pagination = null;
protected mixed $state = null;
public function display($tpl = null): void
{
$this->items = $this->get('Items') ?: [];
$this->pagination = $this->get('Pagination');
$this->state = $this->get('State');
ToolbarHelper::title('MokoSuite Logistics - Proof of Delivery');
parent::display($tpl);
}
}
@@ -0,0 +1,32 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
namespace Moko\Component\MokoSuiteLogistics\Administrator\View\Routes;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Toolbar\ToolbarHelper;
class HtmlView extends BaseHtmlView
{
protected array $items = [];
protected mixed $pagination = null;
protected mixed $state = null;
public function display($tpl = null): void
{
$this->items = $this->get('Items') ?: [];
$this->pagination = $this->get('Pagination');
$this->state = $this->get('State');
ToolbarHelper::title('MokoSuite Logistics - Routes');
parent::display($tpl);
}
}
@@ -0,0 +1,32 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
namespace Moko\Component\MokoSuiteLogistics\Administrator\View\Warehouses;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Toolbar\ToolbarHelper;
class HtmlView extends BaseHtmlView
{
protected array $items = [];
protected mixed $pagination = null;
protected mixed $state = null;
public function display($tpl = null): void
{
$this->items = $this->get('Items') ?: [];
$this->pagination = $this->get('Pagination');
$this->state = $this->get('State');
ToolbarHelper::title('MokoSuite Logistics - Warehouses');
parent::display($tpl);
}
}
@@ -0,0 +1,14 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
defined('_JEXEC') or die;
?>
<div class="alert alert-info">
<h4>Deliveries</h4>
<p>Coming soon.</p>
</div>
@@ -0,0 +1,14 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
defined('_JEXEC') or die;
?>
<div class="alert alert-info">
<h4>Drivers</h4>
<p>Coming soon.</p>
</div>
@@ -0,0 +1,62 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
defined('_JEXEC') or die;
$d = $this->dashboard;
?>
<div class="row g-3 mb-4">
<div class="col-md-4">
<div class="card text-bg-primary">
<div class="card-body">
<h5 class="card-title">Total Orders</h5>
<p class="display-6"><?php echo (int) $d->total_orders; ?></p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card text-bg-success">
<div class="card-body">
<h5 class="card-title">Active Deliveries</h5>
<p class="display-6"><?php echo (int) $d->active_deliveries; ?></p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card text-bg-info">
<div class="card-body">
<h5 class="card-title">Available Drivers</h5>
<p class="display-6"><?php echo (int) $d->available_drivers; ?></p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">Total Drivers</h5>
<p class="display-6"><?php echo (int) $d->total_drivers; ?></p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">Total Warehouses</h5>
<p class="display-6"><?php echo (int) $d->total_warehouses; ?></p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card text-bg-warning">
<div class="card-body">
<h5 class="card-title">Today's Revenue</h5>
<p class="display-6"><?php echo number_format((float) $d->today_revenue, 2); ?></p>
</div>
</div>
</div>
</div>
@@ -0,0 +1,68 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Router\Route;
$listOrder = $this->escape($this->state->get('list.ordering'));
$listDirn = $this->escape($this->state->get('list.direction'));
$statusBadges = [
'pending' => 'secondary',
'confirmed' => 'info',
'assigned' => 'warning',
'picked_up' => 'primary',
'in_transit' => 'primary',
'delivered' => 'success',
'failed' => 'danger',
'cancelled' => 'danger',
'returned' => 'dark',
];
?>
<form action="<?php echo Route::_('index.php?option=com_mokosuitelogistics&view=orders'); ?>" method="post" name="adminForm" id="adminForm">
<div id="j-main-container">
<table class="table table-striped">
<thead>
<tr>
<th style="width:1%">#</th>
<th><?php echo HTMLHelper::_('searchtools.sort', 'Order Ref', 'o.order_ref', $listDirn, $listOrder); ?></th>
<th>Status</th>
<th>Customer</th>
<th>Driver</th>
<th>Pickup</th>
<th><?php echo HTMLHelper::_('searchtools.sort', 'Delivery Fee', 'o.delivery_fee', $listDirn, $listOrder); ?></th>
<th><?php echo HTMLHelper::_('searchtools.sort', 'Created', 'o.created', $listDirn, $listOrder); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($this->items as $i => $item) :
$badge = $statusBadges[$item->status] ?? 'secondary';
?>
<tr>
<td><?php echo $this->pagination->getRowOffset($i); ?></td>
<td><strong><?php echo $this->escape($item->order_ref); ?></strong></td>
<td><span class="badge bg-<?php echo $badge; ?>"><?php echo $this->escape($item->status); ?></span></td>
<td><?php echo $this->escape($item->customer_name); ?></td>
<td><?php echo $this->escape($item->driver_name ?? ''); ?></td>
<td><?php echo $this->escape($item->pickup_address); ?></td>
<td><?php echo $item->delivery_fee ? number_format((float) $item->delivery_fee, 2) : '-'; ?></td>
<td><?php echo HTMLHelper::_('date', $item->created, 'Y-m-d H:i'); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php echo $this->pagination->getListFooter(); ?>
<input type="hidden" name="task" value="">
<input type="hidden" name="boxchecked" value="0">
<?php echo HTMLHelper::_('form.token'); ?>
</div>
</form>
@@ -0,0 +1,14 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
defined('_JEXEC') or die;
?>
<div class="alert alert-info">
<h4>Pricing Rules</h4>
<p>Coming soon.</p>
</div>
@@ -0,0 +1,14 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
defined('_JEXEC') or die;
?>
<div class="alert alert-info">
<h4>Proof of Delivery</h4>
<p>Coming soon.</p>
</div>
@@ -0,0 +1,14 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
defined('_JEXEC') or die;
?>
<div class="alert alert-info">
<h4>Routes</h4>
<p>Coming soon.</p>
</div>
@@ -0,0 +1,14 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
defined('_JEXEC') or die;
?>
<div class="alert alert-info">
<h4>Warehouses</h4>
<p>Coming soon.</p>
</div>
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<extension type="component" method="upgrade">
<name>MokoSuite Logistics</name>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<creationDate>2026-06-27</creationDate>
<copyright>Copyright (C) 2026 Moko Consulting.</copyright>
<license>GPL-3.0-or-later</license>
<version>06.00.00</version>
<description>Delivery management, route optimization, courier dispatch, proof of delivery</description>
<namespace path="src">Moko\Component\MokoSuiteLogistics</namespace>
<administration>
<files folder="admin">
<filename>access.xml</filename>
<filename>config.xml</filename>
<folder>language</folder>
<folder>services</folder>
<folder>src</folder>
<folder>tmpl</folder>
</files>
<languages folder="admin/language">
<language tag="en-GB">en-GB/com_mokosuitelogistics.ini</language>
<language tag="en-GB">en-GB/com_mokosuitelogistics.sys.ini</language>
</languages>
<menu>COM_MOKOSUITELOGISTICS</menu>
<submenu>
<menu link="option=com_mokosuitelogistics&amp;view=logisticsdashboard">COM_MOKOSUITELOGISTICS_DASHBOARD</menu>
<menu link="option=com_mokosuitelogistics&amp;view=orders">COM_MOKOSUITELOGISTICS_ORDERS</menu>
<menu link="option=com_mokosuitelogistics&amp;view=deliveries">COM_MOKOSUITELOGISTICS_DELIVERIES</menu>
<menu link="option=com_mokosuitelogistics&amp;view=routes">COM_MOKOSUITELOGISTICS_ROUTES</menu>
<menu link="option=com_mokosuitelogistics&amp;view=warehouses">COM_MOKOSUITELOGISTICS_WAREHOUSES</menu>
<menu link="option=com_mokosuitelogistics&amp;view=pricingrules">COM_MOKOSUITELOGISTICS_PRICING</menu>
<menu link="option=com_mokosuitelogistics&amp;view=drivers">COM_MOKOSUITELOGISTICS_DRIVERS</menu>
<menu link="option=com_mokosuitelogistics&amp;view=proofs">COM_MOKOSUITELOGISTICS_PROOFS</menu>
</submenu>
</administration>
</extension>
@@ -0,0 +1,2 @@
PLG_SYSTEM_MOKOSUITELOGISTICS="System - MokoSuite Logistics"
PLG_SYSTEM_MOKOSUITELOGISTICS_DESC="Delivery management, route optimization, courier dispatch, proof of delivery"
@@ -0,0 +1,2 @@
PLG_SYSTEM_MOKOSUITELOGISTICS="System - MokoSuite Logistics"
PLG_SYSTEM_MOKOSUITELOGISTICS_DESC="Delivery management, route optimization, courier dispatch, proof of delivery"
@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<extension type="plugin" group="system" method="upgrade">
<name>System - MokoSuite Logistics</name>
<element>mokosuitelogistics</element>
<author>Moko Consulting</author>
<creationDate>2026-06-27</creationDate>
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
<license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>06.00.00</version>
<php_minimum>8.3</php_minimum>
<description>PLG_SYSTEM_MOKOSUITELOGISTICS_DESC</description>
<namespace path="src">Moko\Plugin\System\MokoSuiteLogistics</namespace>
<files>
<folder>src</folder>
<folder>services</folder>
<folder>language</folder>
<folder>sql</folder>
</files>
<languages folder="language">
<language tag="en-GB">en-GB/plg_system_mokosuitelogistics.ini</language>
<language tag="en-GB">en-GB/plg_system_mokosuitelogistics.sys.ini</language>
</languages>
<install><sql><file driver="mysql" charset="utf8">sql/install.mysql.sql</file></sql></install>
<uninstall><sql><file driver="mysql" charset="utf8">sql/uninstall.mysql.sql</file></sql></uninstall>
<config>
<fields name="params">
<fieldset name="basic" label="Logistics Defaults">
<field name="company_name" type="text" default="" label="Company Name" />
<field name="default_currency" type="text" default="USD" label="Default Currency" />
<field name="timezone" type="text" default="UTC" label="Operating Timezone" />
<field name="distance_unit" type="list" default="mi" label="Distance Unit">
<option value="mi">Miles</option>
<option value="km">Kilometres</option>
</field>
</fieldset>
<fieldset name="delivery" label="Delivery Settings">
<field name="max_delivery_radius" type="number" default="50" label="Max Delivery Radius" hint="In distance units" />
<field name="default_time_window" type="number" default="4" label="Default Time Window (hours)" />
<field name="auto_assign" type="radio" default="1" label="Auto-Assign Drivers" class="btn-group btn-group-yesno">
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
</fieldset>
<fieldset name="pricing" label="Pricing">
<field name="base_delivery_fee" type="number" default="5.00" step="0.01" label="Base Delivery Fee" />
<field name="per_distance_rate" type="number" default="1.00" step="0.01" label="Per Distance Unit Rate" />
<field name="express_multiplier" type="number" default="1.50" step="0.1" label="Express Multiplier" />
<field name="minimum_charge" type="number" default="5.00" step="0.01" label="Minimum Charge" />
</fieldset>
<fieldset name="notifications" label="Notifications">
<field name="order_confirmation" type="radio" default="1" label="Order Confirmation" class="btn-group btn-group-yesno">
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field name="dispatch_notification" type="radio" default="1" label="Dispatch Notification" class="btn-group btn-group-yesno">
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field name="delivery_proof" type="radio" default="1" label="Delivery Proof" class="btn-group btn-group-yesno">
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
</fieldset>
</fields>
</config>
</extension>
@@ -0,0 +1,33 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
defined('_JEXEC') or die;
use Joomla\CMS\Extension\PluginInterface;
use Joomla\CMS\Factory;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
use Joomla\Event\DispatcherInterface;
use Moko\Plugin\System\MokoSuiteLogistics\Extension\Logistics;
return new class implements ServiceProviderInterface
{
public function register(Container $container): void
{
$container->set(
PluginInterface::class,
function (Container $container) {
$dispatcher = $container->get(DispatcherInterface::class);
$plugin = new Logistics($dispatcher, (array) PluginHelper::getPlugin('system', 'mokosuitelogistics'));
$plugin->setApplication(Factory::getApplication());
return $plugin;
}
);
}
};
@@ -0,0 +1,155 @@
CREATE TABLE IF NOT EXISTS `#__mokosuitelogistics_orders` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`order_ref` VARCHAR(20) NOT NULL,
`customer_contact_id` INT DEFAULT NULL,
`customer_name` VARCHAR(255) NOT NULL,
`customer_phone` VARCHAR(50) NOT NULL DEFAULT '',
`customer_email` VARCHAR(255) NOT NULL DEFAULT '',
`status` ENUM('pending','confirmed','assigned','picked_up','in_transit','delivered','failed','cancelled','returned') NOT NULL DEFAULT 'pending',
`order_type` ENUM('standard','express','same_day','scheduled','bulk') NOT NULL DEFAULT 'standard',
`pickup_address` VARCHAR(500) NOT NULL,
`pickup_lat` DECIMAL(10,7) DEFAULT NULL,
`pickup_lng` DECIMAL(10,7) DEFAULT NULL,
`delivery_address` VARCHAR(500) NOT NULL,
`delivery_lat` DECIMAL(10,7) DEFAULT NULL,
`delivery_lng` DECIMAL(10,7) DEFAULT NULL,
`package_type` ENUM('envelope','small_box','medium_box','large_box','pallet','fragile','perishable','custom') NOT NULL DEFAULT 'small_box',
`weight_kg` DECIMAL(8,2) DEFAULT NULL,
`estimated_distance_km` DECIMAL(10,2) DEFAULT NULL,
`delivery_fee` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
`total_charge` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
`payment_method` ENUM('cash','card','account','prepaid','cod') NOT NULL DEFAULT 'card',
`payment_status` ENUM('pending','paid','refunded','failed') NOT NULL DEFAULT 'pending',
`notes` TEXT,
`created` DATETIME NOT NULL,
`created_by` INT NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_ref` (`order_ref`),
KEY `idx_customer` (`customer_contact_id`),
KEY `idx_status` (`status`),
KEY `idx_type` (`order_type`),
KEY `idx_created` (`created`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `#__mokosuitelogistics_deliveries` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`order_id` INT UNSIGNED NOT NULL,
`driver_id` INT UNSIGNED DEFAULT NULL,
`route_id` INT UNSIGNED DEFAULT NULL,
`status` ENUM('assigned','en_route_pickup','picked_up','en_route_delivery','delivered','failed','returned') NOT NULL DEFAULT 'assigned',
`assigned_at` DATETIME DEFAULT NULL,
`picked_up_at` DATETIME DEFAULT NULL,
`delivered_at` DATETIME DEFAULT NULL,
`failed_at` DATETIME DEFAULT NULL,
`failure_reason` VARCHAR(500) NOT NULL DEFAULT '',
`attempt_number` TINYINT UNSIGNED NOT NULL DEFAULT 1,
`actual_distance_km` DECIMAL(10,2) DEFAULT NULL,
`driver_notes` TEXT,
`created` DATETIME NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_order` (`order_id`),
KEY `idx_driver` (`driver_id`),
KEY `idx_route` (`route_id`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `#__mokosuitelogistics_routes` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`route_ref` VARCHAR(20) NOT NULL,
`driver_id` INT UNSIGNED DEFAULT NULL,
`status` ENUM('planned','active','completed','cancelled') NOT NULL DEFAULT 'planned',
`planned_date` DATE NOT NULL,
`started_at` DATETIME DEFAULT NULL,
`completed_at` DATETIME DEFAULT NULL,
`total_stops` INT UNSIGNED NOT NULL DEFAULT 0,
`total_distance_km` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
`notes` TEXT,
`created` DATETIME NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_ref` (`route_ref`),
KEY `idx_driver` (`driver_id`),
KEY `idx_status` (`status`),
KEY `idx_date` (`planned_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `#__mokosuitelogistics_route_stops` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`route_id` INT UNSIGNED NOT NULL,
`order_id` INT UNSIGNED DEFAULT NULL,
`stop_type` ENUM('pickup','delivery','warehouse','return') NOT NULL DEFAULT 'delivery',
`address` VARCHAR(500) NOT NULL,
`lat` DECIMAL(10,7) DEFAULT NULL,
`lng` DECIMAL(10,7) DEFAULT NULL,
`sequence_order` INT UNSIGNED NOT NULL DEFAULT 0,
`estimated_arrival` DATETIME DEFAULT NULL,
`actual_arrival` DATETIME DEFAULT NULL,
`status` ENUM('pending','arrived','completed','skipped') NOT NULL DEFAULT 'pending',
`notes` TEXT,
PRIMARY KEY (`id`),
KEY `idx_route` (`route_id`),
KEY `idx_order` (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `#__mokosuitelogistics_delivery_proofs` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`delivery_id` INT UNSIGNED NOT NULL,
`proof_type` ENUM('signature','photo','both') NOT NULL DEFAULT 'photo',
`recipient_name` VARCHAR(255) NOT NULL DEFAULT '',
`signature_data` TEXT,
`photo_path` VARCHAR(500) NOT NULL DEFAULT '',
`geolocation_lat` DECIMAL(10,7) DEFAULT NULL,
`geolocation_lng` DECIMAL(10,7) DEFAULT NULL,
`notes` TEXT,
`created` DATETIME NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_delivery` (`delivery_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `#__mokosuitelogistics_pricing_rules` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
`order_type` ENUM('standard','express','same_day','scheduled','bulk','all') NOT NULL DEFAULT 'all',
`base_fee` DECIMAL(10,2) NOT NULL DEFAULT 5.00,
`per_km` DECIMAL(10,2) NOT NULL DEFAULT 1.00,
`per_kg` DECIMAL(10,2) NOT NULL DEFAULT 0.50,
`minimum_charge` DECIMAL(10,2) NOT NULL DEFAULT 5.00,
`express_multiplier` DECIMAL(4,2) NOT NULL DEFAULT 1.50,
`published` TINYINT NOT NULL DEFAULT 1,
`created` DATETIME NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_order_type` (`order_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `#__mokosuitelogistics_drivers` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`contact_id` INT DEFAULT NULL,
`name` VARCHAR(255) NOT NULL,
`phone` VARCHAR(50) NOT NULL DEFAULT '',
`email` VARCHAR(255) NOT NULL DEFAULT '',
`license_number` VARCHAR(50) NOT NULL DEFAULT '',
`vehicle_description` VARCHAR(255) NOT NULL DEFAULT '',
`status` ENUM('available','on_delivery','off_duty','inactive') NOT NULL DEFAULT 'available',
`current_lat` DECIMAL(10,7) DEFAULT NULL,
`current_lng` DECIMAL(10,7) DEFAULT NULL,
`rating` DECIMAL(3,2) NOT NULL DEFAULT 5.00,
`total_deliveries` INT UNSIGNED NOT NULL DEFAULT 0,
`published` TINYINT NOT NULL DEFAULT 1,
`created` DATETIME NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_contact` (`contact_id`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `#__mokosuitelogistics_warehouses` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
`address` VARCHAR(500) NOT NULL,
`lat` DECIMAL(10,7) DEFAULT NULL,
`lng` DECIMAL(10,7) DEFAULT NULL,
`manager_contact_id` INT DEFAULT NULL,
`phone` VARCHAR(50) NOT NULL DEFAULT '',
`published` TINYINT NOT NULL DEFAULT 1,
`created` DATETIME NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_manager` (`manager_contact_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
@@ -0,0 +1,8 @@
DROP TABLE IF EXISTS `#__mokosuitelogistics_warehouses`;
DROP TABLE IF EXISTS `#__mokosuitelogistics_drivers`;
DROP TABLE IF EXISTS `#__mokosuitelogistics_pricing_rules`;
DROP TABLE IF EXISTS `#__mokosuitelogistics_delivery_proofs`;
DROP TABLE IF EXISTS `#__mokosuitelogistics_route_stops`;
DROP TABLE IF EXISTS `#__mokosuitelogistics_routes`;
DROP TABLE IF EXISTS `#__mokosuitelogistics_deliveries`;
DROP TABLE IF EXISTS `#__mokosuitelogistics_orders`;
@@ -0,0 +1,24 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
namespace Moko\Plugin\System\MokoSuiteLogistics\Extension;
defined('_JEXEC') or die;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\Event\SubscriberInterface;
class Logistics extends CMSPlugin implements SubscriberInterface
{
protected $autoloadLanguage = true;
public static function getSubscribedEvents(): array
{
return [];
}
}
@@ -0,0 +1,87 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
declare(strict_types=1);
namespace Moko\Plugin\System\MokoSuiteLogistics\Helper;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\Database\DatabaseInterface;
class LogisticsHelper
{
public static function getDashboard(): object
{
$db = Factory::getContainer()->get(DatabaseInterface::class);
$db->setQuery($db->getQuery(true)
->select('COUNT(*)')
->from('#__mokosuitelogistics_orders'));
$totalOrders = (int) $db->loadResult();
$db->setQuery($db->getQuery(true)
->select('COUNT(*)')
->from('#__mokosuitelogistics_deliveries')
->where($db->quoteName('status') . ' IN (' . implode(',', array_map([$db, 'quote'], ['assigned', 'en_route_pickup', 'picked_up', 'en_route_delivery'])) . ')'));
$activeDeliveries = (int) $db->loadResult();
$db->setQuery($db->getQuery(true)
->select('COUNT(*)')
->from('#__mokosuitelogistics_drivers')
->where($db->quoteName('status') . ' = ' . $db->quote('available'))
->where($db->quoteName('published') . ' = 1'));
$availableDrivers = (int) $db->loadResult();
$db->setQuery($db->getQuery(true)
->select('COUNT(*)')
->from('#__mokosuitelogistics_drivers')
->where($db->quoteName('published') . ' = 1'));
$totalDrivers = (int) $db->loadResult();
$db->setQuery($db->getQuery(true)
->select('COUNT(*)')
->from('#__mokosuitelogistics_warehouses')
->where($db->quoteName('published') . ' = 1'));
$totalWarehouses = (int) $db->loadResult();
$db->setQuery($db->getQuery(true)
->select('COALESCE(SUM(' . $db->quoteName('total_charge') . '), 0)')
->from('#__mokosuitelogistics_orders')
->where($db->quoteName('status') . ' = ' . $db->quote('delivered'))
->where($db->quoteName('payment_status') . ' = ' . $db->quote('paid'))
->where('DATE(' . $db->quoteName('created') . ') = CURDATE()'));
$todayRevenue = (float) $db->loadResult();
return (object) [
'total_orders' => $totalOrders,
'active_deliveries' => $activeDeliveries,
'available_drivers' => $availableDrivers,
'total_drivers' => $totalDrivers,
'total_warehouses' => $totalWarehouses,
'today_revenue' => $todayRevenue,
];
}
public static function generateOrderRef(): string
{
$db = Factory::getContainer()->get(DatabaseInterface::class);
$prefix = 'LG';
do {
$ref = $prefix . strtoupper(bin2hex(random_bytes(4)));
$db->setQuery($db->getQuery(true)
->select('COUNT(*)')
->from('#__mokosuitelogistics_orders')
->where($db->quoteName('order_ref') . ' = ' . $db->quote($ref)));
} while ((int) $db->loadResult() > 0);
return $ref;
}
}
@@ -0,0 +1,15 @@
<?xml version="1.0"?>
<extension type="plugin" group="webservices" method="upgrade">
<name>Web Services - MokoSuite Logistics</name>
<element>mokosuitelogistics</element>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<creationDate>2026-06-27</creationDate>
<copyright>Copyright (C) 2026 Moko Consulting.</copyright>
<version>06.00.00</version>
<license>GPL-3.0-or-later</license>
<description>REST API routes for MokoSuite Logistics delivery operations</description>
<namespace path="src">Moko\Plugin\WebServices\MokoSuiteLogistics</namespace>
<files><folder>src</folder><folder>services</folder></files>
</extension>
@@ -0,0 +1,24 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
defined('_JEXEC') or die;
use Joomla\CMS\Extension\PluginInterface;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
use Joomla\Event\DispatcherInterface;
use Moko\Plugin\WebServices\MokoSuiteLogistics\Extension\MokoSuiteLogistics;
return new class implements ServiceProviderInterface {
public function register(Container $container): void {
$container->set(PluginInterface::class, function (Container $container) {
return new MokoSuiteLogistics($container->get(DispatcherInterface::class), (array) PluginHelper::getPlugin('webservices', 'mokosuitelogistics'));
});
}
};
@@ -0,0 +1,36 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Authored-by: Moko Consulting
*/
namespace Moko\Plugin\WebServices\MokoSuiteLogistics\Extension;
defined('_JEXEC') or die;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\Event\SubscriberInterface;
class MokoSuiteLogistics extends CMSPlugin implements SubscriberInterface
{
public static function getSubscribedEvents(): array
{
return ['onBeforeApiRoute' => 'onBeforeApiRoute'];
}
public function onBeforeApiRoute(&$event): void
{
$router = $event->getArgument('router');
$opts = ['component' => 'com_mokosuitelogistics'];
$router->createCRUDRoutes('v1/mokosuitelogistics/orders', 'orders', $opts);
$router->createCRUDRoutes('v1/mokosuitelogistics/deliveries', 'deliveries', $opts);
$router->createCRUDRoutes('v1/mokosuitelogistics/routes', 'routes', $opts);
$router->createCRUDRoutes('v1/mokosuitelogistics/warehouses', 'warehouses', $opts);
$router->createCRUDRoutes('v1/mokosuitelogistics/pricingrules', 'pricingrules', $opts);
$router->createCRUDRoutes('v1/mokosuitelogistics/drivers', 'drivers', $opts);
$router->createCRUDRoutes('v1/mokosuitelogistics/proofs', 'proofs', $opts);
}
}
+25
View File
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<extension type="package" method="upgrade">
<name>Package - MokoSuite Logistics</name>
<packagename>mokosuitelogistics</packagename>
<version>06.00.00</version>
<creationDate>2026-06-27</creationDate>
<author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
<license>GNU General Public License version 3 or later; see LICENSE</license>
<description>Delivery management, route optimization, courier dispatch, proof of delivery</description>
<scriptfile>script.php</scriptfile>
<php_minimum>8.3</php_minimum>
<dlid prefix="dlid=" suffix=""/>
<blockChildUninstall>true</blockChildUninstall>
<files folder="packages">
<file type="plugin" id="plg_system_mokosuitelogistics" group="system">plg_system_mokosuitelogistics.zip</file>
<file type="component" id="com_mokosuitelogistics">com_mokosuitelogistics.zip</file>
<file type="plugin" id="plg_webservices_mokosuitelogistics" group="webservices">plg_webservices_mokosuitelogistics.zip</file>
</files>
<updateservers>
<server type="extension" priority="1" name="Package - MokoSuite Logistics">https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteLogistics/updates.xml</server>
</updateservers>
</extension>
+74
View File
@@ -0,0 +1,74 @@
<?php
/**
* @package MokoSuiteLogistics
* @subpackage pkg_mokosuitelogistics
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
*/
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Log\Log;
/**
* Package installation script for MokoSuiteLogistics.
*/
class Pkg_MokoSuiteLogisticsInstallerScript
{
public function postflight(string $type, InstallerAdapter $adapter): void
{
$this->warnMissingLicenseKey();
}
private function warnMissingLicenseKey(): void
{
try
{
$db = Factory::getDbo();
$app = Factory::getApplication();
$query = $db->getQuery(true)
->select([$db->quoteName('update_site_id'), $db->quoteName('extra_query')])
->from($db->quoteName('#__update_sites'))
->where('(' . $db->quoteName('name') . ' LIKE ' . $db->quote('%MokoSuiteLogistics%')
. ' OR ' . $db->quoteName('location') . ' LIKE ' . $db->quote('%MokoSuiteLogistics%') . ')')
->setLimit(1);
$db->setQuery($query);
$site = $db->loadObject();
if ($site)
{
$extraQuery = (string) ($site->extra_query ?? '');
if (!empty($extraQuery) && strpos($extraQuery, 'dlid=') !== false)
{
parse_str($extraQuery, $parsed);
if (!empty($parsed['dlid']))
{
return;
}
}
$editUrl = 'index.php?option=com_installer&task=updatesite.edit&update_site_id=' . (int) $site->update_site_id;
}
else
{
$editUrl = 'index.php?option=com_installer&view=updatesites';
}
$app->enqueueMessage(
'<strong>Moko Consulting License Key Required</strong> — '
. 'No download key is configured. Updates will not be available until a valid license key is entered. '
. '<a href="' . $editUrl . '" class="btn btn-sm btn-warning ms-2">Enter License Key</a>',
'warning'
);
}
catch (\Throwable $e)
{
// Silent — avoid breaking install if update_sites query fails
}
}
}