feat: add ERP REST API controllers and route registration (#192)
Add CRUD API endpoints for the ERP system: - ErpRolesController: list/create/update/delete contact roles - ErpDealsController: full deal management with stage/contact joins - ErpActivitiesController: activity log CRUD per contact/deal - ErpPipelineController: pipeline summary with stage counts and weighted values Routes registered in plg_webservices_mokowaas: - /v1/mokowaas/erp/roles - /v1/mokowaas/erp/deals - /v1/mokowaas/erp/activities - /v1/mokowaas/erp/pipeline
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoWaaS
|
||||
* @subpackage com_mokowaas
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
namespace Moko\Component\MokoWaaS\Api\Controller;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\MVC\Controller\BaseController;
|
||||
|
||||
/**
|
||||
* ERP Activities API controller.
|
||||
*
|
||||
* GET /api/index.php/v1/mokowaas/erp/activities — List activities
|
||||
* POST /api/index.php/v1/mokowaas/erp/activities — Create activity
|
||||
* PATCH /api/index.php/v1/mokowaas/erp/activities/{id} — Update activity
|
||||
* DELETE /api/index.php/v1/mokowaas/erp/activities/{id} — Delete activity
|
||||
*
|
||||
* @since 02.34.16
|
||||
*/
|
||||
class ErpActivitiesController extends BaseController
|
||||
{
|
||||
/**
|
||||
* List activities with optional filters.
|
||||
*/
|
||||
public function displayList(): void
|
||||
{
|
||||
$this->requireAuth();
|
||||
|
||||
$app = Factory::getApplication();
|
||||
$db = Factory::getDbo();
|
||||
$input = $app->getInput();
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select('a.*, cd.name AS contact_name, u.name AS user_name')
|
||||
->from($db->quoteName('#__mokowaas_erp_activities', 'a'))
|
||||
->join('LEFT', $db->quoteName('#__contact_details', 'cd') . ' ON cd.id = a.contact_id')
|
||||
->join('LEFT', $db->quoteName('#__users', 'u') . ' ON u.id = a.user_id')
|
||||
->order('a.created DESC');
|
||||
|
||||
$contactId = $input->getInt('contact_id', 0);
|
||||
|
||||
if ($contactId)
|
||||
{
|
||||
$query->where($db->quoteName('a.contact_id') . ' = ' . $contactId);
|
||||
}
|
||||
|
||||
$dealId = $input->getInt('deal_id', 0);
|
||||
|
||||
if ($dealId)
|
||||
{
|
||||
$query->where($db->quoteName('a.deal_id') . ' = ' . $dealId);
|
||||
}
|
||||
|
||||
$type = $input->get('type', '', 'CMD');
|
||||
|
||||
if ($type)
|
||||
{
|
||||
$query->where($db->quoteName('a.type') . ' = ' . $db->quote($type));
|
||||
}
|
||||
|
||||
$limit = $input->getInt('limit', 50);
|
||||
$offset = $input->getInt('offset', 0);
|
||||
|
||||
$db->setQuery($query, $offset, $limit);
|
||||
$items = $db->loadObjectList();
|
||||
|
||||
$this->sendJson(200, ['data' => $items, 'total' => \count($items)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new activity.
|
||||
*/
|
||||
public function add(): void
|
||||
{
|
||||
$this->requireAuth();
|
||||
|
||||
$input = json_decode(Factory::getApplication()->getInput()->json->getRaw(), true);
|
||||
$db = Factory::getDbo();
|
||||
|
||||
$contactId = (int) ($input['contact_id'] ?? 0);
|
||||
$type = $input['type'] ?? 'note';
|
||||
|
||||
if (!$contactId)
|
||||
{
|
||||
$this->sendJson(400, ['error' => 'contact_id is required']);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!\in_array($type, ['call', 'email', 'meeting', 'note', 'task'], true))
|
||||
{
|
||||
$this->sendJson(400, ['error' => 'type must be one of: call, email, meeting, note, task']);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$obj = (object) [
|
||||
'contact_id' => $contactId,
|
||||
'deal_id' => ($input['deal_id'] ?? null) ? (int) $input['deal_id'] : null,
|
||||
'type' => $type,
|
||||
'subject' => $input['subject'] ?? '',
|
||||
'body' => $input['body'] ?? null,
|
||||
'due_date' => $input['due_date'] ?? null,
|
||||
'completed' => (int) ($input['completed'] ?? 0),
|
||||
'user_id' => (int) Factory::getUser()->id,
|
||||
'created' => Factory::getDate()->toSql(),
|
||||
];
|
||||
|
||||
$db->insertObject('#__mokowaas_erp_activities', $obj, 'id');
|
||||
|
||||
$this->sendJson(201, ['data' => $obj, 'id' => $db->insertid()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an activity.
|
||||
*/
|
||||
public function edit(): void
|
||||
{
|
||||
$this->requireAuth();
|
||||
|
||||
$id = Factory::getApplication()->getInput()->getInt('id', 0);
|
||||
$input = json_decode(Factory::getApplication()->getInput()->json->getRaw(), true);
|
||||
$db = Factory::getDbo();
|
||||
|
||||
$allowed = ['subject', 'body', 'due_date', 'completed', 'type'];
|
||||
$updates = [];
|
||||
|
||||
foreach ($allowed as $field)
|
||||
{
|
||||
if (isset($input[$field]))
|
||||
{
|
||||
$updates[$field] = $input[$field];
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($updates))
|
||||
{
|
||||
$this->sendJson(400, ['error' => 'No valid fields to update']);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$obj = (object) array_merge(['id' => $id], $updates);
|
||||
$db->updateObject('#__mokowaas_erp_activities', $obj, 'id');
|
||||
|
||||
$this->sendJson(200, ['data' => $obj]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an activity.
|
||||
*/
|
||||
public function delete(): void
|
||||
{
|
||||
$this->requireAuth();
|
||||
|
||||
$id = Factory::getApplication()->getInput()->getInt('id', 0);
|
||||
$db = Factory::getDbo();
|
||||
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->delete($db->quoteName('#__mokowaas_erp_activities'))
|
||||
->where($db->quoteName('id') . ' = ' . $id)
|
||||
);
|
||||
$db->execute();
|
||||
|
||||
$this->sendJson(200, ['message' => 'Activity deleted', 'id' => $id]);
|
||||
}
|
||||
|
||||
private function requireAuth(): void
|
||||
{
|
||||
$user = Factory::getApplication()->getIdentity();
|
||||
|
||||
if (!$user->authorise('core.manage', 'com_mokowaas'))
|
||||
{
|
||||
$this->sendJson(403, ['error' => 'Not authorized']);
|
||||
$this->app->close();
|
||||
}
|
||||
}
|
||||
|
||||
private function sendJson(int $code, array $data): void
|
||||
{
|
||||
$app = Factory::getApplication();
|
||||
$app->setHeader('Content-Type', 'application/json', true);
|
||||
$app->setHeader('Status', (string) $code, true);
|
||||
echo json_encode($data, JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoWaaS
|
||||
* @subpackage com_mokowaas
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
namespace Moko\Component\MokoWaaS\Api\Controller;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\MVC\Controller\BaseController;
|
||||
|
||||
/**
|
||||
* ERP Deals API controller.
|
||||
*
|
||||
* GET /api/index.php/v1/mokowaas/erp/deals — List deals
|
||||
* GET /api/index.php/v1/mokowaas/erp/deals/{id} — Get single deal
|
||||
* POST /api/index.php/v1/mokowaas/erp/deals — Create deal
|
||||
* PATCH /api/index.php/v1/mokowaas/erp/deals/{id} — Update deal
|
||||
* DELETE /api/index.php/v1/mokowaas/erp/deals/{id} — Delete deal
|
||||
*
|
||||
* @since 02.34.16
|
||||
*/
|
||||
class ErpDealsController extends BaseController
|
||||
{
|
||||
/**
|
||||
* List deals with optional filters.
|
||||
*/
|
||||
public function displayList(): void
|
||||
{
|
||||
$this->requireAuth();
|
||||
|
||||
$app = Factory::getApplication();
|
||||
$db = Factory::getDbo();
|
||||
$input = $app->getInput();
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select('d.*, ps.title AS stage_title, ps.color AS stage_color, ps.probability AS stage_probability')
|
||||
->select('cd.name AS contact_name')
|
||||
->from($db->quoteName('#__mokowaas_erp_deals', 'd'))
|
||||
->join('LEFT', $db->quoteName('#__mokowaas_erp_pipeline_stages', 'ps') . ' ON ps.id = d.stage_id')
|
||||
->join('LEFT', $db->quoteName('#__contact_details', 'cd') . ' ON cd.id = d.contact_id')
|
||||
->order('d.created DESC');
|
||||
|
||||
$status = $input->get('status', '', 'CMD');
|
||||
|
||||
if ($status)
|
||||
{
|
||||
$query->where($db->quoteName('d.status') . ' = ' . $db->quote($status));
|
||||
}
|
||||
|
||||
$stageId = $input->getInt('stage_id', 0);
|
||||
|
||||
if ($stageId)
|
||||
{
|
||||
$query->where($db->quoteName('d.stage_id') . ' = ' . $stageId);
|
||||
}
|
||||
|
||||
$contactId = $input->getInt('contact_id', 0);
|
||||
|
||||
if ($contactId)
|
||||
{
|
||||
$query->where($db->quoteName('d.contact_id') . ' = ' . $contactId);
|
||||
}
|
||||
|
||||
$assignedTo = $input->getInt('assigned_to', 0);
|
||||
|
||||
if ($assignedTo)
|
||||
{
|
||||
$query->where($db->quoteName('d.assigned_to') . ' = ' . $assignedTo);
|
||||
}
|
||||
|
||||
$limit = $input->getInt('limit', 50);
|
||||
$offset = $input->getInt('offset', 0);
|
||||
|
||||
$db->setQuery($query, $offset, $limit);
|
||||
$items = $db->loadObjectList();
|
||||
|
||||
$this->sendJson(200, ['data' => $items, 'total' => \count($items)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single deal by ID.
|
||||
*/
|
||||
public function displayItem(): void
|
||||
{
|
||||
$this->requireAuth();
|
||||
|
||||
$id = Factory::getApplication()->getInput()->getInt('id', 0);
|
||||
$db = Factory::getDbo();
|
||||
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select('d.*, ps.title AS stage_title, ps.color AS stage_color')
|
||||
->select('cd.name AS contact_name')
|
||||
->from($db->quoteName('#__mokowaas_erp_deals', 'd'))
|
||||
->join('LEFT', $db->quoteName('#__mokowaas_erp_pipeline_stages', 'ps') . ' ON ps.id = d.stage_id')
|
||||
->join('LEFT', $db->quoteName('#__contact_details', 'cd') . ' ON cd.id = d.contact_id')
|
||||
->where($db->quoteName('d.id') . ' = ' . $id)
|
||||
);
|
||||
|
||||
$item = $db->loadObject();
|
||||
|
||||
if (!$item)
|
||||
{
|
||||
$this->sendJson(404, ['error' => 'Deal not found']);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Include activities for this deal
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select('*')
|
||||
->from($db->quoteName('#__mokowaas_erp_activities'))
|
||||
->where($db->quoteName('deal_id') . ' = ' . $id)
|
||||
->order('created DESC'),
|
||||
0, 20
|
||||
);
|
||||
|
||||
$item->activities = $db->loadObjectList();
|
||||
|
||||
$this->sendJson(200, ['data' => $item]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new deal.
|
||||
*/
|
||||
public function add(): void
|
||||
{
|
||||
$this->requireAuth();
|
||||
|
||||
$input = json_decode(Factory::getApplication()->getInput()->json->getRaw(), true);
|
||||
$db = Factory::getDbo();
|
||||
$now = Factory::getDate()->toSql();
|
||||
|
||||
$title = trim($input['title'] ?? '');
|
||||
$contactId = (int) ($input['contact_id'] ?? 0);
|
||||
|
||||
if (!$title || !$contactId)
|
||||
{
|
||||
$this->sendJson(400, ['error' => 'title and contact_id are required']);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$obj = (object) [
|
||||
'title' => $title,
|
||||
'contact_id' => $contactId,
|
||||
'stage_id' => (int) ($input['stage_id'] ?? 1),
|
||||
'value' => (float) ($input['value'] ?? 0),
|
||||
'currency' => $input['currency'] ?? 'USD',
|
||||
'probability' => isset($input['probability']) ? (int) $input['probability'] : null,
|
||||
'expected_close' => $input['expected_close'] ?? null,
|
||||
'assigned_to' => (int) ($input['assigned_to'] ?? Factory::getUser()->id),
|
||||
'status' => 'open',
|
||||
'notes' => $input['notes'] ?? null,
|
||||
'created' => $now,
|
||||
'created_by' => (int) Factory::getUser()->id,
|
||||
];
|
||||
|
||||
$db->insertObject('#__mokowaas_erp_deals', $obj, 'id');
|
||||
|
||||
$this->sendJson(201, ['data' => $obj, 'id' => $db->insertid()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a deal (including stage moves for pipeline drag-and-drop).
|
||||
*/
|
||||
public function edit(): void
|
||||
{
|
||||
$this->requireAuth();
|
||||
|
||||
$id = Factory::getApplication()->getInput()->getInt('id', 0);
|
||||
$input = json_decode(Factory::getApplication()->getInput()->json->getRaw(), true);
|
||||
$db = Factory::getDbo();
|
||||
|
||||
$allowed = [
|
||||
'title', 'stage_id', 'value', 'currency', 'probability',
|
||||
'expected_close', 'assigned_to', 'status', 'lost_reason', 'notes',
|
||||
];
|
||||
|
||||
$updates = [];
|
||||
|
||||
foreach ($allowed as $field)
|
||||
{
|
||||
if (isset($input[$field]))
|
||||
{
|
||||
$updates[$field] = $input[$field];
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($updates))
|
||||
{
|
||||
$this->sendJson(400, ['error' => 'No valid fields to update']);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$updates['modified'] = Factory::getDate()->toSql();
|
||||
|
||||
// Auto-set closed date on won/lost
|
||||
if (isset($updates['status']) && \in_array($updates['status'], ['won', 'lost'], true))
|
||||
{
|
||||
$updates['closed'] = Factory::getDate()->toSql();
|
||||
}
|
||||
|
||||
$obj = (object) array_merge(['id' => $id], $updates);
|
||||
$db->updateObject('#__mokowaas_erp_deals', $obj, 'id');
|
||||
|
||||
$this->sendJson(200, ['data' => $obj]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a deal and its activities.
|
||||
*/
|
||||
public function delete(): void
|
||||
{
|
||||
$this->requireAuth();
|
||||
|
||||
$id = Factory::getApplication()->getInput()->getInt('id', 0);
|
||||
$db = Factory::getDbo();
|
||||
|
||||
// Delete linked activities first
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->delete($db->quoteName('#__mokowaas_erp_activities'))
|
||||
->where($db->quoteName('deal_id') . ' = ' . $id)
|
||||
);
|
||||
$db->execute();
|
||||
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->delete($db->quoteName('#__mokowaas_erp_deals'))
|
||||
->where($db->quoteName('id') . ' = ' . $id)
|
||||
);
|
||||
$db->execute();
|
||||
|
||||
$this->sendJson(200, ['message' => 'Deal deleted', 'id' => $id]);
|
||||
}
|
||||
|
||||
private function requireAuth(): void
|
||||
{
|
||||
$user = Factory::getApplication()->getIdentity();
|
||||
|
||||
if (!$user->authorise('core.manage', 'com_mokowaas'))
|
||||
{
|
||||
$this->sendJson(403, ['error' => 'Not authorized']);
|
||||
$this->app->close();
|
||||
}
|
||||
}
|
||||
|
||||
private function sendJson(int $code, array $data): void
|
||||
{
|
||||
$app = Factory::getApplication();
|
||||
$app->setHeader('Content-Type', 'application/json', true);
|
||||
$app->setHeader('Status', (string) $code, true);
|
||||
echo json_encode($data, JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoWaaS
|
||||
* @subpackage com_mokowaas
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
namespace Moko\Component\MokoWaaS\Api\Controller;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\MVC\Controller\BaseController;
|
||||
|
||||
/**
|
||||
* ERP Pipeline API controller.
|
||||
*
|
||||
* GET /api/index.php/v1/mokowaas/erp/pipeline — Pipeline summary with stages, deal counts, values
|
||||
*
|
||||
* @since 02.34.16
|
||||
*/
|
||||
class ErpPipelineController extends BaseController
|
||||
{
|
||||
/**
|
||||
* Return pipeline summary: stages with deal counts and total values.
|
||||
*/
|
||||
public function displayList(): void
|
||||
{
|
||||
$user = Factory::getApplication()->getIdentity();
|
||||
|
||||
if (!$user->authorise('core.manage', 'com_mokowaas'))
|
||||
{
|
||||
$this->sendJson(403, ['error' => 'Not authorized']);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$db = Factory::getDbo();
|
||||
|
||||
// Get all stages
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select('*')
|
||||
->from($db->quoteName('#__mokowaas_erp_pipeline_stages'))
|
||||
->where($db->quoteName('published') . ' = 1')
|
||||
->order($db->quoteName('ordering') . ' ASC')
|
||||
);
|
||||
|
||||
$stages = $db->loadObjectList();
|
||||
|
||||
// Get deal counts and values per stage (open deals only)
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select($db->quoteName('stage_id'))
|
||||
->select('COUNT(*) AS ' . $db->quoteName('deal_count'))
|
||||
->select('COALESCE(SUM(' . $db->quoteName('value') . '), 0) AS ' . $db->quoteName('total_value'))
|
||||
->from($db->quoteName('#__mokowaas_erp_deals'))
|
||||
->where($db->quoteName('status') . ' = ' . $db->quote('open'))
|
||||
->group($db->quoteName('stage_id'))
|
||||
);
|
||||
|
||||
$dealStats = [];
|
||||
|
||||
foreach ($db->loadObjectList() as $row)
|
||||
{
|
||||
$dealStats[(int) $row->stage_id] = $row;
|
||||
}
|
||||
|
||||
// Merge stats into stages
|
||||
foreach ($stages as &$stage)
|
||||
{
|
||||
$stats = $dealStats[(int) $stage->id] ?? null;
|
||||
$stage->deal_count = $stats ? (int) $stats->deal_count : 0;
|
||||
$stage->total_value = $stats ? (float) $stats->total_value : 0.00;
|
||||
$stage->weighted_value = $stage->total_value * ($stage->probability / 100);
|
||||
}
|
||||
|
||||
// Summary totals
|
||||
$totalDeals = array_sum(array_column($stages, 'deal_count'));
|
||||
$totalValue = array_sum(array_column($stages, 'total_value'));
|
||||
$weightedValue = array_sum(array_column($stages, 'weighted_value'));
|
||||
|
||||
$this->sendJson(200, [
|
||||
'stages' => $stages,
|
||||
'summary' => [
|
||||
'total_deals' => $totalDeals,
|
||||
'total_value' => $totalValue,
|
||||
'weighted_value' => round($weightedValue, 2),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
private function sendJson(int $code, array $data): void
|
||||
{
|
||||
$app = Factory::getApplication();
|
||||
$app->setHeader('Content-Type', 'application/json', true);
|
||||
$app->setHeader('Status', (string) $code, true);
|
||||
echo json_encode($data, JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoWaaS
|
||||
* @subpackage com_mokowaas
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
namespace Moko\Component\MokoWaaS\Api\Controller;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\MVC\Controller\BaseController;
|
||||
|
||||
/**
|
||||
* ERP Roles API controller.
|
||||
*
|
||||
* GET /api/index.php/v1/mokowaas/erp/roles — List all roles
|
||||
* GET /api/index.php/v1/mokowaas/erp/roles/{id} — Get single role
|
||||
* POST /api/index.php/v1/mokowaas/erp/roles — Create role
|
||||
* PATCH /api/index.php/v1/mokowaas/erp/roles/{id} — Update role
|
||||
* DELETE /api/index.php/v1/mokowaas/erp/roles/{id} — Delete role
|
||||
*
|
||||
* @since 02.34.16
|
||||
*/
|
||||
class ErpRolesController extends BaseController
|
||||
{
|
||||
/**
|
||||
* List all ERP roles, optionally filtered by role type or contact.
|
||||
*/
|
||||
public function displayList(): void
|
||||
{
|
||||
$this->requireAuth();
|
||||
|
||||
$app = Factory::getApplication();
|
||||
$db = Factory::getDbo();
|
||||
$input = $app->getInput();
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select('r.*, cd.name AS contact_name, cd.email_to AS contact_email')
|
||||
->from($db->quoteName('#__mokowaas_erp_roles', 'r'))
|
||||
->join('LEFT', $db->quoteName('#__contact_details', 'cd') . ' ON cd.id = r.contact_id')
|
||||
->order('r.created DESC');
|
||||
|
||||
// Filters
|
||||
$role = $input->get('role', '', 'CMD');
|
||||
|
||||
if ($role)
|
||||
{
|
||||
$query->where($db->quoteName('r.role') . ' = ' . $db->quote($role));
|
||||
}
|
||||
|
||||
$status = $input->get('status', '', 'CMD');
|
||||
|
||||
if ($status)
|
||||
{
|
||||
$query->where($db->quoteName('r.status') . ' = ' . $db->quote($status));
|
||||
}
|
||||
|
||||
$contactId = $input->getInt('contact_id', 0);
|
||||
|
||||
if ($contactId)
|
||||
{
|
||||
$query->where($db->quoteName('r.contact_id') . ' = ' . $contactId);
|
||||
}
|
||||
|
||||
$limit = $input->getInt('limit', 50);
|
||||
$offset = $input->getInt('offset', 0);
|
||||
|
||||
$db->setQuery($query, $offset, $limit);
|
||||
$items = $db->loadObjectList();
|
||||
|
||||
$this->sendJson(200, ['data' => $items, 'total' => \count($items)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single role by ID.
|
||||
*/
|
||||
public function displayItem(): void
|
||||
{
|
||||
$this->requireAuth();
|
||||
|
||||
$id = Factory::getApplication()->getInput()->getInt('id', 0);
|
||||
$db = Factory::getDbo();
|
||||
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select('r.*, cd.name AS contact_name, cd.email_to AS contact_email')
|
||||
->from($db->quoteName('#__mokowaas_erp_roles', 'r'))
|
||||
->join('LEFT', $db->quoteName('#__contact_details', 'cd') . ' ON cd.id = r.contact_id')
|
||||
->where($db->quoteName('r.id') . ' = ' . $id)
|
||||
);
|
||||
|
||||
$item = $db->loadObject();
|
||||
|
||||
if (!$item)
|
||||
{
|
||||
$this->sendJson(404, ['error' => 'Role not found']);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->sendJson(200, ['data' => $item]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new ERP role.
|
||||
*/
|
||||
public function add(): void
|
||||
{
|
||||
$this->requireAuth();
|
||||
|
||||
$app = Factory::getApplication();
|
||||
$input = json_decode($app->getInput()->json->getRaw(), true);
|
||||
|
||||
$contactId = (int) ($input['contact_id'] ?? 0);
|
||||
$role = $input['role'] ?? '';
|
||||
|
||||
if (!$contactId || !\in_array($role, ['customer', 'vendor', 'prospect'], true))
|
||||
{
|
||||
$this->sendJson(400, ['error' => 'contact_id and valid role (customer, vendor, prospect) are required']);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$db = Factory::getDbo();
|
||||
|
||||
// Check duplicate
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select('COUNT(*)')
|
||||
->from($db->quoteName('#__mokowaas_erp_roles'))
|
||||
->where($db->quoteName('contact_id') . ' = ' . $contactId)
|
||||
->where($db->quoteName('role') . ' = ' . $db->quote($role))
|
||||
);
|
||||
|
||||
if ((int) $db->loadResult() > 0)
|
||||
{
|
||||
$this->sendJson(409, ['error' => 'Role already exists for this contact']);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$prefixes = ['customer' => 'CU', 'vendor' => 'VN', 'prospect' => 'PR'];
|
||||
$prefix = $prefixes[$role];
|
||||
|
||||
// Use ErpHelper if available, otherwise inline
|
||||
$year = date('Y');
|
||||
$like = $db->quote($prefix . $year . '-%');
|
||||
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select('MAX(' . $db->quoteName('code') . ')')
|
||||
->from($db->quoteName('#__mokowaas_erp_roles'))
|
||||
->where($db->quoteName('role') . ' = ' . $db->quote($role))
|
||||
->where($db->quoteName('code') . ' LIKE ' . $like)
|
||||
);
|
||||
|
||||
$maxCode = $db->loadResult();
|
||||
$seq = $maxCode ? ((int) end(explode('-', $maxCode)) + 1) : 1;
|
||||
$code = sprintf('%s%s-%03d', $prefix, $year, $seq);
|
||||
|
||||
$obj = (object) [
|
||||
'contact_id' => $contactId,
|
||||
'role' => $role,
|
||||
'status' => $input['status'] ?? ($role === 'prospect' ? 'lead' : 'active'),
|
||||
'code' => $code,
|
||||
'credit_limit' => $input['credit_limit'] ?? null,
|
||||
'payment_terms_days' => (int) ($input['payment_terms_days'] ?? 30),
|
||||
'notes' => $input['notes'] ?? null,
|
||||
'created' => Factory::getDate()->toSql(),
|
||||
'created_by' => (int) Factory::getUser()->id,
|
||||
];
|
||||
|
||||
$db->insertObject('#__mokowaas_erp_roles', $obj, 'id');
|
||||
|
||||
$this->sendJson(201, ['data' => $obj, 'id' => $db->insertid()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing role.
|
||||
*/
|
||||
public function edit(): void
|
||||
{
|
||||
$this->requireAuth();
|
||||
|
||||
$id = Factory::getApplication()->getInput()->getInt('id', 0);
|
||||
$input = json_decode(Factory::getApplication()->getInput()->json->getRaw(), true);
|
||||
$db = Factory::getDbo();
|
||||
|
||||
$allowed = ['status', 'credit_limit', 'payment_terms_days', 'notes'];
|
||||
$updates = [];
|
||||
|
||||
foreach ($allowed as $field)
|
||||
{
|
||||
if (isset($input[$field]))
|
||||
{
|
||||
$updates[$field] = $input[$field];
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($updates))
|
||||
{
|
||||
$this->sendJson(400, ['error' => 'No valid fields to update']);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$updates['modified'] = Factory::getDate()->toSql();
|
||||
|
||||
$obj = (object) array_merge(['id' => $id], $updates);
|
||||
$db->updateObject('#__mokowaas_erp_roles', $obj, 'id');
|
||||
|
||||
$this->sendJson(200, ['data' => $obj]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a role.
|
||||
*/
|
||||
public function delete(): void
|
||||
{
|
||||
$this->requireAuth();
|
||||
|
||||
$id = Factory::getApplication()->getInput()->getInt('id', 0);
|
||||
$db = Factory::getDbo();
|
||||
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->delete($db->quoteName('#__mokowaas_erp_roles'))
|
||||
->where($db->quoteName('id') . ' = ' . $id)
|
||||
);
|
||||
$db->execute();
|
||||
|
||||
$this->sendJson(200, ['message' => 'Role deleted', 'id' => $id]);
|
||||
}
|
||||
|
||||
private function requireAuth(): void
|
||||
{
|
||||
$user = Factory::getApplication()->getIdentity();
|
||||
|
||||
if (!$user->authorise('core.manage', 'com_mokowaas'))
|
||||
{
|
||||
$this->sendJson(403, ['error' => 'Not authorized']);
|
||||
$this->app->close();
|
||||
}
|
||||
}
|
||||
|
||||
private function sendJson(int $code, array $data): void
|
||||
{
|
||||
$app = Factory::getApplication();
|
||||
$app->setHeader('Content-Type', 'application/json', true);
|
||||
$app->setHeader('Status', (string) $code, true);
|
||||
echo json_encode($data, JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
}
|
||||
@@ -124,5 +124,30 @@ final class MokoWaaSApi extends CMSPlugin implements SubscriberInterface
|
||||
'provision',
|
||||
['component' => 'com_mokowaas']
|
||||
);
|
||||
|
||||
// ERP routes
|
||||
$router->createCRUDRoutes(
|
||||
'v1/mokowaas/erp/roles',
|
||||
'erproles',
|
||||
['component' => 'com_mokowaas']
|
||||
);
|
||||
|
||||
$router->createCRUDRoutes(
|
||||
'v1/mokowaas/erp/deals',
|
||||
'erpdeals',
|
||||
['component' => 'com_mokowaas']
|
||||
);
|
||||
|
||||
$router->createCRUDRoutes(
|
||||
'v1/mokowaas/erp/activities',
|
||||
'erpactivities',
|
||||
['component' => 'com_mokowaas']
|
||||
);
|
||||
|
||||
$router->createCRUDRoutes(
|
||||
'v1/mokowaas/erp/pipeline',
|
||||
'erppipeline',
|
||||
['component' => 'com_mokowaas']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user