Merge pull request #56 from copenhagentruckwash/limble-module

limble-module
This commit is contained in:
Jeppe B
2025-06-16 15:24:01 +02:00
committed by GitHub
29 changed files with 1312 additions and 12 deletions
+72
View File
@@ -0,0 +1,72 @@
<?php
namespace classes;
require_once WD . '/modules/limble/limble_c.php';
require_once WD . '/modules/limble/limble_helpers.php';
require_once WD . '/modules/limble/classes/limble_endpoints.php';
/**
* Actions
*/
use Exception;
use interfaces\limble_i;
use limble\classes\limble_endpoints;
use limble\helpers\limble_tasks;
use limble\limble_c;
use limble\limble_helpers;
class limble extends limble_endpoints implements limble_i
{
/**
* The configuration of the module
* @var limble_c
*/
public limble_c $config;
/**
* The task helper for the module
* @var limble_helpers
*/
public limble_helpers $helpers;
public function __construct()
{
$this->config = new limble_c();
$this->helpers = new limble_helpers();
}
/**
* @inheritDoc
* @throws Exception If the module is not enabled
*/
public function requireModuleEnabled(): void
{
if (!(bool)$this->config->enabled->getVariableValue()) {
throw new Exception('Limble module is not enabled');
}
}
/**
* Get the task helper for the module
* @return limble_tasks
*/
public function getTasks(): limble_tasks
{
return new $this->helpers->tasks();
}
/**
* Require the webhooks to be enabled
* @throws Exception
*/
public function requireWebhooksEnabled(): void
{
if (!(bool)$this->config->webhooks_enabled->getVariableValue()) {
throw new Exception('Limble webhooks are not enabled');
}
}
}
+1
View File
@@ -67,6 +67,7 @@ require_once 'classes/fxratesapi.php';
require_once 'classes/gatewayapi.php';
require_once 'classes/xlvask.php';
require_once 'classes/entra.php';
require_once 'classes/limble.php';
/**
* Modules
@@ -0,0 +1,15 @@
<?php
namespace interfaces;
require_once WD . '/interfaces/universal_module_i.php';
use limble\helpers\limble_tasks;
interface limble_i extends universal_module_i
{
/**
* Get the tasks helper for the limble module
* @return limble_tasks
*/
public function getTasks(): limble_tasks;
}
@@ -0,0 +1,15 @@
<?php
namespace interfaces;
use Exception;
interface universal_module_i
{
/**
* Require the module to be enabled
* @return void
* @throws Exception If the module is not enabled
*/
public function requireModuleEnabled(): void;
}
+1 -9
View File
@@ -2,14 +2,6 @@
namespace interfaces;
use Exception;
interface xlvask_i
interface xlvask_i extends universal_module_i
{
/**
* Require the xlvask module to be enabled
* @return void
* @throws Exception If the module is not enabled
*/
public function requireModuleEnabled(): void;
}
@@ -0,0 +1,71 @@
<?php
namespace limble\classes;
require_once WD . '/modules/limble/interfaces/limble_endpoints_i.php';
require_once WD . '/modules/limble/classes/limble_request.php';
use Exception;
use limble_endpoints_i;
abstract class limble_endpoints extends limble_request implements limble_endpoints_i
{
/**
* @inheritDoc
* @throws Exception If the module is not enabled
*/
public function listTasks(): array
{
$this->requireModuleEnabled();
$url = $this->config->api_url . '/tasks';
$params = [
// 'status' => 'open',
// 'limit' => 100,
];
return $this->sendRequest(
$url,
'GET',
$params,
[self::getAuthHeader(), 'Content-Type: application/json']
);
}
/**
* @inheritDoc
*/
public function getAuthHeader(): string
{
$this->requireModuleEnabled();
$client_id = $this->config->client_id->getVariableValue();
$client_secret = $this->config->client_secret->getVariableValue();
if (empty($client_id)) {
throw new Exception('Client ID is not set');
}
if (empty($client_secret)) {
throw new Exception('Client Secret is not set');
}
// Generate the Basic Auth header
return $this->generateBasicAuthHeader(
(string)$client_id,
(string)$client_secret
);
}
/**
* @inheritDoc
* @throws Exception If the module is not enabled
*/
public function getTask(int $taskId): array
{
$this->requireModuleEnabled();
$url = $this->config->api_url . '/tasks';
return $this->sendRequest(
$url,
'GET',
[
'cursor' => $taskId,
],
[self::getAuthHeader(), 'Content-Type: application/json']
);
}
}
@@ -0,0 +1,71 @@
<?php
namespace limble\classes;
require_once WD . '/modules/limble/interfaces/limble_request_i.php';
use limble_request_i;
class limble_request implements limble_request_i
{
/**
* @inheritDoc
*/
public function sendRequest(string $url, string $method = 'GET', array $data = [], array $headers = []): array
{
// Initialize cURL
$ch = curl_init();
$method = strtoupper($method);
// Set the URL
curl_setopt($ch, CURLOPT_URL, $url . ($method === 'GET' && !empty($data) ? '?' . http_build_query($data) : ''));
// Set the HTTP method
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
// Set the data to send with the request
if ($method !== 'GET') {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
$headers[] = 'Content-Type: application/json';
};
// Set the headers
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
// Set options to return the response and handle SSL
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
// Execute the request
$response = curl_exec($ch);
// Check for errors
if (curl_errno($ch)) {
throw new \Exception('cURL error: ' . curl_error($ch));
}
// Get the HTTP status code
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
// Close the cURL session
curl_close($ch);
// Check if the response is successful
if ($httpCode < 200 || $httpCode >= 300) {
$slack = new \classes\slack();
echo 'Attempting credentials: ' . $url . ' with method: ' . $method . ' and data: ' . json_encode($data) . "\n";
echo 'Response: ' . $response . "\n";
echo 'HTTP Code: ' . $httpCode . "\n";
echo 'Headers: ' . json_encode($headers) . "\n";
$slack->send_message('Limble Request Failed: ' . $response, 'Limble Request Error');
throw new \Exception('Request failed with status code ' . $httpCode);
}
// Check if the response is valid JSON
$responseData = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new \Exception('Invalid JSON response: ' . json_last_error_msg());
}
// Return the response data
return $responseData;
}
/**
* @inheritDoc
*/
public function generateBasicAuthHeader(string $client_id, string $client_secret): string
{
// Generate the Basic Auth header using the client ID and secret
return 'Authorization: Basic ' . base64_encode($client_id . ':' . $client_secret);
}
}
@@ -0,0 +1,29 @@
<?php
namespace limble\config;
use Exception;
use traits\module_config_variable;
class limble_client_id_c
{
use module_config_variable;
/**
* @throws Exception
*/
public function __construct()
{
self::setupConfigVariable(
'limble',
'client_id',
'string',
false,
null,
'The client_id for limble',
'client_id',
false,
''
);
}
}
@@ -0,0 +1,29 @@
<?php
namespace limble\config;
use Exception;
use traits\module_config_variable;
class limble_client_secret_c
{
use module_config_variable;
/**
* @throws Exception
*/
public function __construct()
{
self::setupConfigVariable(
'limble',
'client_secret',
'string',
false,
null,
'The client secret for limble',
'client_secret',
true,
''
);
}
}
@@ -0,0 +1,29 @@
<?php
namespace limble\config;
use Exception;
use traits\module_config_variable;
class limble_enabled_c
{
use module_config_variable;
/**
* @throws Exception
*/
public function __construct()
{
self::setupConfigVariable(
'limble',
'enabled',
'bool',
true,
null,
'Whether the limble module is enabled or not',
'1',
false,
'false'
);
}
}
@@ -0,0 +1,29 @@
<?php
namespace limble\config;
use Exception;
use traits\module_config_variable;
class limble_webhooks_enabled_c
{
use module_config_variable;
/**
* @throws Exception
*/
public function __construct()
{
self::setupConfigVariable(
'limble',
'webhooks_enabled',
'bool',
true,
null,
'Whether the limble modules webhooks are enabled or not',
'1',
false,
'false'
);
}
}
@@ -0,0 +1,133 @@
<?php
namespace limble\helpers;
class limble_task
{
/**
* The task ID
* @var int $taskID
*/
public int $taskID;
/**
* The name of the task
* @var string $name
*/
public string $name;
/**
* The user ID of the user assigned to the task
* @var int $userID
*/
public int $userID;
/**
* The team ID of the team assigned to the task
* @var int $teamID
*/
public int $teamID;
/**
* The location ID of the task
* @var int $locationID
*/
public int $locationID;
/**
* Whether the task is a template
* @var bool $template
*/
public bool $template;
public int $createdDate;
public int $startDate;
public int $due;
public string $description;
public int $dateCompleted;
public int $lastEdited;
public int $lastEditedByUser;
public int $completedByUser;
public int $assetID;
public int $completedUserWage;
public int $estimatedTime;
public int $priority;
public int $priorityID;
public int $downtime;
public string $completionNotes;
public ?string $requestorName;
public ?string $requestorEmail;
public ?string $requestorPhone;
public ?string $requestTitle;
public ?string $requestField1;
public ?string $requestField2;
public ?string $requestField3;
public ?string $requestDropdown1;
public ?string $requestDropdown2;
public ?string $requestDropdown3;
public ?string $requestorDescription;
public ?string $meta1;
public ?string $meta2;
public ?string $meta3;
public int $statusID;
public ?string $geoLocation;
public int|string $type;
public int $associatedTaskID;
public array $customTags;
public int $status;
public array $meta;
public function __construct(array $data)
{
$this->taskID = $data['taskID'] ?? 0;
$this->name = $data['name'] ?? '';
$this->userID = $data['userID'] ?? 0;
$this->teamID = $data['teamID'] ?? 0;
$this->locationID = $data['locationID'] ?? 0;
$this->template = $data['template'] ?? false;
$this->createdDate = $data['createdDate'] ?? 0;
$this->startDate = $data['startDate'] ?? 0;
$this->due = $data['due'] ?? 0;
$this->description = $data['description'] ?? '';
$this->dateCompleted = $data['dateCompleted'] ?? 0;
$this->lastEdited = $data['lastEdited'] ?? 0;
$this->lastEditedByUser = $data['lastEditedByUser'] ?? 0;
$this->completedByUser = $data['completedByUser'] ?? 0;
$this->assetID = $data['assetID'] ?? 0;
$this->completedUserWage = $data['completedUserWage'] ?? 0;
$this->estimatedTime = $data['estimatedTime'] ?? 0;
$this->priority = $data['priority'] ?? 0;
$this->priorityID = $data['priorityID'] ?? 0;
$this->downtime = $data['downtime'] ?? 0;
$this->completionNotes = isset($data['completionNotes']) ? (string)$data['completionNotes'] : '';
// Optional fields
foreach ( [
'requestorName', 'requestorEmail', 'requestorPhone', 'requestTitle',
'requestField1', 'requestField2', 'requestField3',
'requestDropdown1', 'requestDropdown2', 'requestDropdown3',
'requestorDescription', 'meta1', 'meta2', 'meta3',
'geoLocation'
] as $field ) {
if (isset($data[$field])) {
$this->$field = (string)$data[$field];
} else {
$this->$field = null;
}
}
$this->statusID = $data['statusID'] ?? 0;
$this->type = $data['type'] ?? 0;
$this->associatedTaskID = $data['associatedTaskID'] ?? 0;
$this->customTags = $data['customTags'] ?? [];
$this->status = $data['status'] ?? 0;
$this->meta = $data['meta'] ?? [];
$this->requestorName = $data['requestorName'] ?? '';
$this->requestorEmail = $data['requestorEmail'] ?? '';
$this->requestorPhone = $data['requestorPhone'] ?? '';
$this->requestTitle = $data['requestTitle'] ?? '';
$this->requestField1 = $data['requestField1'] ?? '';
$this->requestField2 = $data['requestField2'] ?? '';
$this->requestField3 = $data['requestField3'] ?? '';
$this->requestDropdown1 = $data['requestDropdown1'] ?? '';
$this->requestDropdown2 = $data['requestDropdown2'] ?? '';
$this->requestDropdown3 = $data['requestDropdown3'] ?? '';
$this->requestorDescription = $data['requestorDescription'] ?? '';
$this->meta1 = $data['meta1'] ?? '';
$this->meta2 = $data['meta2'] ?? '';
$this->meta3 = $data['meta3'] ?? '';
$this->geoLocation = $data['geoLocation'] ?? '';
}
}
@@ -0,0 +1,32 @@
<?php
namespace limble\helpers;
class limble_tasks
{
/**
* Format an array of tasks into an array of limble_task objects
* @param array $tasks
* @return limble_task[]
* @see limble_task
*/
public function formatTasks(array $tasks): array
{
$formattedTasks = [];
foreach ( $tasks as $task ) {
$formattedTasks[] = $this->formatTask($task);
}
return $formattedTasks;
}
/**
* Format a task array into a limble_task object
* @param array $task
* @return limble_task
* @see limble_task
*/
public function formatTask(array $task): limble_task
{
return new limble_task($task);
}
}
@@ -0,0 +1,27 @@
<?php
namespace limble\helpers;
use classes\slack;
abstract class limble_webhook_payload
{
/**
* The payload data from the webhook request.
* @var array $payload
*/
public array $payload = [];
protected slack $slack;
/**
* The constructor initializes the payload with the provided data.
*
* @param array $payload The payload data from the webhook request.
*/
public function __construct(array $payload)
{
$this->slack = new slack(); // Initialize the Slack class for sending notifications
$this->payload = $payload;
}
}
@@ -0,0 +1,137 @@
<?php
namespace limble\helpers;
use classes\limble;
use Exception;
/**
* {
* "taskID": 8203, // The unique identifier for the item the webhook event describes
* "status": "COMPLETE", // The status (e.g. task created, task complete, etc.) -- see below for a full list of possible statuses.
* "category": "task", // The category of item that triggered the webhook to fire (e.g. task, po, poItem).
* "user": "bob@limblecmms.com" // The user whose action caused the webhook to fire.
* }
*/
class limble_webhook_payload_task extends limble_webhook_payload
{
/**
* The task object
* @var string $task
*/
public string $task = limble_task::class;
/**
* The Task ID
* @var int $taskID
*/
public int $taskID;
/**
* The status of the task
* @var string $status
*/
public string $status;
/**
* The category of the task
* @var string $category
*/
public string $category;
/**
* The user who triggered the webhook
* @var string $user
*/
public string $user;
/**
* The task object
* @var limble_task $taskObject
*/
public limble_task $taskObject;
/**
* Constructor for the limble_webhook_payload_task class
* @throws Exception If the task ID is not provided or is invalid
*/
public function __construct(array $payload)
{
parent::__construct($payload);
$this->slack->send_message('Initializing Limble Webhook Payload Task', 'Limble Webhook Task Initialization');
$this->taskID = (int)($payload['taskID'] ?? 0);
$this->status = (string)($payload['status'] ?? '');
$this->category = (string)($payload['category'] ?? '');
$this->user = (string)($payload['user'] ?? '');
$this->taskObject = new limble_task(((new limble())->getTask($payload['taskID'])) ?: []);
$this->slack->send_message('Task has been initialized with ID: ' . $this->taskID, 'Limble Webhook Task Initialized');
$this->notifyWebhook();
}
protected function notifyWebhook(): void
{
// Notify the webhook with the task object
$this->slack->send_message('
Task ID: ' . $this->taskID . '
Status: ' . $this->status . '
Category: ' . $this->category . '
User: ' . $this->user,
'Limble Webhook Notification'
);
}
/**
* CREATED
* DELETED
* CHANGED DUE DATE
* CHANGED ASSIGNMENT
* COMPLETE
* CHANGED COMPLETED TASK
* COMPLETED TASK REOPENED
* CHANGED TASK NAME
* CHANGED TASK DESCRIPTION
* ADDED COMMENT TO TASK
* CUSTOM TAG ADDED TO TASK
* CUSTOM TAG REMOVED FROM TASK
* LOGGED TIME ON TASK
*/
protected function CREATED(): void
{
// Notify the webhook that a task has been created
$this->slack->send_message('Task Created: ' . $this->taskObject->name, 'Limble Webhook Task Created');
}
protected function DELETED(): void
{
// Notify the webhook that a task has been deleted
$this->slack->send_message('Task Deleted: ' . $this->taskObject->name, 'Limble Webhook Task Deleted');
}
protected function CHANGED_DUE_DATE(): void
{
// Notify the webhook that the due date of the task has changed
$this->slack->send_message('Task Due Date Changed: ' . $this->taskObject->name, 'Limble Webhook Task Due Date Changed');
}
protected function CHANGED_ASSIGNMENT(): void
{
// Notify the webhook that the assignment of the task has changed
$this->slack->send_message('Task Assignment Changed: ' . $this->taskObject->name, 'Limble Webhook Task Assignment Changed');
}
protected function COMPLETE(): void
{
// Notify the webhook that the task has been completed
$this->slack->send_message('Task Completed: ' . $this->taskObject->name, 'Limble Webhook Task Completed');
}
protected function CHANGED_COMPLETED_TASK(): void
{
// Notify the webhook that a completed task has been changed
$this->slack->send_message('Completed Task Changed: ' . $this->taskObject->name, 'Limble Webhook Completed Task Changed');
}
protected function CHANGED_TASK_NAME(): void
{
// Notify the webhook that the task name has changed
$this->slack->send_message('Task Name Changed: ' . $this->taskObject->name, 'Limble Webhook Task Name Changed');
}
}
@@ -0,0 +1,28 @@
<?php
interface limble_endpoints_i
{
/**
* Get the tasks from the limble API
* @return array
*/
public function listTasks(): array;
/**
* Get a specific task from the limble API
* @param int $taskId
* @return array
*/
public function getTask(int $taskId): array;
/**
* Get the auth header for the API
* @return string
* @throws Exception If the client_id is not set
* @throws Exception If the client_secret is not set
*/
public function getAuthHeader(): string;
}
@@ -0,0 +1,34 @@
<?php
interface limble_request_i
{
/**
* Send a request to the Limble API
* @param string $url The URL to send the request to
* @param string $method The HTTP method to use (GET, POST, PUT, DELETE)
* @param array $data The data to send with the request
* @param array $headers The headers to send with the request
* @return array The response from the API
* @throws Exception If there is an error with the request
* @throws Exception If the response is not valid JSON
* @throws Exception If the response is not successful
*/
public function sendRequest(
string $url,
string $method = 'GET',
array $data = [],
array $headers = []
): array;
/**
* Generate a Basic Auth header
* @param string $username The username for Basic Auth
* @param string $password The password for Basic Auth
* @return string The Basic Auth header
*/
public function generateBasicAuthHeader(
string $client_id,
string $client_secret
): string;
}
@@ -0,0 +1,59 @@
<?php
namespace limble;
require_once WD . '/modules/limble/config/limble_enabled_c.php';
require_once WD . '/modules/limble/config/limble_webhooks_enabled_c.php';
require_once WD . '/modules/limble/config/limble_client_id_c.php';
require_once WD . '/modules/limble/config/limble_client_secret_c.php';
use limble\config\limble_client_id_c;
use limble\config\limble_client_secret_c;
use limble\config\limble_enabled_c;
use limble\config\limble_webhooks_enabled_c;
use traits\module_config_t;
class limble_c
{
use module_config_t;
/**
* The API URL for the limble module
* @var string $api_url
*/
public string $api_url = 'https://api.limblecmms.com:443/v2'; //;'https://eu-api.limblecmms.com:443/v2';
/**
* The status of the module
* @var limble_enabled_c
*/
public limble_enabled_c $enabled;
/**
* The status of the webhooks
* @var limble_webhooks_enabled_c $webhooks_enabled
*/
public limble_webhooks_enabled_c $webhooks_enabled;
/**
* The client ID for the limble module
* @var limble_client_id_c
*/
public limble_client_id_c $client_id;
/**
* The client secret for the limble module
* @var limble_client_secret_c
*/
public limble_client_secret_c $client_secret;
public function __construct()
{
$this->setupConfig('limble');
$this->allowUpdate([
limble_enabled_c::class,
limble_webhooks_enabled_c::class,
limble_client_id_c::class,
limble_client_secret_c::class,
]);
$this->enabled = new limble_enabled_c();
$this->webhooks_enabled = new limble_webhooks_enabled_c();
$this->client_id = new limble_client_id_c();
$this->client_secret = new limble_client_secret_c();
}
}
@@ -0,0 +1,39 @@
<?php
namespace limble;
require_once WD . '/modules/limble/helpers/limble_task.php';
require_once WD . '/modules/limble/helpers/limble_tasks.php';
require_once WD . '/modules/limble/helpers/limble_webhook_payload.php';
require_once WD . '/modules/limble/helpers/limble_webhook_payload_task.php';
use limble\helpers\limble_task;
use limble\helpers\limble_tasks;
use limble\helpers\limble_webhook_payload;
class limble_helpers
{
/**
* The task helper
* @var string $tasks
*/
public string $tasks = limble_tasks::class;
/**
* The task helper
* @var string $limble_tasks
*/
public string $limble_tasks = limble_tasks::class;
/**
* The task object
* @var string $limble_task
*/
public string $limble_task = limble_task::class;
/**
* The webhook payload task object
* @var string $limble_webhook_payload_task
*/
public string $limble_webhook_payload_task = limble_webhook_payload::class;
}
@@ -0,0 +1,28 @@
<?php
namespace helpers;
use Exception;
use objects\products_o;
class xlvask_parser_lille_bil extends xlvask_product_parser
{
use xlvask_product_parser_t;
public function __construct()
{
$this->setup('Lille bil');
}
/**
* @inheritDoc
* @throws Exception
*/
protected function parse(xlvask_wash_item $wash_item, xlvask_usage_log $xlvask_usage_log): products_o
{
// For some reason, in XLVask the primary product is stored as vask_udf_rt in some cases,
// This is a workaround to handle that case. (Since the functionality is exactly the same as // xlvask_parser_stor_bil)
return (new xlvask_parser_stor_bil())->parseProduct(...func_get_args());
}
}
@@ -5,6 +5,7 @@ namespace helpers;
use Exception;
use objects\orders_o;
use objects\users_o;
use objects\xlvask_potential_order_matches_o;
class xlvask_tasks
{
@@ -35,6 +36,7 @@ class xlvask_tasks
// Synchronize users, this will keep the users (XL Vask customer objects) in cache up to date
$this->runSyncUsers(true);
$this->runSyncUsage();
$this->runCleanupTasks();
};
}
@@ -255,6 +257,21 @@ class xlvask_tasks
//echo '# Checking for orders that might be addressing this wash.' . PHP_EOL;
// Check if an order that matches this wash exists.
if ($log->getPotentialOrder() !== null) {
$xlvask_potential_order_matches_o = new xlvask_potential_order_matches_o();
// Check if the potential order match is already in the database (Prevent duplicates)
if (!$xlvask_potential_order_matches_o->doesWashPotentialOrderMatchExist(
$log->WashId,
)) {
// There's no match in the database, so we will add it.
//echo '# Adding potential order match for wash: ' . $log->WashId . ' - Order: ' . $log->getPotentialOrder()->id . ' - Customer: ' . $customer->customerId . ' - Customer Number: ' . (int)$customer->getUser()->customer_number->value() . ' - Department: ' . $log->getDepartment()->id . PHP_EOL;
$xlvask_potential_order_matches_o->add(
(string)$log->WashId,
(int)$log->getPotentialOrder()->id,
(string)$customer->customerId,
(int)$customer->getUser()->customer_number->value(),
(int)$log->getDepartment()->id,
);
}
//echo '# This wash might be associated with an order: ' . $log->getPotentialOrder()->id . PHP_EOL;
} else {
// If no order is found, we will generate a new order.
@@ -332,4 +349,19 @@ class xlvask_tasks
// Return the order object
return $order;
}
/**
* Run the cleanup tasks
* This task is used to clean up the XL Vask module.
* @return void
* @throws Exception If the task fails
*/
public function runCleanupTasks(): void
{
// Define the XL Vask object
$xlvask = new \classes\xlvask();
// Require the module to be enabled
$xlvask->requireModuleEnabled();
// Run
}
}
@@ -6,6 +6,7 @@ use classes\xlvask;
use Exception;
use objects\departments_o;
use objects\orders_o;
use objects\xlvask_potential_order_matches_o;
class xlvask_usage_log
{
@@ -551,8 +552,11 @@ class xlvask_usage_log
$date['end'],
);
if ($order) {
// If an order is found, return it
return $order;
// Make sure the order duplicate is not ignored
if (!(new xlvask_potential_order_matches_o())->shouldIgnoreDuplicate($this->WashId)) {
// If an order is found, return it
return $order;
}
}
// If no order is found, return null
return null;
@@ -30,6 +30,7 @@ require_once WD . '/modules/xlvask/helpers/xlvask_parser_manuel_spot_free.php';
require_once WD . '/modules/xlvask/helpers/xlvask_parser_manual_ht_varmtvand.php';
require_once WD . '/modules/xlvask/helpers/xlvask_parser_kapellvask_alle_trailere.php';
require_once WD . '/modules/xlvask/helpers/xlvask_parser_special_s_be_front.php';
require_once WD . '/modules/xlvask/helpers/xlvask_parser_lille_bil.php';
use helpers\xlvask_cache;
use helpers\xlvask_customer;
@@ -0,0 +1,154 @@
<?php
namespace objects;
use classes\db;
use classes\object_property;
use classes\slack;
use Exception;
use traits\db_object_t;
class xlvask_potential_order_matches_o extends db
{
use db_object_t;
public object_property $wash_id; // The wash id (In XL Vask - UUID)
public object_property $order_id; // The order id that this potential order match is for (In XL Vask - UUID)
public object_property $customer_id; // The customer id (In XL Vask - UUID)
public object_property $customer_number; // The customer number
public object_property $ignore_duplicate; // Whether to ignore duplicate matches (0 = No, 1 = Yes)
public object_property $department; // The department id
public object_property $created_at; // The created at timestamp
public object_property $updated_at; // The updated at timestamp
public object_property $deleted_at; // The deleted at timestamp
public function structure(): void
{
$this->setTable('xlvask_potential_order_matches');
}
/**
* Convert the object to an array
* @throws Exception If the object is not selected
* @throws Exception If the object is not found
*/
public function asArray(): array
{
self::requireSelected();
return [
'id' => (int)$this->id,
'wash_id' => (string)$this->wash_id->value(),
'order_id' => (int)$this->order_id->value(),
'customer_id' => (int)$this->customer_id->value(),
'customer_number' => $this->customer_number->value(),
'ignore_duplicate' => (int)$this->ignore_duplicate->value(),
'department' => (int)$this->department->value(),
'created_at' => (int)$this->created_at->value(),
'updated_at' => (int)$this->updated_at->value(),
];
}
/**
* Add a new object to the database
* @param string $washId The wash id (In XL Vask - UUID)
* @param int $order The order id that this potential order match is for (In XL Vask - UUID)
* @param string $customer_id The customer id (In XL Vask - UUID)
* @param int $customerNumber The customer number
* @param int $department The department id
* @return void
* @throws Exception If the object was not created successfully
*/
public function add(
string $washId,
int $order,
string $customer_id,
int $customerNumber,
int $department,
): void
{
//echo 'Wash ID: ' . $washId . ', Order: ' . $order . ', Customer ID: ' . $customer_id . ', Customer Number: ' . $customerNumber . ', Department: ' . $department . "\n";
$tmp_id = self::add_object(
[
'wash_id' => $washId,
'order_id' => $order,
'customer_id' => $customer_id,
'customer_number' => $customerNumber,
'ignore_duplicate' => 0, // Default to not ignoring duplicates
'department' => $department,
]
);
//echo 'Temporary ID: ' . $tmp_id . "\n";
$this->id = $tmp_id;
self::getObjectProperties();
self::objectChanged();
self::notifyDepartment();
}
public function getObjectProperties(): void
{
$this->wash_id = new object_property($this->table, $this->id, 'wash_id', 'string', true);
$this->order_id = new object_property($this->table, $this->id, 'order_id', 'int', true);
$this->customer_id = new object_property($this->table, $this->id, 'customer_id', 'string', true);
$this->customer_number = new object_property($this->table, $this->id, 'customer_number', 'int', true);
$this->ignore_duplicate = new object_property($this->table, $this->id, 'ignore_duplicate', 'int', true);
$this->department = new object_property($this->table, $this->id, 'department', 'int', true);
$this->created_at = new object_property($this->table, $this->id, 'created_at', 'int', true);
$this->updated_at = new object_property($this->table, $this->id, 'updated_at', 'int', true);
$this->deleted_at = new object_property($this->table, $this->id, 'deleted_at', 'int', false);
}
public function objectChanged(): void
{
//TODO: Add cache invalidation
}
/**
* Notify the department about the new potential order match
* @throws Exception If the object is not selected
* @throws Exception If the department is not found
* @throws Exception If the department notification fails
*/
public function notifyDepartment(): void
{
$this->requireSelected();
$slack = new slack();
$slack->send_message('
A new potential order match has been created:
- Wash ID: ' . $this->wash_id->value() . '
- Order ID: ' . $this->order_id->value() . '
- Customer ID: ' . $this->customer_id->value() . '
- Customer Number: ' . $this->customer_number->value() . '
- Department: ' . $this->department->value(),
'xlvask_potential_order_matches');
}
public function shouldIgnoreDuplicate(string $WashId): bool
{
if (!$this->doesWashPotentialOrderMatchExist($WashId)) {
return false;
}
// Check if the potential order match for the given wash ID has ignore_duplicate set to 1
$result = $this->getFieldsWhere(
[
'wash_id' => $WashId,
'ignore_duplicate' => 1,
],
[
'id'
]
);
return count($result) > 0;
}
public function doesWashPotentialOrderMatchExist(string $WashId): bool
{
// Check if a potential order match exists for the given wash ID
return count($this->getFieldsWhere(
[
'wash_id' => $WashId,
],
[
'id'
])) > 0;
}
}
@@ -449,5 +449,43 @@ class moduleConfigRoute
'entra_config' => 'Update entra config'
]
);
/** Limble config > GET */
$this->get('/limble/config', function () {
global $response;
$this->requirePermission('modules_limble_config');
$user = (new authentication())->get_user();
if ($user) {
(new logs_o())->add('limble_config', 'global', 1, $user->id, 'LIMBLE_CONFIG', 'Successfully fetched limble config');
$response->success(
(new \classes\limble())->config->getConfigRequest()
);
} else {
(new logs_o())->add('limble_config', 'global', 1, 0, 'LIMBLE_CONFIG', 'No user found, or invalid session');
$response->error('Invalid session', 400);
}
},
[
'modules_limble_config' => 'Get limble config'
]
);
/** Limble config > POST */
$this->post('/limble/config', function () {
global $response;
$this->requirePermission('modules_limble_config');
$user = (new authentication())->get_user();
if ($user) {
(new logs_o())->add('limble_config', 'global', 1, $user->id, 'LIMBLE_CONFIG', 'Successfully updated limble config');
$response->success(
(new \classes\limble())->config->postConfigRequest()
);
} else {
(new logs_o())->add('limble_config', 'global', 1, 0, 'LIMBLE_CONFIG', 'No user found, or invalid session');
$response->error('Invalid session', 400);
}
},
[
'modules_limble_config' => 'Update limble config'
]
);
}
}
@@ -0,0 +1,69 @@
<?php
namespace routes;
use classes\limble;
use classes\response;
use classes\router;
use classes\slack;
use limble\helpers\limble_webhook_payload_task;
use traits\route_t;
class moduleLimbleRoute
{
use route_t;
public function run(): void
{
global /** @var response $response */
/** @var router $router */
$router, $response;
$this->post('/modules/limble/webhook/task', function () {
global $response;
$slack = new slack();
$slack->send_message('Limble Webhook Task Triggered', 'Limble Webhook');
//TODO: Add authentication of some sort here
//self::requirePermission('modules_limble_webhooks_task');
// Check if the module is enabled
$limble = new limble();
$limble->requireModuleEnabled();
$limble->requireWebhooksEnabled();
$payload = json_decode(file_get_contents('php://input'), true);
if (json_last_error() !== JSON_ERROR_NONE) {
$response->error('Invalid JSON payload', 400);
}
$slack->send_message('TaskId: ' . ($payload['taskID'] ?? 'Not provided'), 'Limble Webhook Task ID');
$slack->send_message('Payload: ' . json_encode($payload), 'Limble Webhook Task Payload');
$task = new limble_webhook_payload_task($payload);
//$task = new $limble->helpers->limble_webhook_payload_task($payload);
$slack->send_message('Limble Webhook Task Received: ' . $task->taskID, 'Limble Webhook Task');
$slack->send_message('Task Name: ' . $task->taskObject->name, 'Limble Webhook Task Name');
// $slack->send_message('Task ID: ' . $payload['task_id'], 'Limble Webhook Task ID');
// Response
$response->success('Debug', 200);
},
[
'modules_limble_webhooks_task' => 'Send Limble Webhook Task (POST)',
]
);
$this->get('/modules/limble/tasks', function () {
global $response;
$slack = new slack();
$slack->send_message('Limble Tasks Endpoint Triggered', 'Limble Tasks');
//self::requirePermission('modules_limble_tasks');
// Check if the module is enabled
$limble = new limble();
$limble->requireModuleEnabled();
// Get the tasks
$tasks = $limble->getTasks()->formatTasks($limble->listTasks());
// Response
$response->success($tasks, 200);
},
[
'modules_limble_tasks' => 'Get Limble Tasks (GET)',
]
);
}
}
@@ -172,7 +172,7 @@ class moduleXLVaskRoute
$this->get('/modules/xlvask/tasks/sync-usage', function () {
global $response;
//self::requirePermission('modules_xlvask_sync_usage');
self::requirePermission('modules_xlvask_sync_usage');
// Create the xlvask tasks object
$xlvask = new xlvask();
// Run the sync usage task
@@ -0,0 +1,130 @@
<?php
namespace routes;
use classes\authentication;
use objects\logs_o;
use objects\xlvask_potential_order_matches_o;
use traits\route_t;
class potentialOrderMatchesRoute
{
use route_t;
public function run(): void
{
$this->get('/orders/sync/potential-matches', function () {
// Require the user to be logged in
global $response;
self::requirePermission('list_potential_order_matches');
// Check if the user has permission to list all potential order matches
if (self::hasPermission('list_all_potential_order_matches')) {
$this->requirePermission('list_all_potential_order_matches');
} else {
$this->requirePermission('list_own_potential_order_matches');
}
// Get the user object
$user = (new authentication())->get_user();
// Check if the request was successful
if ($user) {
// Log the incident
(new logs_o())->add('potential_order_matches', 'global', 1, $user->id, 'LIST_OWN_POTENTIAL_ORDER_MATCHES', 'User accessed the list of potential order matches');
$potentialOrderMatches = new \objects\xlvask_potential_order_matches_o();
// Return the list of potential order matches
$response->success(
$potentialOrderMatches
->setSearchableFields([
'id',
'wash_id',
'order_id',
'customer_id',
'customer_number',
'ignore_duplicate',
'department',
'created_at',
'updated_at',
'deleted_at',
])
->listObjectsWithPaginationIfSet(
function ($match) use ($potentialOrderMatches, $user) {
return [
'id' => (int)$match['id'],
'wash_id' => (string)$match['wash_id'],
'order_id' => (int)$match['order_id'],
'customer_id' => (string)$match['customer_id'],
'customer_number' => (int)$match['customer_number'],
'ignore_duplicate' => (int)$match['ignore_duplicate'],
'department' => (int)$match['department'],
'created_at' => (string)$match['created_at'],
'updated_at' => (string)$match['updated_at'],
'deleted_at' => $match['deleted_at'] ? (string)$match['deleted_at'] : null,
];
},
$potentialOrderMatches->forceRestrictFilters(
[
// This makes sure that the user can only see department matches that belong to their departments
'department' => $user->getGroup()->getDepartments()
]
)
)
);
} else {
// Log the incident
(new logs_o())->add('potential_order_matches', 'global', 1, 0, 'LIST_OWN_POTENTIAL_ORDER_MATCHES', 'No user found, or invalid session');
// Return an error
$response->error('Invalid session', 400);
}
},
[
'list_potential_order_matches' => 'List potential order matches, provided the user has either list_all_potential_order_matches, or list_own_potential_order_matches permission',
'list_own_potential_order_matches' => 'List all potential order matches for the logged in user',
'list_all_potential_order_matches' => 'List all potential order matches for all users (superuser only)',
]
);
$this->post('/orders/sync/potential-matches/ignore-duplicate', function () {
// Require the user to be logged in
global $response;
self::requirePermission('ignore_duplicate_potential_order_matches');
// Get the user object
$user = (new authentication())->get_user();
// Check if the request was successful
if ($user) {
// Log the incident
(new logs_o())->add('potential_order_matches', 'global', 1, $user->id, 'IGNORE_DUPLICATE_POTENTIAL_ORDER_MATCHES', 'User ignored duplicate potential order matches');
// Get the potential order match data from the request
self::requireParameters(['id']);
self::requireType((int)self::getParameter('id'), self::type_int());
self::requireMinValue((int)self::getParameter('id'), 1); // Ensure the ID is a positive integer
$potentialOrderMatch = new xlvask_potential_order_matches_o();
$potentialOrderMatch->select((int)self::getParameter('id'));
if (!$potentialOrderMatch->exists()) {
// Log the incident
(new logs_o())->add('potential_order_matches', 'global', 1, $user->id, 'IGNORE_DUPLICATE_POTENTIAL_ORDER_MATCHES', 'Potential order match not found');
// Return an error response
$response->error('Potential order match not found', 404);
}
// Check if the user has permission to ignore duplicate potential order matches in the department of the potential order match
self::requireDepartmentAccess((int)$potentialOrderMatch->department->value());
// Set the ignore_duplicate field to 1
$potentialOrderMatch->ignore_duplicate->set(1);
// Save the potential order match
$potentialOrderMatch->objectChanged();
// Return a success response
$response->success(
$potentialOrderMatch->asArray(),
200,
);
} else {
// Log the incident
(new logs_o())->add('potential_order_matches', 'global', 1, 0, 'IGNORE_DUPLICATE_POTENTIAL_ORDER_MATCHES', 'No user found, or invalid session');
// Return an error response
$response->error('Invalid session', 400);
}
},
[
'ignore_duplicate_potential_order_matches' => 'Ignore duplicate potential order matches for a specific wash and order',
]
);
}
}
@@ -951,8 +951,10 @@ trait db_object_t
}
// Prepare the SQL query to insert the data
$columns = implode(', ', array_keys($data));
// Escape the column names to prevent SQL injection and reserved keyword issues
$values = implode("', '", array_values($data));
$sql = "INSERT INTO $this->table ($columns) VALUES ('$values')";
//echo "SQL: $sql\n"; // Debugging line, can be removed in production
$db->query($sql);
return $db->insert_id();
} catch (Exception $e) {