676 lines
33 KiB
PHP
676 lines
33 KiB
PHP
<?php
|
|
|
|
namespace routes;
|
|
|
|
require_once WD . '/classes/xlvask_automation_service.php';
|
|
require_once WD . '/classes/xlvask_autopilot_service.php';
|
|
require_once WD . '/classes/xlvask_automation_policy_service.php';
|
|
|
|
use classes\authentication;
|
|
use classes\redis;
|
|
use classes\response;
|
|
use classes\stripe;
|
|
use classes\xlvask;
|
|
use classes\xlvask_autopilot_service;
|
|
use classes\xlvask_automation_service;
|
|
use classes\xlvask_automation_policy_service;
|
|
use objects\collected_order_invoices_o;
|
|
use objects\departments_o;
|
|
use objects\economic_module_orders;
|
|
use objects\orders_o;
|
|
use objects\stripe_module_orders_o;
|
|
use objects\stripe_payment_intents_o;
|
|
use objects\xlvask_usage_logs_o;
|
|
use traits\route_t;
|
|
|
|
class xlvaskUsageLogsRoute
|
|
{
|
|
use route_t;
|
|
|
|
public function run(): void
|
|
{
|
|
$this->get('/modules/xlvask/services/usage/orders', function () {
|
|
// Define the permissions:
|
|
$permission_list_own = 'list_xlvask_usage_orders_own'; // Permission to list own orders (Without department filter)
|
|
$permission_list_all = 'list_xlvask_usage_orders_all'; // Permission to list all orders (With department filter)
|
|
$response_includes_items = false; // Whether to include items in the response (This would increase the memory usage significantly)
|
|
// Require the user to be logged in
|
|
global $response;
|
|
if (!$this->hasPermission($permission_list_all)) {
|
|
$this->requirePermission($permission_list_own);
|
|
}
|
|
// Get the user object
|
|
$user = (new authentication())->get_user();
|
|
// Check if the request was successful
|
|
if ($user) {
|
|
$allowedHallIds = $this->allowedHallIdsForUser($user);
|
|
if ($allowedHallIds === []) {
|
|
$response->error('No XL Vask hall scope is available', 403);
|
|
return;
|
|
}
|
|
$xlvask_usage_logs = new xlvask_usage_logs_o();
|
|
$xlvask = new xlvask();
|
|
$automation_service = new xlvask_automation_service();
|
|
$linked_order_ids_by_wash_id = [];
|
|
$xlvask->new($xlvask->helpers->xlvask_usage_log)->getDepartment();
|
|
$orders_o = new orders_o();
|
|
$xlvask_usage_log = $xlvask->new($xlvask->helpers->xlvask_usage_log);
|
|
// Increase the memory limit to 512MB (Provided it's currently less than that)
|
|
if (ini_get('memory_limit') < '5120M') {
|
|
ini_set('memory_limit', '5120M');
|
|
}
|
|
// Return the list of usage logs
|
|
$result = $xlvask_usage_logs
|
|
// Make sure the Customer is not in the default customers list
|
|
->setAdditionalWhereClause("`Customer` NOT IN ('" . implode("', '", $xlvask_usage_log::$default_customers) . "')")
|
|
->listObjectsWithPaginationIfSet(
|
|
function ($log) use ($response_includes_items, $xlvask_usage_logs, $xlvask, $automation_service, &$linked_order_ids_by_wash_id) {
|
|
// Remove the 'id' field from the log
|
|
$id = (int)$log['id'];
|
|
$automation = $automation_service->readAutomationStateByUsageLogId($id, $log);
|
|
$amount_summary = $xlvask_usage_logs->getAmountSummaryReadOnly($log);
|
|
unset($log['id']);
|
|
// Convert the 'WashItems' field from JSON to an array
|
|
$log['WashItems'] = json_decode($log['WashItems'], true);
|
|
$usage_log_payload = array_intersect_key($log, array_flip([
|
|
'WashId',
|
|
'CustomerId',
|
|
'Customer',
|
|
'VatNumber',
|
|
'Location',
|
|
'Hall',
|
|
'HallId',
|
|
'StartTime',
|
|
'FinishTime',
|
|
'RegistrationNumber',
|
|
'VehicleType',
|
|
'IdentificationType',
|
|
'IdentificationId',
|
|
'Info',
|
|
'Updated',
|
|
'Prepaid',
|
|
'FinishStatus',
|
|
'CustomerGuid',
|
|
'VehicleId',
|
|
'WashItems',
|
|
'ignored_at',
|
|
'ignored_by',
|
|
'ignored_reason',
|
|
]));
|
|
// Create a new xlvask usage log object
|
|
$tmp = $xlvask->new($xlvask->helpers->xlvask_usage_log);
|
|
// Set the properties of the temporary object
|
|
$tmp->setProperties($usage_log_payload);
|
|
$wash_id = (string)$tmp->WashId;
|
|
if ($wash_id !== '' && !array_key_exists($wash_id, $linked_order_ids_by_wash_id)) {
|
|
$linked_order = (new orders_o())->selectByWashId($wash_id);
|
|
$linked_order_ids_by_wash_id[$wash_id] = $linked_order !== null ? (int)$linked_order->id : null;
|
|
}
|
|
$linked_order_id = $wash_id !== '' ? $linked_order_ids_by_wash_id[$wash_id] : null;
|
|
// Define the result structure
|
|
$isEligibleForAutomaticContinuance = $tmp->isEligibleForAutomaticContinuance(true);
|
|
// Return the result
|
|
$tmp_res = ($isEligibleForAutomaticContinuance ? (new orders_o())->simulateOrderFromXLVask($tmp, $response_includes_items) : []);
|
|
$tmp_res['order']['customer_name'] = $tmp->Customer; // Add the customer name to the order
|
|
$tmp_res['order']['total_net_amount'] = $amount_summary['total_net_amount'];
|
|
$tmp_res['order']['xlvask_primary_product_name'] = $amount_summary['primary_product_name'];
|
|
$tmp_res['order']['xlvask_amount_cached'] = $amount_summary['cached'];
|
|
// Clear memory
|
|
unset($tmp);
|
|
// Return the result
|
|
return [
|
|
'id' => $id, // Return the ID of the log
|
|
'fast_link_key' => null,
|
|
'automation' => $automation,
|
|
'source_hash' => $log['source_hash'] ?? null,
|
|
'source_revision' => $log['source_revision'] ?? null,
|
|
'source_observed_at' => $log['source_observed_at'] ?? null,
|
|
'source_stable_since' => $log['source_stable_since'] ?? null,
|
|
'source_observation_count' => (int)($log['source_observation_count'] ?? 0),
|
|
'import_state' => $log['import_state'] ?? 'unchanged',
|
|
'resolution_state' => $log['resolution_state'] ?? 'needs_review',
|
|
'certainty' => $log['certainty'] ?? 'none',
|
|
'planned_action' => $log['planned_action'] ?? 'none',
|
|
'state_reason' => $log['state_reason'] ?? null,
|
|
'expected_version' => isset($log['expected_version']) ? (int)$log['expected_version'] : 1,
|
|
'last_run_id' => isset($log['last_run_id']) ? (int)$log['last_run_id'] : null,
|
|
'last_evaluated_at' => $log['last_evaluated_at'] ?? null,
|
|
...$tmp_res['order'], // Return the simulated order from XLVask (with or without items)
|
|
'usage_log_id' => $id,
|
|
'linked_order_id' => $linked_order_id,
|
|
];
|
|
},
|
|
$xlvask_usage_logs->forceRestrictFilters(
|
|
[
|
|
// This makes sure that the user can only see department logs that belong to their departments
|
|
'HallId' => $allowedHallIds,
|
|
'FinishStatus' => ['1'], // Only show finished logs
|
|
]
|
|
)
|
|
);
|
|
// Return the response
|
|
$response->success($result);
|
|
} else {
|
|
// Return an error
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
},
|
|
[
|
|
'list_xlvask_usage_orders_own' => 'List own xlvask usage orders (Without department filter)',
|
|
'list_xlvask_usage_orders_all' => 'List all xlvask usage orders (With department filter)',
|
|
]
|
|
);
|
|
|
|
$this->get('/modules/xlvask/services/usage/orders/summary', function () {
|
|
global $response;
|
|
if (!$this->hasPermission('list_xlvask_usage_orders_all')) {
|
|
$this->requirePermission('list_xlvask_usage_orders_own');
|
|
}
|
|
$user = (new authentication())->get_user();
|
|
if (!$user) {
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
$dateFrom = $this->isParametersSet(['dateFrom']) ? (string)$this->getParameter('dateFrom') : null;
|
|
$dateTo = $this->isParametersSet(['dateTo']) ? (string)$this->getParameter('dateTo') : null;
|
|
|
|
$response->success([
|
|
'summary' => (new xlvask_autopilot_service())->getSummary(
|
|
$dateFrom,
|
|
$dateTo,
|
|
$this->allowedHallIdsForUser($user)
|
|
),
|
|
]);
|
|
},
|
|
[
|
|
'list_xlvask_usage_orders_own' => 'Read XL Vask usage-log automation summary',
|
|
'list_xlvask_usage_orders_all' => 'Read all XL Vask usage-log automation summaries',
|
|
]
|
|
);
|
|
|
|
$this->post('/modules/xlvask/services/usage/autopilot-runs', function () {
|
|
global $response;
|
|
$this->requirePermission('manage_xlvask_usage_automation');
|
|
|
|
$user = (new authentication())->get_user();
|
|
if (!$user) {
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
|
|
$input = [];
|
|
foreach (['ids', 'dateFrom', 'dateTo', 'limit', 'forceRefetch', 'mode', 'idempotency_key'] as $key) {
|
|
if ($this->isParametersSet([$key])) {
|
|
$input[$key] = $this->getParameter($key);
|
|
}
|
|
}
|
|
|
|
$response->success([
|
|
'run' => (new xlvask_autopilot_service())->createRun(
|
|
$input,
|
|
(int)$user->id,
|
|
$this->allowedHallIdsForUser($user)
|
|
),
|
|
], 202);
|
|
},
|
|
[
|
|
'manage_xlvask_usage_automation' => 'Create an XL Vask usage-log autopilot run',
|
|
]
|
|
);
|
|
|
|
$this->get('/modules/xlvask/services/usage/automation/admin/readiness', function () {
|
|
global $response;
|
|
$this->requirePermission('superuser_xlvask_automation_activate');
|
|
$user = (new authentication())->get_user();
|
|
if (!$user) {
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
$dateFrom = $this->isParametersSet(['dateFrom']) ? trim((string)$this->getParameter('dateFrom')) : null;
|
|
$dateTo = $this->isParametersSet(['dateTo']) ? trim((string)$this->getParameter('dateTo')) : null;
|
|
$response->success((new xlvask_automation_policy_service())->readinessReadOnly(
|
|
$dateFrom,
|
|
$dateTo,
|
|
$this->allowedHallIdsForUser($user)
|
|
));
|
|
}, [
|
|
'superuser_xlvask_automation_activate' => 'Inspect XL Vask automation activation readiness',
|
|
]);
|
|
|
|
$this->get('/modules/xlvask/services/usage/automation/capabilities', function () {
|
|
global $response;
|
|
if (!$this->hasPermission('list_xlvask_usage_orders_all')
|
|
&& !$this->hasPermission('list_xlvask_usage_orders_own')) {
|
|
$this->requirePermission('list_xlvask_usage_orders_own');
|
|
}
|
|
$user = (new authentication())->get_user();
|
|
if (!$user) {
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
$dateFrom = $this->isParametersSet(['dateFrom']) ? trim((string)$this->getParameter('dateFrom')) : null;
|
|
$dateTo = $this->isParametersSet(['dateTo']) ? trim((string)$this->getParameter('dateTo')) : null;
|
|
$service = new xlvask_automation_policy_service();
|
|
$capabilities = $service->capabilitiesReadOnly($dateFrom, $dateTo, $this->allowedHallIdsForUser($user));
|
|
$canManage = $this->hasPermission('manage_xlvask_usage_automation');
|
|
$canManagePolicy = $this->hasPermission('superuser_xlvask_automation_activate');
|
|
$response->success([
|
|
'can_view' => true,
|
|
'can_review' => $canManage,
|
|
'can_dry_run' => $canManage,
|
|
'can_execute' => $canManage && in_array('execute', $capabilities['allowed_modes'], true),
|
|
'can_manage_policy' => $canManagePolicy,
|
|
'can_halt' => $canManagePolicy,
|
|
...$capabilities,
|
|
]);
|
|
}, [
|
|
'list_xlvask_usage_orders_own' => 'Inspect XL Vask automation capabilities',
|
|
]);
|
|
|
|
$this->get('/modules/xlvask/services/usage/autopilot-runs/active', function () {
|
|
global $response;
|
|
$this->requirePermission('manage_xlvask_usage_automation');
|
|
$user = (new authentication())->get_user();
|
|
if (!$user) {
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
$response->success(['run' => (new xlvask_automation_policy_service())->activeRunReadOnly(
|
|
$this->allowedHallIdsForUser($user)
|
|
)]);
|
|
}, ['manage_xlvask_usage_automation' => 'Inspect the active XL Vask automation run']);
|
|
|
|
$this->post('/modules/xlvask/services/usage/automation/admin/policy/previews', function () {
|
|
global $response;
|
|
$this->requirePermission('superuser_xlvask_automation_activate');
|
|
self::requireParameters(['target_stage', 'reason']);
|
|
$user = (new authentication())->get_user();
|
|
if (!$user) {
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
$response->success(['preview' => (new xlvask_automation_policy_service())->createPolicyPreview(
|
|
(string)$this->getParameter('target_stage'),
|
|
(string)$this->getParameter('reason'),
|
|
(int)$user->id
|
|
)]);
|
|
}, ['superuser_xlvask_automation_activate' => 'Preview an XL Vask automation stage transition']);
|
|
|
|
$this->post('/modules/xlvask/services/usage/automation/admin/policy/apply', function () {
|
|
global $response;
|
|
$this->requirePermission('superuser_xlvask_automation_activate');
|
|
self::requireParameters(['preview_id', 'selection_hash', 'confirmation_text']);
|
|
$user = (new authentication())->get_user();
|
|
if (!$user) {
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
$response->success((new xlvask_automation_policy_service())->applyPolicyPreview([
|
|
'preview_id' => $this->getParameter('preview_id'),
|
|
'selection_hash' => $this->getParameter('selection_hash'),
|
|
'confirmation_text' => $this->getParameter('confirmation_text'),
|
|
], (int)$user->id));
|
|
}, ['superuser_xlvask_automation_activate' => 'Apply a previewed XL Vask automation stage transition']);
|
|
|
|
$this->post('/modules/xlvask/services/usage/automation/admin/halt', function () {
|
|
global $response;
|
|
$this->requirePermission('superuser_xlvask_automation_activate');
|
|
$user = (new authentication())->get_user();
|
|
if (!$user) {
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
$reason = $this->isParametersSet(['reason']) ? (string)$this->getParameter('reason') : '';
|
|
$response->success((new xlvask_automation_policy_service())->halt((int)$user->id, $reason));
|
|
}, ['superuser_xlvask_automation_activate' => 'Immediately halt XL Vask automatic actions']);
|
|
|
|
$this->post('/modules/xlvask/services/usage/automation/admin/calibrations/backtest', function () {
|
|
global $response;
|
|
$this->requirePermission('superuser_xlvask_automation_activate');
|
|
self::requireParameters(['segment_key']);
|
|
$user = (new authentication())->get_user();
|
|
if (!$user) {
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
$response->success([
|
|
'artifact' => (new xlvask_autopilot_service())->generateCalibrationArtifact(
|
|
trim((string)$this->getParameter('segment_key')),
|
|
(int)$user->id
|
|
),
|
|
]);
|
|
}, [
|
|
'superuser_xlvask_automation_activate' => 'Generate an inactive XL Vask historical calibration artifact',
|
|
]);
|
|
|
|
$this->post('/modules/xlvask/services/usage/automation/admin/calibrations/labels', function () {
|
|
global $response;
|
|
$this->requirePermission('superuser_xlvask_automation_activate');
|
|
self::requireParameters(['suggestion_id', 'outcome']);
|
|
$user = (new authentication())->get_user();
|
|
if (!$user) {
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
$allowedHallIds = $this->allowedHallIdsForUser($user);
|
|
$result = (new xlvask_autopilot_service())->adjudicateCalibrationLabel(
|
|
(int)$this->getParameter('suggestion_id'),
|
|
trim((string)$this->getParameter('outcome')),
|
|
(int)$user->id,
|
|
$allowedHallIds
|
|
);
|
|
$dateFrom = $this->isParametersSet(['dateFrom']) ? trim((string)$this->getParameter('dateFrom')) : null;
|
|
$dateTo = $this->isParametersSet(['dateTo']) ? trim((string)$this->getParameter('dateTo')) : null;
|
|
$response->success([
|
|
...$result,
|
|
'readiness' => (new xlvask_automation_policy_service())->readinessReadOnly($dateFrom, $dateTo, $allowedHallIds),
|
|
]);
|
|
}, [
|
|
'superuser_xlvask_automation_activate' => 'Adjudicate one exact XL Vask suggestion for calibration evidence',
|
|
]);
|
|
|
|
$this->post('/modules/xlvask/services/usage/automation/admin/calibrations/{id}/activate', function () {
|
|
global $response;
|
|
$this->requirePermission('superuser_xlvask_automation_activate');
|
|
self::requireParameters(['artifact_hash', 'confirmation_text']);
|
|
$user = (new authentication())->get_user();
|
|
if (!$user) {
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
$response->success((new xlvask_autopilot_service())->activateCalibration(
|
|
(int)($this->fromRoute('id') ?? 0),
|
|
trim((string)$this->getParameter('artifact_hash')),
|
|
(string)$this->getParameter('confirmation_text'),
|
|
(int)$user->id
|
|
));
|
|
}, [
|
|
'superuser_xlvask_automation_activate' => 'Explicitly activate a qualifying XL Vask calibration artifact',
|
|
]);
|
|
|
|
$this->post('/modules/xlvask/services/usage/automation/admin/wash-id-uniqueness/activate', function () {
|
|
global $response;
|
|
$this->requirePermission('superuser_xlvask_automation_activate');
|
|
self::requireParameters(['confirmation_text']);
|
|
$response->success((new xlvask_autopilot_service())->activateWashIdUniqueness(
|
|
(string)$this->getParameter('confirmation_text')
|
|
));
|
|
}, [
|
|
'superuser_xlvask_automation_activate' => 'Explicitly activate the guarded XL Vask wash-id uniqueness migration',
|
|
]);
|
|
|
|
$this->get('/modules/xlvask/services/usage/autopilot-runs/{id}', function () {
|
|
global $response;
|
|
$this->requirePermission('manage_xlvask_usage_automation');
|
|
$id = (int)($this->fromRoute('id') ?? 0);
|
|
if ($id < 1) {
|
|
$response->error('Invalid XL Vask autopilot run id', 400);
|
|
}
|
|
$user = (new authentication())->get_user();
|
|
if (!$user) {
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
$run = (new xlvask_autopilot_service())->getRun(
|
|
$id,
|
|
(int)$user->id,
|
|
$this->allowedHallIdsForUser($user)
|
|
);
|
|
$response->success(['run' => $run]);
|
|
},
|
|
[
|
|
'manage_xlvask_usage_automation' => 'Read XL Vask usage-log autopilot run status',
|
|
]
|
|
);
|
|
|
|
$this->post('/modules/xlvask/services/usage/automation/decisions/preview', function () {
|
|
global $response;
|
|
$this->requirePermission('manage_xlvask_usage_automation');
|
|
$user = (new authentication())->get_user();
|
|
if (!$user) {
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
$input = [];
|
|
foreach (['usage_log_ids', 'action', 'suggestion_id', 'order_id', 'reason'] as $key) {
|
|
if ($this->isParametersSet([$key])) {
|
|
$input[$key] = $this->getParameter($key);
|
|
}
|
|
}
|
|
$response->success([
|
|
'preview' => (new xlvask_autopilot_service())->createDecisionPreview(
|
|
$input,
|
|
(int)$user->id,
|
|
$this->allowedHallIdsForUser($user)
|
|
),
|
|
]);
|
|
}, ['manage_xlvask_usage_automation' => 'Preview an XL Vask automation decision']);
|
|
|
|
$this->post('/modules/xlvask/services/usage/automation/decisions/apply', function () {
|
|
global $response;
|
|
$this->requirePermission('manage_xlvask_usage_automation');
|
|
$user = (new authentication())->get_user();
|
|
if (!$user) {
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
$input = [];
|
|
foreach (['preview_id', 'selection_hash', 'confirmation_text'] as $key) {
|
|
if ($this->isParametersSet([$key])) {
|
|
$input[$key] = $this->getParameter($key);
|
|
}
|
|
}
|
|
$response->success(
|
|
(new xlvask_autopilot_service())->applyDecision(
|
|
$input,
|
|
(int)$user->id,
|
|
$this->allowedHallIdsForUser($user)
|
|
)
|
|
);
|
|
}, ['manage_xlvask_usage_automation' => 'Apply a previewed XL Vask automation decision']);
|
|
|
|
$this->patch('/modules/xlvask/services/usage/orders/{id}/ignore', function () {
|
|
global $response;
|
|
$this->requirePermission('ignore_xlvask_usage_order');
|
|
$response->error('Use the server-generated automation decision preview and apply endpoints.', 409);
|
|
},
|
|
[
|
|
'ignore_xlvask_usage_order' => 'Ignore an XL Vask usage log for invoice period flagging',
|
|
]
|
|
);
|
|
|
|
$this->post('/modules/xlvask/services/usage/orders/automation/run', function () {
|
|
global $response;
|
|
$this->requirePermission('manage_xlvask_usage_automation');
|
|
$response->error('Deprecated. Use /modules/xlvask/services/usage/autopilot-runs.', 410);
|
|
},
|
|
[
|
|
'manage_xlvask_usage_automation' => 'Evaluate and execute XL Vask usage-log automation',
|
|
]
|
|
);
|
|
|
|
$this->post('/modules/xlvask/services/usage/orders/{id}/automation/evaluate', function () {
|
|
global $response;
|
|
$this->requirePermission('manage_xlvask_usage_automation');
|
|
$response->error('Deprecated. Use /modules/xlvask/services/usage/autopilot-runs.', 410);
|
|
},
|
|
[
|
|
'manage_xlvask_usage_automation' => 'Evaluate XL Vask usage-log automation',
|
|
]
|
|
);
|
|
|
|
$this->post('/modules/xlvask/services/usage/orders/{id}/automation/accept', function () {
|
|
global $response;
|
|
$this->requirePermission('manage_xlvask_usage_automation');
|
|
|
|
$user = (new authentication())->get_user();
|
|
if (!$user) {
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
|
|
$id = (int)($this->fromRoute('id') ?? 0);
|
|
if ($id < 1) {
|
|
$response->error('Invalid XL Vask usage log id', 400);
|
|
}
|
|
self::requireUsageLogInHallScope($id, $this->allowedHallIdsForUser($user));
|
|
|
|
$response->error('Use the server-generated automation decision preview and apply endpoints.', 409);
|
|
},
|
|
[
|
|
'manage_xlvask_usage_automation' => 'Accept an XL Vask usage-log automation suggestion',
|
|
]
|
|
);
|
|
|
|
$this->post('/modules/xlvask/services/usage/orders/{id}/automation/deny', function () {
|
|
global $response;
|
|
$this->requirePermission('manage_xlvask_usage_automation');
|
|
|
|
$user = (new authentication())->get_user();
|
|
if (!$user) {
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
|
|
$id = (int)($this->fromRoute('id') ?? 0);
|
|
if ($id < 1) {
|
|
$response->error('Invalid XL Vask usage log id', 400);
|
|
}
|
|
self::requireUsageLogInHallScope($id, $this->allowedHallIdsForUser($user));
|
|
|
|
$response->error('Use the server-generated automation decision preview and apply endpoints.', 409);
|
|
},
|
|
[
|
|
'manage_xlvask_usage_automation' => 'Deny an XL Vask usage-log automation suggestion',
|
|
]
|
|
);
|
|
|
|
$this->get('/modules/xlvask/services/usage/orders/fast-link', function () {
|
|
global $response;
|
|
$this->requirePermission('list_xlvask_usage_orders_own');
|
|
$user = (new authentication())->get_user();
|
|
if (!$user) {
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
self::requireParameters([
|
|
'fast_link_key', // Example: 'temporary_cache_6878cf0603d77'
|
|
]);
|
|
// Get the fast link key from the request
|
|
$fast_link_key = (string)self::getParameter('fast_link_key');
|
|
self::requireType($fast_link_key, self::type_string());
|
|
self::requireMinLength('fast_link_key', 20); // Minimum length of the fast link key
|
|
self::requireMaxLength('fast_link_key', 50); // Maximum length of the fast link key
|
|
// Check if the fast link key is valid
|
|
// First, check if the key has the correct format
|
|
if (preg_match('/^temporary_cache_[a-z0-9]{12,32}$/', $fast_link_key)) {
|
|
// Get the cached data from Redis
|
|
$cached_data = redis->get($fast_link_key);
|
|
// Check if the cached data is valid
|
|
if ($cached_data) {
|
|
// Decode the cached data
|
|
$data = json_decode($cached_data, true);
|
|
// Check if the data is valid
|
|
if (is_array($data)) {
|
|
$allowedHallIds = $this->allowedHallIdsForUser($user);
|
|
if (!in_array(trim((string)($data['HallId'] ?? '')), $allowedHallIds, true)) {
|
|
$response->error('Fast link is outside the current XL Vask hall scope', 403);
|
|
}
|
|
// Delete the cached data from Redis
|
|
redis->delete($fast_link_key);
|
|
// Return the data
|
|
$xlvask = new xlvask();
|
|
$order_arr = (new orders_o())->simulateOrderFromXLVask($xlvask->new($xlvask->helpers->xlvask_usage_log)->setProperties($data), true); // ['order' => $order_arr, 'order_items' => $items_arr]
|
|
$tmp_order_obj = (object)[];
|
|
$tmp_order_arr = $order_arr['order'] ?? [];
|
|
/**
|
|
* "id": -1,
|
|
* "customer_id": 39159000,
|
|
* "cashier_id": 2285,
|
|
* "reference": "Simulated Order from XL Vask",
|
|
* "notes": "This is a simulated order generated from an XL Vask usage log",
|
|
* "department_id": 1,
|
|
* "reg_1": "DE55248",
|
|
* "reg_2": "",
|
|
* "reg_3": "",
|
|
* "completed_at": null,
|
|
* "created_at": "2025-07-16 13:04:54",
|
|
* "deleted_at": null,
|
|
* "total_net_amount": 683,
|
|
* "invoice_collection_id": 0,
|
|
* "booking_id": 0,
|
|
* "wash_id": "b24728ea-e22b-4dce-8cd3-a0998f7fdc5e",
|
|
* "lane": 1,
|
|
* "closed_at": null
|
|
*/
|
|
$tmp_order_obj->customer_id = $tmp_order_arr['customer_id'] ?? 0;
|
|
$tmp_order_obj->department_id = $tmp_order_arr['department_id'] ?? 0;
|
|
$tmp_order_obj->reg_1 = $tmp_order_arr['reg_1'] ?? '';
|
|
$tmp_order_obj->reg_2 = $tmp_order_arr['reg_2'] ?? '';
|
|
$tmp_order_obj->reg_3 = $tmp_order_arr['reg_3'] ?? '';
|
|
$tmp_order_obj->created_at = $tmp_order_arr['created_at'] ?? '';
|
|
$tmp_order_obj->wash_id = $tmp_order_arr['wash_id'] ?? '';
|
|
$tmp_order_obj->lane = $tmp_order_arr['lane'] ?? 0;
|
|
$response->success([
|
|
...$order_arr,
|
|
'potential_duplicates' => (new orders_o())->getOrderPotentialDuplicatesIfFound(
|
|
$tmp_order_obj->reg_1,
|
|
$tmp_order_obj->reg_2,
|
|
$tmp_order_obj->reg_3,
|
|
$tmp_order_obj->department_id,
|
|
$tmp_order_obj->created_at,
|
|
),
|
|
]);
|
|
} else {
|
|
// Return an error if the data is not valid
|
|
$response->error('Invalid cached data', 400);
|
|
}
|
|
} else {
|
|
// Return an error if the fast link key does not exist in Redis
|
|
$response->error('Fast link key not found', 404);
|
|
}
|
|
} else {
|
|
// Return an error if the fast link key is invalid
|
|
$response->error('Invalid fast link key format', 400);
|
|
}
|
|
},
|
|
[
|
|
'fast_link_key' => 'string', // Example: 'temporary_cache_6878cf0603d77'
|
|
]
|
|
);
|
|
}
|
|
|
|
private function allowedHallIdsForUser(object $user): array
|
|
{
|
|
global $db;
|
|
if ($this->hasPermission('list_xlvask_usage_orders_all')) {
|
|
$sqlWithSoftDelete = "SELECT DISTINCT HallId FROM plate_scanners
|
|
WHERE HallId IS NOT NULL AND TRIM(HallId) <> '' AND deleted_at IS NULL";
|
|
$sqlWithoutSoftDelete = "SELECT DISTINCT HallId FROM plate_scanners
|
|
WHERE HallId IS NOT NULL AND TRIM(HallId) <> ''";
|
|
try {
|
|
$result = $db->query($sqlWithSoftDelete);
|
|
} catch (\Throwable $throwable) {
|
|
$message = (string)$throwable->getMessage();
|
|
$missingDeletedAt = str_contains($message, "Unknown column 'deleted_at'")
|
|
|| str_contains($message, "Unknown column `deleted_at`");
|
|
if (!$missingDeletedAt) {
|
|
throw $throwable;
|
|
}
|
|
$result = $db->query($sqlWithoutSoftDelete);
|
|
}
|
|
$hallIds = $result === false ? [] : array_map(
|
|
static fn(array $row): string => trim((string)($row['HallId'] ?? '')),
|
|
$db->fetch_all($result)
|
|
);
|
|
} else {
|
|
$hallIds = (array)$user->getGroup()->getDepartmentsScannersHallIds();
|
|
}
|
|
return array_values(array_unique(array_filter(
|
|
array_map(static fn(mixed $id): string => trim((string)$id), $hallIds),
|
|
static fn(string $id): bool => $id !== '' && strlen($id) <= 191
|
|
)));
|
|
}
|
|
|
|
private static function requireUsageLogInHallScope(int $usageLogId, array $allowedHallIds): void
|
|
{
|
|
global $db, $response;
|
|
if ($allowedHallIds === []) {
|
|
$response->error('XL Vask usage log not found', 404);
|
|
}
|
|
$hallSql = implode(',', array_map(
|
|
static fn(string $hallId): string => "'" . $db->escape_string($hallId) . "'",
|
|
$allowedHallIds
|
|
));
|
|
$result = $db->query(
|
|
"SELECT id FROM xlvask_usage_logs WHERE id = {$usageLogId} AND HallId IN ({$hallSql}) LIMIT 1"
|
|
);
|
|
if ($result === false || $result->num_rows < 1) {
|
|
$response->error('XL Vask usage log not found', 404);
|
|
}
|
|
}
|
|
}
|