diff --git a/services/nginx/app/modules/xlvask/helpers/xlvask_parser_2_b_rstevask.php b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_2_b_rstevask.php new file mode 100644 index 00000000..20aa2745 --- /dev/null +++ b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_2_b_rstevask.php @@ -0,0 +1,26 @@ +setup('2_b_rstevask'); + } + + + /** + * @inheritDoc + * @throws Exception + */ + protected function parse(xlvask_wash_item $wash_item, xlvask_usage_log $xlvask_usage_log): products_o + { + return (new products_o())->select(63); // 2 Børstevask product ID + } +} \ No newline at end of file diff --git a/services/nginx/app/modules/xlvask/helpers/xlvask_parser_3_b_rstevask.php b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_3_b_rstevask.php new file mode 100644 index 00000000..21b2f4b8 --- /dev/null +++ b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_3_b_rstevask.php @@ -0,0 +1,26 @@ +setup('3_b_rstevask'); + } + + + /** + * @inheritDoc + * @throws Exception + */ + protected function parse(xlvask_wash_item $wash_item, xlvask_usage_log $xlvask_usage_log): products_o + { + return (new products_o())->select(64); // Irrelevant product ID + } +} \ No newline at end of file diff --git a/services/nginx/app/modules/xlvask/helpers/xlvask_parser_eu_spejl.php b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_eu_spejl.php new file mode 100644 index 00000000..c1e81278 --- /dev/null +++ b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_eu_spejl.php @@ -0,0 +1,26 @@ +setup('EU spejl'); + } + + + /** + * @inheritDoc + * @throws Exception + */ + protected function parse(xlvask_wash_item $wash_item, xlvask_usage_log $xlvask_usage_log): products_o + { + return (new products_o())->select(64); // Other irrelevant product ID + } +} \ No newline at end of file diff --git a/services/nginx/app/modules/xlvask/helpers/xlvask_parser_halleje.php b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_halleje.php new file mode 100644 index 00000000..c0c410ea --- /dev/null +++ b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_halleje.php @@ -0,0 +1,26 @@ +setup('Halleje'); + } + + + /** + * @inheritDoc + * @throws Exception + */ + protected function parse(xlvask_wash_item $wash_item, xlvask_usage_log $xlvask_usage_log): products_o + { + return (new products_o())->select(62); // Halleje product ID + } +} \ No newline at end of file diff --git a/services/nginx/app/modules/xlvask/helpers/xlvask_parser_ht_chassis.php b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_ht_chassis.php new file mode 100644 index 00000000..e7f336ce --- /dev/null +++ b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_ht_chassis.php @@ -0,0 +1,26 @@ +setup('HT chassis'); + } + + + /** + * @inheritDoc + * @throws Exception + */ + protected function parse(xlvask_wash_item $wash_item, xlvask_usage_log $xlvask_usage_log): products_o + { + return (new products_o())->select(64); // Irrelevant product ID + } +} \ No newline at end of file diff --git a/services/nginx/app/modules/xlvask/helpers/xlvask_parser_ht_osc_sider_verst.php b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_ht_osc_sider_verst.php new file mode 100644 index 00000000..8489a7ad --- /dev/null +++ b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_ht_osc_sider_verst.php @@ -0,0 +1,26 @@ +setup('ht_osc_sider_verst'); + } + + + /** + * @inheritDoc + * @throws Exception + */ + protected function parse(xlvask_wash_item $wash_item, xlvask_usage_log $xlvask_usage_log): products_o + { + return (new products_o())->select(64); // Irrelevant product ID + } +} \ No newline at end of file diff --git a/services/nginx/app/modules/xlvask/helpers/xlvask_parser_ht_sider.php b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_ht_sider.php new file mode 100644 index 00000000..f81ee75e --- /dev/null +++ b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_ht_sider.php @@ -0,0 +1,26 @@ +setup('HT sider'); + } + + + /** + * @inheritDoc + * @throws Exception + */ + protected function parse(xlvask_wash_item $wash_item, xlvask_usage_log $xlvask_usage_log): products_o + { + return (new products_o())->select(64); // Other irrelevant product ID + } +} \ No newline at end of file diff --git a/services/nginx/app/modules/xlvask/helpers/xlvask_parser_ikke_ht_dysebom_bag.php b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_ikke_ht_dysebom_bag.php new file mode 100644 index 00000000..7616130d --- /dev/null +++ b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_ikke_ht_dysebom_bag.php @@ -0,0 +1,26 @@ +setup('Ikke HT dysebom bag'); + } + + + /** + * @inheritDoc + * @throws Exception + */ + protected function parse(xlvask_wash_item $wash_item, xlvask_usage_log $xlvask_usage_log): products_o + { + return (new products_o())->select(64); // Other irrelevant product ID + } +} \ No newline at end of file diff --git a/services/nginx/app/modules/xlvask/helpers/xlvask_parser_ikke_ht_dysebom_p_front.php b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_ikke_ht_dysebom_p_front.php new file mode 100644 index 00000000..bbd630d2 --- /dev/null +++ b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_ikke_ht_dysebom_p_front.php @@ -0,0 +1,26 @@ +setup('Ikke HT dysebom p\u00e5 front'); + } + + + /** + * @inheritDoc + * @throws Exception + */ + protected function parse(xlvask_wash_item $wash_item, xlvask_usage_log $xlvask_usage_log): products_o + { + return (new products_o())->select(64); // Other irrelevant product ID + } +} \ No newline at end of file diff --git a/services/nginx/app/modules/xlvask/helpers/xlvask_parser_lift.php b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_lift.php new file mode 100644 index 00000000..0e28966a --- /dev/null +++ b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_lift.php @@ -0,0 +1,26 @@ +setup('lift'); + } + + + /** + * @inheritDoc + * @throws Exception + */ + protected function parse(xlvask_wash_item $wash_item, xlvask_usage_log $xlvask_usage_log): products_o + { + return (new products_o())->select(64); // Irrelevant product ID for lift + } +} \ No newline at end of file diff --git a/services/nginx/app/modules/xlvask/helpers/xlvask_parser_s_be_dysebom.php b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_s_be_dysebom.php new file mode 100644 index 00000000..cbf0f707 --- /dev/null +++ b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_s_be_dysebom.php @@ -0,0 +1,26 @@ +setup('s_be_dysebom'); + } + + + /** + * @inheritDoc + * @throws Exception + */ + protected function parse(xlvask_wash_item $wash_item, xlvask_usage_log $xlvask_usage_log): products_o + { + return (new products_o())->select(64); // Other irrelevant product ID + } +} \ No newline at end of file diff --git a/services/nginx/app/modules/xlvask/helpers/xlvask_parser_skylning_med_ro.php b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_skylning_med_ro.php new file mode 100644 index 00000000..3a3ffac3 --- /dev/null +++ b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_skylning_med_ro.php @@ -0,0 +1,44 @@ +setup('Skylning med RO'); // This is the name of Spot free product(s) + } + + + /** + * @inheritDoc + * @throws Exception + */ + protected function parse(xlvask_wash_item $wash_item, xlvask_usage_log $xlvask_usage_log): products_o + { + // This parser is used for the "Skylning med RO" product, which is a spot-free rinse. + // There are two different products that can be used for this, depending on the vehicle type. + // Spot Free- Varevogn (23) + // Spot Free- Lastbil (24) + // This means that we need to check the vehicle type and return the appropriate product. + $primary_product = $xlvask_usage_log->getPrimaryItem()->getProduct($xlvask_usage_log); + $primary_product_id = $primary_product->id; + // Get the available addons for the primary product + $product_options = new product_options_o(); + if ($product_options->isOptionAllowedOnType(23, $primary_product_id)) { + // If the primary product is a varevogn (van), return the Spot Free- Varevogn product + return (new products_o())->select(23); // Spot Free- Varevogn product ID + } elseif ($product_options->isOptionAllowedOnType(24, $primary_product_id)) { + // If the primary product is a lastbil (truck), return the Spot Free- Lastbil product + return (new products_o())->select(24); // Spot Free- Lastbil product ID + } + // Throw an exception if the vehicle type is not supported + throw new Exception('Unsupported vehicle type for Skylning med RO product.'); + } +} \ No newline at end of file diff --git a/services/nginx/app/modules/xlvask/helpers/xlvask_parser_stor_bil.php b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_stor_bil.php new file mode 100644 index 00000000..160843cd --- /dev/null +++ b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_stor_bil.php @@ -0,0 +1,229 @@ +setup('Stor bil'); + } + + + /** + * @inheritDoc + * @throws \Exception + */ + protected function parse(xlvask_wash_item $wash_item, xlvask_usage_log $xlvask_usage_log): products_o + { + //echo 'Attempting to parse Stor Bil product: ' . $this->OriginalProductName . PHP_EOL; + //echo ' Step 1: Validating wash item and usage log.' . PHP_EOL; + $registrationNumber = $xlvask_usage_log->RegistrationNumber; + $vehicleType = $xlvask_usage_log->VehicleType; // E.g. "Bus/autocamper, M" + /** + * All the vehicle types that this parser supports. + * Copied from XL Vask's vehicle types @ 10:32 10/06/2025 + * ** + * Anlægsmaskine + * Bus/autocamper, L + * Bus/autocamper, M + * Bus/autocamper, S + * Containerbil + * Containerbil med hænger + * Containertræk + * Entreprenør forvogn/trækker + * Entreprenør sættevogn + * Forvogn med hænger + * Forvogn med hænger, autotransport + * Forvogn med krogehejs + * Forvogn, L + * Forvogn, lave sider + * Forvogn, M + * Forvogn, S + * Forvogn, skraldebil + * Kassevogn/Varevogn, L + * Kassevogn/Varevogn, S + * Kun trailer + * Landbrugstraktor + * Minibus + * Personbil + * Pickup + * Sættevognstræk + * Sættevognstræk, maskintrailer + * Tankvogn + * Tankvogn, Slamsuger + * Tankvognstræk + * Tankvognstræk, silobil + * Trækker + * Tømmerbil + */ + + $vehicleTypes = [ + // Formats for vehicle types + // 'exact match' => ['Vehicle Type 1', 'Vehicle Type 2'], - Matches when the vehicle type is exactly one of these. + // 'contains' => ['Vehicle Type 3', 'Vehicle Type 4'], - Matches when the vehicle type contains any of these substrings. + // 'matchFunction' => function($vehicleType) { return true; } - Matches when the vehicle type passes this function. + // product_id => ['exact match' => ['Vehicle Type 1', 'Vehicle Type 2'], 'contains' => ['Vehicle Type 3', 'Vehicle Type 4'], 'matchFunction' => function] + // Trækker (1) + 1 => [ + 'exact match' => ['Trækker'], + 'contains' => ['Trækker'], + 'matchFunction' => function ($vehicleType) { + return preg_match('/^Trækker$/', $vehicleType); + } + ], + // Trailer (2) + 2 => [ + 'exact match' => ['Kun trailer'], + 'contains' => ['Trailer'], + 'matchFunction' => function ($vehicleType) { + return preg_match('/^Kun trailer$/', $vehicleType); + } + ], + // Sættevognstræk (3) + 3 => [ + 'exact match' => ['Sættevognstræk'], + 'contains' => ['Sættevognstræk'], + 'matchFunction' => function ($vehicleType) { + return preg_match('/^Sættevognstræk$/', $vehicleType); + } + ], + // Dolly (4) + 4 => [ + 'exact match' => ['Dolly'], + 'contains' => ['Dolly'], + 'matchFunction' => function ($vehicleType) { + return preg_match('/^Dolly$/', $vehicleType); + } + ], + // Forvogn (5) + 5 => [ + 'exact match' => ['Forvogn, L', 'Forvogn, M', 'Forvogn, S', 'Forvogn, lave sider', 'Forvogn med krogehejs', 'Forvogn, skraldebil'], + 'contains' => ['Forvogn'], + 'matchFunction' => function ($vehicleType) { + return preg_match('/^Forvogn/', $vehicleType); + } + ], + // Forvogn med hænger (6) + 6 => [ + 'exact match' => ['Forvogn med hænger', 'Forvogn med hænger, autotransport'], + 'contains' => ['Forvogn med hænger'], + 'matchFunction' => function ($vehicleType) { + return preg_match('/^Forvogn med hænger/', $vehicleType); + } + ], + // Tankvogn (7) + 7 => [ + 'exact match' => ['Tankvogn', 'Tankvogn, Slamsuger'], + 'contains' => ['Tankvogn'], + 'matchFunction' => function ($vehicleType) { + return preg_match('/^Tankvogn/', $vehicleType); + } + ], + // Tankvogn med hænger (8) + 8 => [ + 'exact match' => ['Tankvognstræk', 'Tankvognstræk, silobil'], + 'contains' => ['Tankvognstræk'], + 'matchFunction' => function ($vehicleType) { + return preg_match('/^Tankvognstræk/', $vehicleType); + } + ], + // Tankvognstræk (9) + 9 => [ + 'exact match' => ['Tankvognstræk', 'Tankvognstræk, silobil'], + 'contains' => ['Tankvognstræk'], + 'matchFunction' => function ($vehicleType) { + return preg_match('/^Tankvognstræk/', $vehicleType); + } + ], + // Kassevogn/Varevogn (15) + 15 => [ + 'exact match' => ['Kassevogn/Varevogn, L', 'Kassevogn/Varevogn, S'], + 'contains' => ['Kassevogn', 'Varevogn'], + 'matchFunction' => function ($vehicleType) { + return preg_match('/^Kassevogn\/Varevogn/', $vehicleType); + } + ], + // Campingvogn/Autocamper (16) + 16 => [ + 'exact match' => ['Bus/autocamper, L', 'Bus/autocamper, M', 'Bus/autocamper, S'], + 'contains' => ['Bus', 'Autocamper'], + 'matchFunction' => function ($vehicleType) { + return preg_match('/^Bus\/autocamper/', $vehicleType); + } + ], + // Bus (17) + 17 => [ + 'exact match' => ['Bus/autocamper, L', 'Bus/autocamper, M', 'Bus/autocamper, S'], + 'contains' => ['Bus'], + 'matchFunction' => function ($vehicleType) { + return preg_match('/^Bus\/autocamper/', $vehicleType); + } + ], + // Entreprenør forvogn (18) + 18 => [ + 'exact match' => ['Entreprenør forvogn/trækker'], + 'contains' => ['Entreprenør forvogn', 'Trækker'], + 'matchFunction' => function ($vehicleType) { + return preg_match('/^Entreprenør forvogn/', $vehicleType); + } + ], + // Entreprenør forvogn med kærre (19) + 19 => [ + 'exact match' => ['Entreprenør sættevogn'], + 'contains' => ['Entreprenør sættevogn'], + 'matchFunction' => function ($vehicleType) { + return preg_match('/^Entreprenør sættevogn$/', $vehicleType); + } + ], + // Entreprenør sættevogn (20) + 20 => [ + 'exact match' => ['Entreprenør sættevogn'], + 'contains' => ['Entreprenør sættevogn'], + 'matchFunction' => function ($vehicleType) { + return preg_match('/^Entreprenør sættevogn$/', $vehicleType); + } + ], + ]; + + // Check if the vehicle type matches any of the defined vehicle types + $isValidVehicleType = false; + $productId = null; // Initialize product_id to null + foreach ( $vehicleTypes as $product_id => $conditions ) { + if (in_array($vehicleType, $conditions['exact match'], true)) { + $isValidVehicleType = true; + $productId = $product_id; // Set the product_id if an exact match is found + break; + } + foreach ( $conditions['contains'] as $substring ) { + if (strpos($vehicleType, $substring) !== false) { + $isValidVehicleType = true; + $productId = $product_id; // Set the product_id if a substring match is found + break 2; // Break out of both loops + } + } + if (isset($conditions['matchFunction']) && is_callable($conditions['matchFunction']) && $conditions['matchFunction']($vehicleType)) { + $isValidVehicleType = true; + $productId = $product_id; // Set the product_id if the match function returns true + break; + } + } + if (!$isValidVehicleType) { + //echo 'Step 2: Vehicle type does not match any of the defined vehicle types.' . PHP_EOL; + throw new \Exception('Vehicle type does not match any of the defined vehicle types.'); + } + //echo ' Found matching vehicle type: ' . $vehicleType . PHP_EOL; + //echo ' Step 3: Vehicle type is valid, proceeding with product creation.' . PHP_EOL; + + + // Check if the vehicle + //print_r($xlvask_usage_log); + + // Create a new product object + return (new products_o())->select((int)$productId); + } +} \ No newline at end of file diff --git a/services/nginx/app/modules/xlvask/helpers/xlvask_parser_undervognskylning.php b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_undervognskylning.php new file mode 100644 index 00000000..86bc461f --- /dev/null +++ b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_undervognskylning.php @@ -0,0 +1,36 @@ +setup('Undervognskylning'); // This is the name of Spot free product(s) + } + + + /** + * @inheritDoc + * @throws Exception + */ + protected function parse(xlvask_wash_item $wash_item, xlvask_usage_log $xlvask_usage_log): products_o + { + // This means that we need to check the vehicle type and return the appropriate product. + $primary_product = $xlvask_usage_log->getPrimaryItem()->getProduct($xlvask_usage_log); + $primary_product_id = $primary_product->id; + // Get the available addons for the primary product + $product_options = new product_options_o(); + if ($product_options->isOptionAllowedOnType(21, $primary_product_id)) { + return (new products_o())->select(21); // Undervognskylning product ID + } + // Throw an exception if the vehicle type is not supported + throw new Exception('Unsupported vehicle type for Undervognskylning'); + } +} \ No newline at end of file diff --git a/services/nginx/app/modules/xlvask/helpers/xlvask_product_parser.php b/services/nginx/app/modules/xlvask/helpers/xlvask_product_parser.php new file mode 100644 index 00000000..1ff101c7 --- /dev/null +++ b/services/nginx/app/modules/xlvask/helpers/xlvask_product_parser.php @@ -0,0 +1,23 @@ +OriginalProductName = $OriginalProductName; + } +} \ No newline at end of file diff --git a/services/nginx/app/modules/xlvask/helpers/xlvask_product_parser_t.php b/services/nginx/app/modules/xlvask/helpers/xlvask_product_parser_t.php new file mode 100644 index 00000000..c77b8d62 --- /dev/null +++ b/services/nginx/app/modules/xlvask/helpers/xlvask_product_parser_t.php @@ -0,0 +1,44 @@ +OriginalProductName)) { + throw new Exception('The Original Product Name is not set. Please provide a valid product name.'); + } + // Make sure this is the correct parser + //if ($this->OriginalProductName !== $wash_item->OriginalProductName) { + // throw new Exception('The Original Product Name does not match the parser\'s Original Product Name. Expected: ' . $this->OriginalProductName . ', got: ' . $wash_item->OriginalProductName); + //} + // All checks passed, parse the product + return $this->parse(...func_get_args()); + } + + /** + * The actual parsing logic. + * This method should be implemented in the class that uses this trait. + * @param xlvask_wash_item $wash_item + * @param xlvask_usage_log $xlvask_usage_log + * @return products_o + */ + abstract protected function parse(xlvask_wash_item $wash_item, xlvask_usage_log $xlvask_usage_log): products_o; + + +} \ No newline at end of file diff --git a/services/nginx/app/modules/xlvask/helpers/xlvask_tasks.php b/services/nginx/app/modules/xlvask/helpers/xlvask_tasks.php index df79bbcc..7304e954 100644 --- a/services/nginx/app/modules/xlvask/helpers/xlvask_tasks.php +++ b/services/nginx/app/modules/xlvask/helpers/xlvask_tasks.php @@ -163,4 +163,107 @@ class xlvask_tasks } return $with_external_id; // Return the synchronized customers } + + /** + * Run the sync usage task + * This task is used to synchronize the usage of the XL Vask module. + * It fetches the usage data from XL Vask and adds it to the applicable users. + * The time format XL vask uses is "2025-05-01T00:00:00.000" - this is the ISO 8601 format. + * @return array|null + * @throws Exception + */ + public function runSyncUsage(): 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()) { + throw new Exception('XL Vask synchronization is not enabled.'); + } + // Set the date from which to fetch the usage data + // Since this is a cron task, we will fetch the usage data from the last 24 hours + $minutes = 24 * 60; // 24 hours in minutes + $dateFrom = date('Y-m-d\TH:i:s.000', strtotime('-' . $minutes . ' minutes')); + echo $dateFrom; + + + // If there are no customers, get them. TODO: Remove this in production, this is just for testing + $xlvask->getTasks()->runSyncUsers(true); + + // Get the cached customers + $customers = $xlvask->getCache()->getAllCachedCustomers(); + + // Loop through the customers and check if there's any new usage data + foreach ( $customers as $customer ) { + /** @var xlvask_customer $customer */ + // Check if the customer has an externId + if (empty($customer->externId)) { + continue; // Skip customers without an externId + } + echo 'Checking customer: ' . $customer->name . ' (' . $customer->externId . ')' . PHP_EOL; + $tmp_usage_logs = self::formatUsageLogs($xlvask->getUsageLog( + $dateFrom, + null, // regNr + null, // vehicleId + $customer->customerId // customerId + )); + foreach ( $tmp_usage_logs as $log ) { + /** @var xlvask_usage_log $log */ + // Check if the wash was completed + if (!$log->isCompleted()) { + echo 'A log is not completed: ' . $log->getFormattedDate() . ' - ' . $log->CustomerId . ' - ' . $log->VehicleId . PHP_EOL; + continue; // Skip logs that are not completed + } + echo PHP_EOL; + echo '### ' . $log->getFormattedDate() . ' - ' . $customer->name . ' (' . $customer->externId . ') - ' . $log->getTotalPrice() . PHP_EOL; + echo '# License Plate: ' . $log->RegistrationNumber . PHP_EOL; + echo '# Vehicle ID: ' . $log->VehicleId . PHP_EOL; + echo '# Wash ID: ' . $log->WashId . PHP_EOL; + echo '# Hall: ' . $log->Hall . PHP_EOL; + echo '# Hall ID: ' . $log->HallId . PHP_EOL; + echo '# Department: ' . $log->getDepartment()->id . PHP_EOL; + echo '# Track / Lane: ' . $log->getLane() . PHP_EOL; + echo '# Linked to order: ' . ($log->isLinkedToOrder() ? 'Yes' : 'No') . PHP_EOL; + echo '# Items: ' . PHP_EOL; + $tmp_skipped_items = []; + foreach ( $log->WashItems as $item ) { + /** @var xlvask_wash_item $item */ + // Skip items that have the id 64, since these are not relevant to anyone. + $tmp_item_id = $item->getProduct($log)->id; + if ($tmp_item_id === 64) { + $tmp_skipped_items[] = $item; + continue; // Skip items with id 64 + } + echo '## ' . $item->Count . ' x ' . $item->OriginalProductName . ' @ ' . $item->getPriceExVat() . ' as ' . $tmp_item_id . PHP_EOL; + } + echo '### Total: ' . $log->getTotalPrice() . PHP_EOL; + echo '### Skipped items: ' . count($tmp_skipped_items) . PHP_EOL; + } + //print_r($tmp_usage_logs); + // Check if the customer has any usage data in the last 24 hours + // This is a placeholder, you might want to implement a method to check this + // if (!$xlvask->hasRecentUsageData($customer->externId, $dateFrom)) { + // continue; // Skip customers without recent usage data + // } + } + + // Process the usage data and update the users accordingly + //$xlvask->processUsageData($usage_data); + return []; + } + + 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); + } } \ No newline at end of file diff --git a/services/nginx/app/modules/xlvask/helpers/xlvask_usage_log.php b/services/nginx/app/modules/xlvask/helpers/xlvask_usage_log.php new file mode 100644 index 00000000..a48605e4 --- /dev/null +++ b/services/nginx/app/modules/xlvask/helpers/xlvask_usage_log.php @@ -0,0 +1,512 @@ + 8cb0b5a7-ab4b-4dd5-b805-869733c23e54 + * [CustomerId] => 59440200 + * [Customer] => DITOBUS EXCURSIONS A/S + * [VatNumber] => 31171520 + * [Location] => Hvidovre + * [Hall] => Hvidovre_1 + * [HallId] => 845d29a1-a7d2-4e3b-bbc3-2b13242d744a + * [StartTime] => 2025-06-02T15:20:14.950 + * [FinishTime] => 2025-06-02T15:25:55.377 + * [RegistrationNumber] => CZ92915 + * [VehicleType] => Bus/autocamper, M + * [IdentificationType] => LPR + * [IdentificationId] => CZ92915 + * [Info] => CZ92915 + * [Updated] => + * [Prepaid] => + * [FinishStatus] => 1 + * [CustomerGuid] => 21ba156a-b2d2-44be-8398-4b67d66003d6 + * [VehicleId] => e6679209-e52f-4376-9660-d56cb46f523b + * [WashItems] => Array[washItem] => Array + */ + + /** + * The wash ID of the usage log entry + * @var int|string|null $WashId + */ + public int|string|null $WashId; + /** + * The ID of the customer (XLVask ID) + * @var int|string|null $CustomerId + */ + public int|string|null $CustomerId; + /** + * The name of the customer + * @var int|string|null $Customer + */ + public int|string|null $Customer; + /** + * The VAT number of the customer + * @var int|string|null $VatNumber + */ + public int|string|null $VatNumber; + /** + * The location of the wash + * @var int|string|null $Location + */ + public int|string|null $Location; + /** + * The hall where the wash took place + * @var int|string|null $Hall + */ + public int|string|null $Hall; + /** + * The ID of the hall where the wash took place + * @var int|string|null $HallId + */ + public int|string|null $HallId; + /** + * The start time of the wash + * @var string|int|null $StartTime + */ + public string|int|null $StartTime; + /** + * The finish time of the wash + * @var string|int|null $FinishTime + */ + public string|int|null $FinishTime; + /** + * The registration number of the vehicle + * @var int|string|null $RegistrationNumber + */ + public int|string|null $RegistrationNumber; + /** + * The type of the vehicle + * @var string|int|null $VehicleType + */ + public string|int|null $VehicleType; + /** + * The type of identification used for the wash + * @var string|int|null $IdentificationType + */ + public string|int|null $IdentificationType; + /** + * The ID of the identification used for the wash + * @var string|int|null $IdentificationId + */ + public string|int|null $IdentificationId; + /** + * Additional information about the wash + * @var string|int|null $Info + */ + public string|int|null $Info; + /** + * The last updated time of the wash entry + * @var string|int|null $Updated + */ + public string|int|null $Updated; + /** + * Indicates if the wash was prepaid + * @var int|string|null $Prepaid + */ + public int|string|null $Prepaid; + /** + * The finish status of the wash + * @var int|string|null $FinishStatus + */ + public int|string|null $FinishStatus; + /** + * The GUID of the customer in XL Vask + * @var string|int|null $CustomerGuid + */ + public string|int|null $CustomerGuid; + /** + * The ID of the vehicle associated with the wash + * @var string|int|null $vehicleId + */ + public string|int|null $VehicleId; + /** + * The wash items associated with the wash + * @var array[washItem] $washItems + * @see xlvask_wash_item + */ + public array $WashItems; + + protected string $default_string = 'DEFAULT_STRING_1'; + protected string $default_int = 'DEFAULT_INT_1'; + protected string $default_bool = 'DEFAULT_BOOL_1'; + protected string $default_int_nullable = 'DEFAULT_INT_NULLABLE_1'; + protected string $default_string_nullable = 'DEFAULT_STRING_NULLABLE_1'; + protected string $default_bool_nullable = 'DEFAULT_BOOL_NULLABLE_1'; + + /** + * Constructor to initialize the customer object with default values + * @throws Exception + */ + public function __construct(array $data = []) + { + $this->default_string = 'DEFAULT_STRING_1'; + $this->default_int = 'DEFAULT_INT_1'; + $this->default_bool = 'DEFAULT_BOOL_1'; + $this->default_int_nullable = 'DEFAULT_INT_NULLABLE_1'; + $this->default_string_nullable = 'DEFAULT_STRING_NULLABLE_1'; + $this->default_bool_nullable = 'DEFAULT_BOOL_NULLABLE_1'; + // Initialize the customer object with default values + $this::reset(); + $this::setProperties($data); + } + + /** + * Reset this object to its default values. + * This method clears all properties of the usage log object, + * effectively resetting it to a clean state. + * @return void + */ + private function reset(): void + { + $this->WashId = $this->default_string; + $this->CustomerId = $this->default_string; + $this->Customer = $this->default_string; + $this->VatNumber = $this->default_string; + $this->Location = $this->default_string; + $this->Hall = $this->default_string; + $this->HallId = $this->default_string; + $this->StartTime = $this->default_string; + $this->FinishTime = $this->default_string; + $this->RegistrationNumber = $this->default_string; + $this->VehicleType = $this->default_string; + $this->IdentificationType = $this->default_string; + $this->IdentificationId = $this->default_string; + $this->Info = $this->default_string; + $this->Updated = $this->default_string_nullable; + $this->Prepaid = $this->default_bool; + $this->FinishStatus = $this->default_int; + $this->CustomerGuid = $this->default_string; + $this->VehicleId = $this->default_string; + $this->WashItems = []; // Initialize as an empty array + } + + /** + * Set the properties of the object based on the provided data. + * @note This method DOES NOT reset the object before setting the properties. + * @param array $data + * @return self + * @throws Exception + * @see reset() + */ + public function setProperties(array $data): self + { + $default_property_values = [ + 'WashId' => $this->default_string, + 'CustomerId' => $this->default_string, + 'Customer' => $this->default_string, + 'VatNumber' => $this->default_string, + 'Location' => $this->default_string, + 'Hall' => $this->default_string, + 'HallId' => $this->default_string, + 'StartTime' => $this->default_string, + 'FinishTime' => $this->default_string, + 'RegistrationNumber' => $this->default_string, + 'VehicleType' => $this->default_string, + 'IdentificationType' => $this->default_string, + 'IdentificationId' => $this->default_string, + 'Info' => $this->default_string, + 'Updated' => $this->default_string_nullable, + 'Prepaid' => $this->default_bool, + 'FinishStatus' => $this->default_int, + 'CustomerGuid' => $this->default_string, + 'VehicleId' => $this->default_string, + ]; + foreach ( $data as $key => $value ) { + if (property_exists(self::class, $key)) { + switch ($default_property_values[$key] ?? null) { + case $this->default_string: + case $this->default_string_nullable: + $nullable = $default_property_values[$key] === $this->default_string_nullable; + // Check if the value is a string or can be converted to a string + if (is_string($value) || is_numeric($value)) { + $value = (string)$value; // Ensure the value is a string + } elseif ($nullable) { + $value = null; // If nullable, set to null + } else { + $value = ''; // If not nullable, set to empty string + } + break; + case $this->default_int: + case $this->default_int_nullable: + $nullable = $default_property_values[$key] === $this->default_int_nullable; + // Check if the value is numeric and convert it to an integer or null + $value = is_numeric($value) ? (int)$value : ($nullable ? null : 0); // Ensure the value is an integer or null + break; + case $this->default_bool: + case $this->default_bool_nullable: + $nullable = $default_property_values[$key] === $this->default_bool_nullable; + // Check if the value is a boolean or can be converted to a boolean + $value = filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); + if ($value === null && !$nullable) { + $value = false; // If the value is not a boolean and not nullable, set it to false + } + break; + case null: + break; + default: + // If the value is not a default string, keep it as is + echo "No conversion needed for property '$key' with value '$value'." . PHP_EOL; + break; + } + // Set the property value + $this->{$key} = $value; + } else { + // If the property does not exist, throw an exception + // Debug print all properties of the class + //$properties = get_class_vars(self::class); + //echo "Available properties in " . self::class . ": " . implode(', ', array_keys($properties)) . PHP_EOL; + throw new Exception("Property '$key' does not exist in " . self::class); + } + } + // If the washItems property is set, ensure it is an array + if (isset($data['WashItems']) && is_array($data['WashItems'])) { + //echo 'Setting WashItems with ' . count($this->WashItems) . ' items.' . PHP_EOL; + $this->WashItems = self::generateWashItems($data['WashItems']); + //print_r($this->WashItems); + } else { + //echo 'No washItems provided or not an array. Initializing as empty array.' . PHP_EOL; + //print_r($data); + } + // After setting all properties, nullify nullable properties + $this->unsetNullifiableProperties(); + // Return the populated object + return $this; + } + + /** + * Generate wash items from the provided array. + * This method takes an array of wash items and converts them into instances of xlvask_wash_item. + * @throws Exception + */ + private static function generateWashItems(array $washItems): array + { + // Generate wash items from the provided array + $generatedItems = []; + foreach ( $washItems as $item ) { + if (is_array($item)) { + $generatedItems[] = new xlvask_wash_item($item); + } elseif ($item instanceof xlvask_wash_item) { + $generatedItems[] = $item; // If it's already an instance, add it + } else { + throw new Exception("Invalid wash item type: " . gettype($item)); + } + } + // Sort the generated items with the sortWashItems method + return self::sortWashItems($generatedItems); + } + + private static function sortWashItems(array $generatedItems): array + { + // Make sure the primary wash item is always first + $primaryItems = [ + 'Stor bil', + 'Lille bil', + ]; + usort($generatedItems, function ($a, $b) use ($primaryItems) { + // Check if the item is a primary item + $aIsPrimary = in_array($a->OriginalProductName, $primaryItems, true); + $bIsPrimary = in_array($b->OriginalProductName, $primaryItems, true); + // If both are primary items, keep their order + if ($aIsPrimary && $bIsPrimary) { + return 0; + } + // If only one is a primary item, it should come first + return $aIsPrimary ? -1 : 1; + }); + // Return the sorted array of wash items + return $generatedItems; + } + + private function unsetNullifiableProperties(): void + { + // Unset properties that are null or empty strings + $properties = [ + 'WashId', 'CustomerId', 'Customer', 'VatNumber', 'Location', + 'Hall', 'HallId', 'StartTime', 'FinishTime', 'RegistrationNumber', + 'VehicleType', 'IdentificationType', 'IdentificationId', 'Info', + 'Updated', 'Prepaid', 'FinishStatus', 'CustomerGuid', 'VehicleId' + ]; + foreach ( $properties as $property ) { + if ($this->isEmptyOrDefault($this->{$property})) { + $tmp_value = $this->{$property}; + if ($tmp_value === $this->default_string || $tmp_value === $this->default_string_nullable) { + $this->{$property} = ''; // Set to null if it matches the default string + } elseif ($tmp_value === $this->default_int || $tmp_value === $this->default_int_nullable) { + if ($tmp_value === $this->default_int_nullable) { + $this->{$property} = null; // Set to null if it matches the default int nullable + } else { + $this->{$property} = 0; // Set to 0 if it matches the default int + } + } elseif ($tmp_value === $this->default_bool || $tmp_value === $this->default_bool_nullable) { + $this->{$property} = false; // Set to null if it matches the default bool + } else { + $this->{$property} = null; // Otherwise, set to null + } + } + } + } + + private function isEmptyOrDefault($param): bool + { + // Check if the parameter is empty or matches the default values + return $param === null || $param === '' || $param === $this->default_string || $param === $this->default_int || $param === $this->default_bool || $param === $this->default_int_nullable || $param === $this->default_string_nullable || $param === $this->default_bool_nullable; + } + + /** + * Convert the customer object to an array representation. + * This method returns an associative array containing all properties of the object. + * @return array + */ + public function toArray(): array + { + return (array)$this; + } + + /** + * Get the formatted date of the wash start time. + * @return string The formatted date of the wash start time. YYYY-MM-DD HH:MM:SS format. + * @note Returns an empty string if StartTime is not set. + */ + public function getFormattedDate(): string + { + // Format the date in a human-readable format + if ($this->StartTime) { + return date('Y-m-d H:i:s', strtotime($this->StartTime)); + } + return ''; + } + + /** + * Get the formatted finish date of the wash. + * @return string The formatted finish date of the wash. YYYY-MM-DD HH:MM:SS format. + * @note Returns an empty string if FinishTime is not set. + */ + public function getFormattedFinishDate(): string + { + // Format the finish date in a human-readable format + if ($this->FinishTime) { + return date('Y-m-d H:i:s', strtotime($this->FinishTime)); + } + return ''; + } + + /** + * Get the total price of the wash. + * This method calculates the total price of the wash by summing up the prices of all wash items. + * @return float The total price of the wash. + * @throws Exception If any of the wash items do not have a valid price. + */ + public function getTotalPrice(): float + { + // Calculate the total price of the wash by summing up the prices of all wash items + $totalPrice = 0.0; + foreach ( $this->WashItems as $item ) { + /** @var xlvask_wash_item $item */ + if (isset($item->PriceIncVat) && is_numeric($item->PriceIncVat)) { + // The reason why we use the PriceIncVat is that it is the final price, and the price without VAT is not always available / correct. + $totalPrice += ((float)$item->PriceIncVat - (float)$item->Vat); + } else { + //print_r($item); + throw new Exception("Invalid price for wash item: " . json_encode($item)); + } + } + return $totalPrice; + } + + /** + * Check if the wash is completed. + * @note This method checks the FinishStatus property to determine if the wash is completed. + * * A wash is considered completed if the FinishStatus is set to 1. + * @return bool + */ + public function isCompleted(): bool + { + // Check if the wash is completed based on the FinishStatus + return $this->FinishStatus === 1; // Assuming 1 indicates a completed wash + } + + /** + * Get the primary wash item from the usage log. + * This method returns the first wash item in the WashItems array as the primary item. + * @throws Exception + */ + public function getPrimaryItem(): xlvask_wash_item + { + // Return the first wash item as the primary item + if (isset($this->WashItems[0]) && $this->WashItems[0] instanceof xlvask_wash_item) { + return $this->WashItems[0]; + } + throw new Exception("No primary wash item found in the usage log."); + } + + /** + * Is the wash linked to an order? + * @return bool + * @note This method checks if an order exists with the "wash_id" field matching the WashId of this usage log. + * @throws Exception + */ + public function isLinkedToOrder(): bool + { + // Check if the wash is linked to an order by checking the "wash_id" field in the orders table + $order = (new orders_o())->selectByWashId($this->WashId); + return !empty($order); + } + + /** + * Get the order associated with this wash. + * @return orders_o|null + * @throws Exception + */ + public function getOrder(): ?orders_o + { + // Get the order associated with this wash by checking the "wash_id" field in the orders table + $order = (new orders_o())->selectByWashId($this->WashId); + if ($order) { + return $order; + } + return null; // Return null if no order is found + } + + /** + * Get the department associated with this wash. + * @throws Exception If the department cannot be determined. + */ + public function getDepartment(): ?departments_o + { + // Since the department / hall XLVASK API endpoints are not available, + // We will try to get the department from the hall name. + // These are formatted as "DepartmentName_HallNumber" (E.g. "Hvidovre_1"). + if (!empty($this->Hall)) { + $parts = explode('_', $this->Hall); + if (count($parts) > 1) { + $departmentName = $parts[0]; + return (new departments_o())->selectByName($departmentName); + } + } + // If the hall is not set or does not contain a department name, return null + return null; + } + + public function getLane(): ?int + { + // Since the department / hall XLVASK API endpoints are not available, + // We will try to get the lane from the hall name. + // These are formatted as "DepartmentName_HallNumber" (E.g. "Hvidovre_1"). + if (!empty($this->Hall)) { + $parts = explode('_', $this->Hall); + if (count($parts) > 1 && is_numeric($parts[1])) { + return (int)$parts[1]; // Return the lane number as an integer + } + } + // If the hall is not set or does not contain a lane number, return null + return null; + } +} \ No newline at end of file diff --git a/services/nginx/app/modules/xlvask/helpers/xlvask_wash_item.php b/services/nginx/app/modules/xlvask/helpers/xlvask_wash_item.php new file mode 100644 index 00000000..eb159856 --- /dev/null +++ b/services/nginx/app/modules/xlvask/helpers/xlvask_wash_item.php @@ -0,0 +1,304 @@ + 0774be08-6777-490f-add2-0ccff70771ee + * [ExternalProductId] => + * [ExternalProductName] => + * [OriginalProductName] => Stor bil + * [Unit] => stk + * [UnitPrice] => 559 + * [Count] => 1 + * [Discount] => 65 + * [PriceExVat] => 559 + * [Vat] => 48.91 + * [PriceIncVat] => 244.56 + */ + + /** + * The ID of the wash item + * @var string|int|float|null $WashItemId + */ + public string|int|float|null $WashItemId; + /** + * The external product ID of the wash item + * @var string|int|float|null $ExternalProductId + */ + public string|int|float|null $ExternalProductId; + /** + * The external product name of the wash item + * @var string|int|float|null $ExternalProductName + */ + public string|int|float|null $ExternalProductName; + /** + * The original product name of the wash item + * @var string|int|float|null $OriginalProductName + */ + public string|int|float|null $OriginalProductName; + /** + * The Unit of the wash item + * @var string|int|float|null $Unit + */ + public string|int|float|null $Unit; + /** + * The Unit price of the wash item + * @var string|int|float|null $UnitPrice + */ + public string|int|float|null $UnitPrice; + /** + * The count of the wash item + * @var string|int|float|null $Count + */ + public string|int|float|null $Count; + /** + * The discount applied to the wash item + * @var string|int|float|null $Discount + */ + public string|int|float|null $Discount; + /** + * The price excluding VAT of the wash item + * @var string|int|float|null $PriceExVat + */ + public string|int|float|null $PriceExVat; + /** + * The VAT amount of the wash item + * @var string|int|float|null $Vat + */ + public string|int|float|null $Vat; + /** + * The price including VAT of the wash item + * @var string|int|float|null $PriceIncVat + */ + public string|int|float|null $PriceIncVat; + + protected string $default_string = 'DEFAULT_STRING_1'; + protected string $default_int = 'DEFAULT_INT_1'; + protected string $default_bool = 'DEFAULT_BOOL_1'; + protected string $default_int_nullable = 'DEFAULT_INT_NULLABLE_1'; + protected string $default_string_nullable = 'DEFAULT_STRING_NULLABLE_1'; + protected string $default_bool_nullable = 'DEFAULT_BOOL_NULLABLE_1'; + + /** + * Constructor to initialize the customer object with default values + * @throws Exception + */ + public function __construct(array $data = []) + { + $this->default_string = 'DEFAULT_STRING_1'; + $this->default_int = 'DEFAULT_INT_1'; + $this->default_bool = 'DEFAULT_BOOL_1'; + $this->default_int_nullable = 'DEFAULT_INT_NULLABLE_1'; + $this->default_string_nullable = 'DEFAULT_STRING_NULLABLE_1'; + $this->default_bool_nullable = 'DEFAULT_BOOL_NULLABLE_1'; + // Initialize the customer object with default values + $this::reset(); + $this::setProperties($data); + } + + /** + * Reset this object to its default values. + * This method clears all properties of the usage log object, + * effectively resetting it to a clean state. + * @return void + */ + private function reset(): void + { + $this->WashItemId = $this->default_string; + $this->ExternalProductId = $this->default_string; + $this->ExternalProductName = $this->default_string; + $this->OriginalProductName = $this->default_string; + $this->Unit = $this->default_string; + $this->UnitPrice = $this->default_int; // Assuming Unit price is a float, but using default_int for initialization + $this->Count = $this->default_int; // Assuming count is an integer + $this->Discount = $this->default_int; // Assuming discount is a float, but using default_int for initialization + $this->PriceExVat = $this->default_int; // Assuming priceExVat is a float, but using default_int for initialization + $this->Vat = $this->default_int; // Assuming vat is a float, but using default_int for initialization + $this->PriceIncVat = $this->default_int; // Assuming priceIncVat is a float, but using default_int for initialization + } + + /** + * Set the properties of the object based on the provided data. + * @note This method DOES NOT reset the object before setting the properties. + * @param array $data + * @return self + * @throws Exception + * @see reset() + */ + public function setProperties(array $data): self + { + $default_property_values = [ + 'WashItemId' => $this->default_string, + 'ExternalProductId' => $this->default_string, + 'ExternalProductName' => $this->default_string, + 'OriginalProductName' => $this->default_string, + 'Unit' => $this->default_string, + 'UnitPrice' => $this->default_int, // Assuming Unit price is a float, but using default_int for initialization + 'Count' => $this->default_int, // Assuming count is an integer + 'Discount' => $this->default_int, // Assuming discount is a float, but using default_int for initialization + 'PriceExVat' => $this->default_int, // Assuming priceExVat is a float, but using default_int for initialization + 'Vat' => $this->default_int, // Assuming vat is a float, but using default_int for initialization + 'PriceIncVat' => $this->default_int, // Assuming priceIncVat is a float, but using default_int for initialization + ]; + foreach ( $data as $key => $value ) { + if (property_exists(self::class, $key)) { + switch ($default_property_values[$key] ?? null) { + case $this->default_string: + case $this->default_string_nullable: + $nullable = $default_property_values[$key] === $this->default_string_nullable; + // Check if the value is a string or can be converted to a string + if (is_string($value) || is_numeric($value)) { + $value = (string)$value; // Ensure the value is a string + } elseif ($nullable) { + $value = null; // If nullable, set to null + } else { + $value = ''; // If not nullable, set to empty string + } + break; + case $this->default_int: + case $this->default_int_nullable: + $nullable = $default_property_values[$key] === $this->default_int_nullable; + // Check if the value is numeric and convert it to an integer or null + $value = is_numeric($value) ? (int)$value : ($nullable ? null : 0); // Ensure the value is an integer or null + break; + case $this->default_bool: + case $this->default_bool_nullable: + $nullable = $default_property_values[$key] === $this->default_bool_nullable; + // Check if the value is a boolean or can be converted to a boolean + $value = filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); + if ($value === null && !$nullable) { + $value = false; // If the value is not a boolean and not nullable, set it to false + } + break; + case null: + break; + default: + // If the value is not a default string, keep it as is + echo "No conversion needed for property '$key' with value '$value'." . PHP_EOL; + break; + } + // Set the property value + $this->{$key} = $value; + } else { + // If the property does not exist, throw an exception + throw new Exception("Property '$key' does not exist in " . self::class); + } + } + // After setting all properties, nullify nullable properties + $this->unsetNullifiableProperties(); + // Return the populated object + return $this; + } + + private function unsetNullifiableProperties(): void + { + // Unset properties that are null or empty strings + $properties = [ + 'WashItemId', 'ExternalProductId', 'ExternalProductName', 'OriginalProductName', + 'Unit', 'UnitPrice', 'Count', 'Discount', 'PriceExVat', 'Vat', 'PriceIncVat' + ]; + foreach ( $properties as $property ) { + if ($this->isEmptyOrDefault($this->{$property})) { + $tmp_value = $this->{$property}; + if ($tmp_value === $this->default_string || $tmp_value === $this->default_string_nullable) { + $this->{$property} = ''; // Set to null if it matches the default string + } elseif ($tmp_value === $this->default_int || $tmp_value === $this->default_int_nullable) { + if ($tmp_value === $this->default_int_nullable) { + $this->{$property} = null; // Set to null if it matches the default int nullable + } else { + $this->{$property} = 0; // Set to 0 if it matches the default int + } + } elseif ($tmp_value === $this->default_bool || $tmp_value === $this->default_bool_nullable) { + $this->{$property} = false; // Set to null if it matches the default bool + } else { + $this->{$property} = null; // Otherwise, set to null + } + } + } + } + + private function isEmptyOrDefault($param): bool + { + // Check if the parameter is empty or matches the default values + return $param === null || $param === '' || $param === $this->default_string || $param === $this->default_int || $param === $this->default_bool || $param === $this->default_int_nullable || $param === $this->default_string_nullable || $param === $this->default_bool_nullable; + } + + /** + * Convert the customer object to an array representation. + * This method returns an associative array containing all properties of the object. + * @return array + */ + public function toArray(): array + { + return (array)$this; + } + + /** + * Get the product associated with this wash item. + * @throws Exception If the OriginalProductName is not set or if the product cannot be found. + */ + public function getProduct(xlvask_usage_log $xlvask_usage_log): products_o + { + // Since the ExternalProductId is rarely set, we must use the OriginalProductName to find the product. + // The OriginalProductName is the name of the service in the xlvask module, this is ususally very ambiguous. + // Example: "Stor bil" represents a large car/vehicle (And doesn't specify the type of vehicle, this could be a truck, bus, etc.) + if (empty($this->OriginalProductName)) { + throw new Exception('The OriginalProductName is not set. Please provide a valid product name.'); + } + + // Check if a parser exists for the OriginalProductName + $parser_class = 'helpers\xlvask_parser_' . $this->getParserName(); + + if (!class_exists($parser_class)) { + throw new Exception("No parser found for the OriginalProductName: {$this->OriginalProductName} (Parsed as: {$this->getParserName()}). Please ensure the parser class exists."); + } + + $parser = new $parser_class(); + if (!$parser instanceof xlvask_product_parser) { + throw new Exception('The xlvask product parser class is not an instance of xlvask_product_parser. Please ensure it implements the correct interface.'); + } + return $parser->parseProduct($this, $xlvask_usage_log); + } + + /** + * Get the parser name based on the OriginalProductName. + * This method converts the OriginalProductName to a valid parser name. + * @return string + */ + private function getParserName(): string + { + // Convert the OriginalProductName to a valid parser name + // Example: "Stor bil" becomes "stor_bil", "2-b\u00f8rstevask" becomes "2_br_stevask" + // Remove all non-alphanumeric characters and replace spaces with underscores + if (empty($this->OriginalProductName)) { + throw new Exception('The OriginalProductName is not set. Please provide a valid product name.'); + } + // Use preg_replace to replace non-alphanumeric characters with underscores + $result = preg_replace('/[^a-z0-9]+/i', '_', $this->OriginalProductName); + // Remove leading and trailing underscores + $result = trim($result, '_'); + // Convert to lowercase + if ($result === '') { + throw new Exception('The OriginalProductName is empty after processing. Please provide a valid product name.'); + } + // Remove any special characters that might still be present + $result = preg_replace('/[^a-z0-9_]/i', '', $result); + return strtolower($result); + } + + public function getPriceExVat(): float + { + // Calculate the price excluding VAT + if (is_numeric($this->PriceIncVat) && is_numeric($this->Count)) { + return (float)$this->PriceIncVat - (float)$this->Vat; + } + // If either PriceIncVat or Count is not numeric, return 0 + return 0.0; + } +} \ No newline at end of file diff --git a/services/nginx/app/modules/xlvask/xlvask_helpers.php b/services/nginx/app/modules/xlvask/xlvask_helpers.php index 3a4d319a..98cb439f 100644 --- a/services/nginx/app/modules/xlvask/xlvask_helpers.php +++ b/services/nginx/app/modules/xlvask/xlvask_helpers.php @@ -5,11 +5,31 @@ namespace xlvask; require_once WD . '/modules/xlvask/helpers/xlvask_tasks.php'; require_once WD . '/modules/xlvask/helpers/xlvask_cache.php'; require_once WD . '/modules/xlvask/helpers/xlvask_customer.php'; +require_once WD . '/modules/xlvask/helpers/xlvask_usage_log.php'; +require_once WD . '/modules/xlvask/helpers/xlvask_wash_item.php'; +require_once WD . '/modules/xlvask/helpers/xlvask_product_parser_t.php'; +require_once WD . '/modules/xlvask/helpers/xlvask_product_parser.php'; +require_once WD . '/modules/xlvask/helpers/xlvask_parser_stor_bil.php'; +require_once WD . '/modules/xlvask/helpers/xlvask_parser_halleje.php'; +require_once WD . '/modules/xlvask/helpers/xlvask_parser_2_b_rstevask.php'; +require_once WD . '/modules/xlvask/helpers/xlvask_parser_3_b_rstevask.php'; +require_once WD . '/modules/xlvask/helpers/xlvask_parser_ikke_ht_dysebom_bag.php'; +require_once WD . '/modules/xlvask/helpers/xlvask_parser_ht_sider.php'; +require_once WD . '/modules/xlvask/helpers/xlvask_parser_skylning_med_ro.php'; +require_once WD . '/modules/xlvask/helpers/xlvask_parser_eu_spejl.php'; +require_once WD . '/modules/xlvask/helpers/xlvask_parser_ikke_ht_dysebom_p_front.php'; +require_once WD . '/modules/xlvask/helpers/xlvask_parser_lift.php'; +require_once WD . '/modules/xlvask/helpers/xlvask_parser_ht_chassis.php'; +require_once WD . '/modules/xlvask/helpers/xlvask_parser_undervognskylning.php'; +require_once WD . '/modules/xlvask/helpers/xlvask_parser_ht_osc_sider_verst.php'; +require_once WD . '/modules/xlvask/helpers/xlvask_parser_s_be_dysebom.php'; use helpers\xlvask_cache; use helpers\xlvask_customer; use helpers\xlvask_tasks; +use helpers\xlvask_usage_log; +use helpers\xlvask_wash_item; class xlvask_helpers { @@ -28,4 +48,14 @@ class xlvask_helpers * @var string $xlvask_customer */ public string $xlvask_customer = xlvask_customer::class; + /** + * The xlvask usage log helper + * @var string $xlvask_usage_log + */ + public string $xlvask_usage_log = xlvask_usage_log::class; + /** + * The xlvask wash item helper + * @var string $xlvask_wash_item + */ + public string $xlvask_wash_item = xlvask_wash_item::class; } \ No newline at end of file diff --git a/services/nginx/app/objects/departments_o.php b/services/nginx/app/objects/departments_o.php index 65506104..01bf0895 100644 --- a/services/nginx/app/objects/departments_o.php +++ b/services/nginx/app/objects/departments_o.php @@ -198,13 +198,6 @@ class departments_o extends db return $db->fetch_all($result); } - public function selectId(int $department_id): self - { - $this->id = $department_id; - $this->getObjectProperties(); - return $this; - } - public function getDepartmentName(int $department): string { global $db; @@ -355,4 +348,30 @@ class departments_o extends db self::requireSelected(); return (bool)$this->variables->getVariable('bookingsystem_time_based_enabled'); } + + /** + * Select a department by its name (case-sensitive, exact match) + * @throws Exception If the department selection fails or the department is not found + * @example "Hvidovre" + */ + public function selectByName(string $departmentName): self + { + $result = self::getFieldsWhere([ + 'name' => $departmentName, + ], [ + 'id', + ]); + if (empty($result)) { + throw new Exception('Department not found: ' . $departmentName); + } + $this->selectId($result[0]['id']); + return $this; + } + + public function selectId(int $department_id): self + { + $this->id = $department_id; + $this->getObjectProperties(); + return $this; + } } \ No newline at end of file diff --git a/services/nginx/app/objects/orders_o.php b/services/nginx/app/objects/orders_o.php index a6d5b2b6..a133cca0 100644 --- a/services/nginx/app/objects/orders_o.php +++ b/services/nginx/app/objects/orders_o.php @@ -653,4 +653,25 @@ class orders_o extends db return false; } } + + /** + * Select an order by its wash ID + * @param int|string|null $WashId The wash ID to select the order by + * @return orders_o|null The selected order object or null if no order is found + * @throws Exception + */ + public function selectByWashId(int|string|null $WashId): orders_o|null + { + $result = self::getFieldsWhere([ + 'wash_id' => $WashId, + 'deleted_at' => null, + ], [ + 'id' + ]); + if (empty($result)) { + return null; // No order found with the given wash ID + } + $this->select((int)$result[0]['id']); + return $this; + } } \ No newline at end of file diff --git a/services/nginx/app/routes/moduleXLVaskRoute.php b/services/nginx/app/routes/moduleXLVaskRoute.php index 729e8421..39d2dca3 100644 --- a/services/nginx/app/routes/moduleXLVaskRoute.php +++ b/services/nginx/app/routes/moduleXLVaskRoute.php @@ -169,5 +169,23 @@ class moduleXLVaskRoute 'modules_xlvask_sync_users' => 'Synchronize users with the xlvask module' ] ); + + $this->get('/modules/xlvask/tasks/sync-usage', function () { + global $response; + //self::requirePermission('modules_xlvask_sync_usage'); + // Create the xlvask tasks object + $xlvask = new xlvask(); + // Run the sync usage task + $result = $xlvask->getTasks()->runSyncUsage(); + // Response + $response->success( + $result, + 200 + ); + }, + [ + 'modules_xlvask_sync_usage' => 'Synchronize usage with the xlvask module' + ] + ); } } \ No newline at end of file