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>, * 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(); } }