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(); // Automation is an optional final phase. A pending migration or an // off/advisory policy must never interrupt the ordinary XL Vask sync. $this->runScheduledAutomationIfReady(); }; } /** * 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(); }; } /** Enqueue automatic work only after explicit migration and policy activation. */ public function runScheduledAutomationIfReady(): array { try { $migrationStatus = \classes\xlvask_usage_logs_schema_bootstrap::migrationStatus(); if (!(bool)($migrationStatus['ready'] ?? false)) { return []; } $hallIds = $this->configuredHallIds(); if ($hallIds === []) { return []; } $autopilot = new xlvask_autopilot_service(); $capabilities = (new \classes\xlvask_automation_policy_service())->capabilitiesReadOnly(null, null, $hallIds); if (!xlvask_autopilot_service::scheduledExecutionAllowed($migrationStatus, $capabilities)) { return []; } $autopilot->createRun(['mode' => 'execute'], null, $hallIds); return $autopilot->processQueuedRuns(3); } catch (\Throwable) { // Fail closed for automation while preserving the completed ordinary sync. return []; } } /** Drain the durable autopilot queue without running the hourly upstream synchronization. */ public function processAutopilotQueue(int $limit = 3): array { $xlvask = new \classes\xlvask(); $xlvask->requireModuleEnabled(); if (!$xlvask->config->synchronization_enabled->isTrue()) { return []; } if (!(bool)(\classes\xlvask_usage_logs_schema_bootstrap::migrationStatus()['ready'] ?? false)) { return []; } // Off/advisory cannot contain execute runs because createRun is server-gated. // Explicit dry-run/replay evidence may still drain in advisory mode. return (new xlvask_autopilot_service())->processQueuedRuns($limit); } /** 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 } /** * Usage synchronization is intentionally disabled until the XL Vask integration is completed. */ public function runSyncUsage(?string $dateFrom = null, ?string $dateTo = null): void { unset($dateFrom, $dateTo); } 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(); if (!(bool)(\classes\xlvask_usage_logs_schema_bootstrap::migrationStatus()['ready'] ?? false)) { return; } (new xlvask_autopilot_service())->pruneExpiredData(); } }