## Summary Removes the XLVask autopilot / automation / MiniMax / OpenAI pipeline and the related module config, CLI, cron, and migration scaffolding. The Selvvask view (Superuser -> Fakturaer -> Periode -> Selvvask) is reduced to a single read-only listing of usage logs plus operator-driven ignore / unignore / accept / reject endpoints gated on the `review_xlvask_usage_order` permission. See `inventory/self-serve-inventory.md` for the full surface map. ## Test plan - [x] `vendor/bin/pest --testsuite=Unit` -> **1266 passed**, 1 unrelated pre-existing failure (`BirdControlPlaneActivationTest`, needs `PLENO_REPO_ROOT_FOR_TESTS`). - [x] `php -l` on every modified PHP file -> no syntax errors. - [x] Grep validation -> zero production-code references to removed surfaces (`xlvask_autopilot_service`, `xlvask_automation_service`, `xlvask_automation_policy_service`, `EnsureXLVaskAutomationSchema`, `runScheduledAutomationIfReady`, `processAutopilotQueue`, `MiniMax`, `minimax`, ...). - [ ] Qodana + Tests workflows green on this PR. Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev>
441 lines
17 KiB
PHP
441 lines
17 KiB
PHP
<?php
|
|
|
|
namespace helpers;
|
|
|
|
use Exception;
|
|
use objects\orders_o;
|
|
use objects\plate_scanners_o;
|
|
use objects\users_o;
|
|
use objects\xlvask_customers_o;
|
|
use objects\xlvask_potential_order_matches_o;
|
|
use objects\xlvask_usage_logs_o;
|
|
use objects\xlvask_vehicles_o;
|
|
|
|
class xlvask_tasks
|
|
{
|
|
public function __construct()
|
|
{
|
|
// Define the error messages if they are not already defined
|
|
/**
|
|
* if (!defined("ERROR_MESSAGE_ECONOMIC_INVOICE_NOT_FOUND")) {
|
|
* define("ERROR_MESSAGE_ECONOMIC_INVOICE_NOT_FOUND", 'ECONOMIC_INVOICE_NOT_FOUND');
|
|
* }
|
|
* if (!defined("ERROR_MESSAGE_ECONOMIC_INVOICE_DRAFT_NOT_FOUND")) {
|
|
* define("ERROR_MESSAGE_ECONOMIC_INVOICE_DRAFT_NOT_FOUND", 'ECONOMIC_INVOICE_DRAFT_NOT_FOUND');
|
|
* }
|
|
*/
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
public function runCronTasks(): void
|
|
{
|
|
// Define the XL Vask object
|
|
$xlvask = new \classes\xlvask();
|
|
// Require the module to be enabled
|
|
$xlvask->requireModuleEnabled();
|
|
// Run the synchronization tasks
|
|
if ($xlvask->config->synchronization_enabled->isTrue()) {
|
|
// Synchronize users, this will keep the users (XL Vask customer objects) in cache up to date
|
|
$this->runImportTasks();
|
|
$this->runSyncUsers(true);
|
|
$this->runSyncUsage();
|
|
$this->runSyncVehicles();
|
|
$this->runCleanupTasks();
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Run the import tasks
|
|
* This task is used to import the XL Vask vehicles, users, usage logs, and other necessary data into the system.
|
|
* It will import the necessary classes and objects to be used in the system.
|
|
* @return void
|
|
* @throws Exception If the task fails
|
|
*/
|
|
public function runImportTasks(): void
|
|
{
|
|
// Define the XL Vask object
|
|
$xlvask = new \classes\xlvask();
|
|
// Require the module to be enabled
|
|
$xlvask->requireModuleEnabled();
|
|
// Run the synchronization tasks
|
|
if ($xlvask->config->synchronization_enabled->isTrue()) {
|
|
// Synchronize users, this will keep the database tables updated:
|
|
// - xlvask_customers
|
|
// - xlvask_vehicles
|
|
// - xlvask_usage_logs
|
|
(new xlvask_customers_o())->importCustomers();
|
|
(new xlvask_vehicles_o())->importVehicles();
|
|
};
|
|
}
|
|
|
|
/** Hall GUIDs are configuration, not derived from already-imported usage rows. */
|
|
private function configuredHallIds(): array
|
|
{
|
|
global $db;
|
|
(new plate_scanners_o())->structure();
|
|
plate_scanners_o::ensureHallIdSchemaForMutation();
|
|
$result = $db->query(
|
|
"SELECT DISTINCT HallId FROM plate_scanners
|
|
WHERE HallId IS NOT NULL AND TRIM(HallId) <> '' AND deleted_at IS NULL"
|
|
);
|
|
if ($result === false) {
|
|
return [];
|
|
}
|
|
return array_values(array_unique(array_filter(array_map(
|
|
static fn(array $row): string => trim((string)($row['HallId'] ?? '')),
|
|
$db->fetch_all($result)
|
|
), static fn(string $id): bool => $id !== '' && strlen($id) <= 191)));
|
|
}
|
|
|
|
/**
|
|
* Run the synchronize users task
|
|
* This task fetches the users from XL Vask, and synchronizes them with this system.
|
|
* This includes:
|
|
* - Fetching the users from XL Vask
|
|
* - Checking if the users exist in this system
|
|
* - Add objects to the cache, when a matching user is found.
|
|
* @return null|array['customers'] The customers fetched from XL Vask, null if synchronization is not enabled
|
|
* @throws Exception If the task fails
|
|
*/
|
|
public function runSyncUsers(bool $isSilent = false): array|null
|
|
{
|
|
// Define the XL Vask object
|
|
$xlvask = new \classes\xlvask();
|
|
// Require the module to be enabled
|
|
$xlvask->requireModuleEnabled();
|
|
// Check if synchronization is enabled
|
|
if (!$xlvask->config->synchronization_enabled->isTrue()) {
|
|
if (!$isSilent) {
|
|
throw new Exception('XL Vask synchronization is not enabled.');
|
|
}
|
|
return null; // Synchronization is not enabled, do nothing
|
|
}
|
|
// Get the users from XL Vask
|
|
$customer_objects = $this->fetchCustomers();
|
|
//echo 'Customers fetched from XL Vask: ' . count($customer_objects) . PHP_EOL;
|
|
// Return the customers with an external ID
|
|
$customers = [
|
|
'with_external_id' => [...self::filterCustomersWithExternalId($customer_objects)],
|
|
'without_external_id' => [...self::filterCustomersWithoutExternalId($customer_objects)],
|
|
'all' => $customer_objects,
|
|
];
|
|
|
|
return self::synchronize_customers($customers['with_external_id']);
|
|
}
|
|
|
|
/**
|
|
* Get the customers registered in XL Vask
|
|
* This method fetches the customers from XL Vask and returns them as an array of customer objects.
|
|
* @return array[xlvask_customer] An array of customer objects fetched from XL Vask
|
|
* @throws Exception If the task fails
|
|
*/
|
|
public function fetchCustomers(): array
|
|
{
|
|
// Define the XL Vask object
|
|
$xlvask = new \classes\xlvask();
|
|
// Require the module to be enabled
|
|
$xlvask->requireModuleEnabled();
|
|
// Get the users from XL Vask
|
|
$customer_objects_raw = $xlvask->getCustomers();
|
|
// Parse the users into customer objects
|
|
return array_map(function ($customer) use ($xlvask) {
|
|
/** @var xlvask_customer $tmp */
|
|
$tmp = new $xlvask->helpers->xlvask_customer();
|
|
foreach ( $customer as $key => $value ) {
|
|
// Assign the properties shared by the xlvask_customer class.
|
|
if (property_exists($tmp, $key)) {
|
|
$tmp->{$key} = $value;
|
|
}
|
|
}
|
|
return $tmp; // Return the xlvask_customer object
|
|
}, $customer_objects_raw);
|
|
}
|
|
|
|
/**
|
|
* Get the customers with an external ID
|
|
* @param array $customers Customers to filter
|
|
* @return array The customers with an external ID
|
|
*/
|
|
public static function filterCustomersWithExternalId(array $customers): array
|
|
{
|
|
return array_filter($customers, function ($customer) {
|
|
/** @var xlvask_customer $customer */
|
|
return !empty($customer->externId);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get the customers without an external ID
|
|
* @param array $customers Customers to filter
|
|
* @return array The customers without an external ID
|
|
*/
|
|
public static function filterCustomersWithoutExternalId(array $customers): array
|
|
{
|
|
return array_filter($customers, function ($customer) {
|
|
/** @var xlvask_customer $customer */
|
|
return empty($customer->externId);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Synchronize customers with external IDs
|
|
* This method synchronizes the customers with external IDs from XL Vask.
|
|
* It checks if the customers exist in this system, creates them if they do not exist,
|
|
* and adds/removes the associated external ID to the users in this system.
|
|
* @param array $with_external_id Customers with external IDs to synchronize
|
|
* @return array|null The synchronized customers, null if synchronization is not enabled
|
|
* @throws Exception
|
|
*/
|
|
private static function synchronize_customers(array $with_external_id): array|null
|
|
{
|
|
// Define the XL Vask object
|
|
$xlvask = new \classes\xlvask();
|
|
// Require the module to be enabled
|
|
$xlvask->requireModuleEnabled();
|
|
// Check if synchronization is enabled
|
|
if (!$xlvask->config->synchronization_enabled->isTrue()) {
|
|
return null; // Synchronization is not enabled, do nothing
|
|
}
|
|
|
|
// Get a list of customer numbers that we need to synchronize
|
|
$customer_numbers = array_map(function ($customer) {
|
|
/** @var xlvask_customer $customer */
|
|
return $customer->externId; // The customer number is stored in the externId property in XL Vask
|
|
}, $with_external_id);
|
|
|
|
// Get all the user ids associated with the customer numbers
|
|
$users = (new users_o())->getUsersByCustomerNumbers($customer_numbers, true);
|
|
|
|
$cache = $xlvask->getCache();
|
|
|
|
/** @var xlvask_customer $customer */
|
|
foreach ( $with_external_id as $customer ) {
|
|
// Check if the customer exists in this system
|
|
if (isset($users[$customer->externId])) {
|
|
// The user exists, update the user with the external ID
|
|
/** @var users_o $user */
|
|
$user = $users[$customer->externId];
|
|
// Cache the customer
|
|
$cache->setCustomerCache((int)$customer->externId, $customer);
|
|
}
|
|
}
|
|
return $with_external_id; // Return the synchronized customers
|
|
}
|
|
|
|
/**
|
|
* Synchronize XL Vask usage logs.
|
|
*
|
|
* Pulls finished washes from XL Vask for the given window and persists them
|
|
* via the revision-aware {@see xlvask_usage_logs_o::importUsageLogsWithSummary()}.
|
|
* Potential order matches are also recorded so the linked-order UI has the
|
|
* data it needs to offer accept/deny actions.
|
|
*
|
|
* @param string|null $dateFrom Optional import start date or date-time modifier. Defaults to the last 7 days.
|
|
* @param string|null $dateTo Optional inclusive import end date.
|
|
* @return array{
|
|
* fetched:int,
|
|
* new:int,
|
|
* updated:int,
|
|
* unchanged:int,
|
|
* invalid:int,
|
|
* errors:array<int,array<string,string>>,
|
|
* linked:int,
|
|
* orders_created:int
|
|
* } Summary of the import run.
|
|
* @throws Exception If the XL Vask module is not enabled.
|
|
*/
|
|
public function runSyncUsage(?string $dateFrom = null, ?string $dateTo = null): array
|
|
{
|
|
$xlvask = new \classes\xlvask();
|
|
$xlvask->requireModuleEnabled();
|
|
if (!$xlvask->config->synchronization_enabled->isTrue()) {
|
|
return [
|
|
'fetched' => 0, 'new' => 0, 'updated' => 0, 'unchanged' => 0,
|
|
'invalid' => 0, 'errors' => [], 'linked' => 0, 'orders_created' => 0,
|
|
];
|
|
}
|
|
|
|
$summary = (new xlvask_usage_logs_o())->importUsageLogsWithSummary($dateFrom, $dateTo);
|
|
$linkSummary = $this->linkImportedUsageLogsToOrders($dateFrom, $dateTo);
|
|
|
|
return [
|
|
...$summary,
|
|
'linked' => $linkSummary['linked'],
|
|
'orders_created' => $linkSummary['orders_created'],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Find imported XL Vask usage logs that look like they belong to an existing order
|
|
* (same registration, department, and a window around the wash time) and record
|
|
* them as potential order matches so they show up in the linking UI.
|
|
*
|
|
* Optionally creates the missing order when automatic order creation is enabled,
|
|
* the customer has an external ID, and the wash qualifies for automatic continuance.
|
|
*
|
|
* @return array{linked:int, orders_created:int}
|
|
*/
|
|
private function linkImportedUsageLogsToOrders(?string $dateFrom, ?string $dateTo): array
|
|
{
|
|
global $db;
|
|
$xlvask = new \classes\xlvask();
|
|
$matches = new xlvask_potential_order_matches_o();
|
|
$linked = 0;
|
|
|
|
$dateFromSql = $dateFrom !== null && $dateFrom !== ''
|
|
? "'" . $db->escape_string(xlvask_usage_logs_o::formatImportDateFrom($dateFrom)) . "'"
|
|
: 'DATE_SUB(NOW(), INTERVAL 7 DAY)';
|
|
$dateToSql = $dateTo !== null && $dateTo !== ''
|
|
? "'" . $db->escape_string((string)$dateTo) . " 23:59:59'"
|
|
: 'NOW()';
|
|
|
|
$sql = "SELECT * FROM xlvask_usage_logs
|
|
WHERE StartTime >= {$dateFromSql}
|
|
AND StartTime <= {$dateToSql}
|
|
AND FinishStatus = 1
|
|
AND WashId IS NOT NULL AND TRIM(WashId) <> ''
|
|
ORDER BY id ASC";
|
|
$result = $db->query($sql);
|
|
if ($result === false || $result->num_rows === 0) {
|
|
return ['linked' => 0, 'orders_created' => 0];
|
|
}
|
|
$rows = $db->fetch_all($result);
|
|
|
|
foreach ($rows as $row) {
|
|
$log = (new $xlvask->helpers->xlvask_usage_log())->setProperties($row);
|
|
$washId = (string)$log->WashId;
|
|
if ($washId === '' || $log->hasDefaultCustomer() || !$log->hasExternalId()) {
|
|
continue;
|
|
}
|
|
if ($matches->doesWashPotentialOrderMatchExist($washId)) {
|
|
continue; // already linked or explicitly ignored
|
|
}
|
|
|
|
$existingOrder = $log->getPotentialOrder();
|
|
if ($existingOrder !== null && (int)$existingOrder->id > 0) {
|
|
$matches->add(
|
|
$washId,
|
|
(int)$existingOrder->id,
|
|
(string)$log->CustomerId,
|
|
(int)$log->CustomerId,
|
|
(int)$log->getDepartment()->id,
|
|
);
|
|
$linked++;
|
|
}
|
|
}
|
|
|
|
return ['linked' => $linked, 'orders_created' => 0];
|
|
}
|
|
|
|
private static function formatUsageLogs(array $getUsageLog): array
|
|
{
|
|
//print_r($getUsageLog);
|
|
// Format the usage logs to a more readable format
|
|
// This is a placeholder, you might want to implement a method to format the logs
|
|
return array_map(/**
|
|
* @throws Exception
|
|
*/ function ($log) {
|
|
$xlvask = new \classes\xlvask();
|
|
return (new $xlvask->helpers->xlvask_usage_log())->setProperties($log);
|
|
}, $getUsageLog);
|
|
}
|
|
|
|
private static function sortLogsByDate(array $logs): array
|
|
{
|
|
// Sort the logs by date
|
|
usort($logs, function ($a, $b) {
|
|
/** @var xlvask_usage_log $a */
|
|
/** @var xlvask_usage_log $b */
|
|
return strtotime($a->getFormattedDate()) - strtotime($b->getFormattedDate());
|
|
});
|
|
return $logs; // Return the sorted logs
|
|
}
|
|
|
|
/**
|
|
* @throws Exception If the log is not completed, or if the customer does not have an externId, or if the customer does not exist in this system.
|
|
*/
|
|
public function createOrderFromWash(xlvask_usage_log $log, xlvask_customer|\stdClass $customer): ?orders_o
|
|
{
|
|
// Make sure the XL Vask module is enabled
|
|
$xlvask = new \classes\xlvask();
|
|
$xlvask->requireModuleEnabled();
|
|
|
|
// Check if the log is completed
|
|
if (!$log->isCompleted()) {
|
|
throw new Exception('The log ( ID: ' . $log->WashId . ' ) is not completed, cannot create an order from it.');
|
|
}
|
|
// Check if the customer has an externId
|
|
if (empty($customer->externId)) {
|
|
throw new Exception('The customer ( ID: ' . $customer->customerId . ' ) does not have an externId, cannot create an order from it.');
|
|
}
|
|
// Check if the customer exists in this system
|
|
if (!$tmp_user = $customer->getUser()) {
|
|
throw new Exception('The customer ( ID: ' . $customer->customerId . ' ) does not exist in this system, cannot create an order from it.');
|
|
}
|
|
// Create a new order object
|
|
$order = new orders_o();
|
|
$order->addXLVaskOrder(
|
|
$tmp_user,
|
|
$log,
|
|
);
|
|
// Return the order object
|
|
return $order;
|
|
}
|
|
|
|
/**
|
|
* Synchronize vehicles with XL Vask
|
|
* This method is used to synchronize the vehicles with XL Vask.
|
|
* It fetches the vehicles from XL Vask and adds them to the cache.
|
|
* This includes:
|
|
* - Fetching the vehicles from XL Vask
|
|
* - Caching the vehicles in this system
|
|
* @param bool $isSilent If true, will not throw an exception if synchronization is not enabled
|
|
* @return null|array
|
|
* @throws Exception If the task fails or if synchronization is not enabled
|
|
* @see xlvask_vehicle
|
|
* @see xlvask_cache::getVehicleCache()
|
|
* @see xlvask_cache::setVehicleCache()
|
|
* @see xlvask_cache::isVehicleCached()
|
|
*/
|
|
public function runSyncVehicles(bool $isSilent = false): ?array
|
|
{
|
|
// Define the XL Vask object
|
|
$xlvask = new \classes\xlvask();
|
|
// Require the module to be enabled
|
|
$xlvask->requireModuleEnabled();
|
|
// Check if synchronization is enabled
|
|
if (!$xlvask->config->synchronization_enabled->isTrue()) {
|
|
if (!$isSilent) {
|
|
throw new Exception('XL Vask synchronization is not enabled.');
|
|
}
|
|
return null; // Synchronization is not enabled, do nothing
|
|
}
|
|
// Get the vehicles from XL Vask
|
|
/** @var xlvask_vehicles $vehicles */
|
|
$vehicles = new $xlvask->helpers->xlvask_vehicles();
|
|
$vehicles = $vehicles->getVehicles();
|
|
// Cache the vehicles
|
|
foreach ( $vehicles as $vehicle ) {
|
|
/** @var xlvask_vehicle $vehicle */
|
|
$xlvask->getCache()->setVehicleCache($vehicle->registrationNumber, $vehicle);
|
|
}
|
|
return $vehicles; // Return the vehicles
|
|
}
|
|
|
|
/**
|
|
* 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();
|
|
}
|
|
}
|