diff --git a/services/nginx/app/classes/response.php b/services/nginx/app/classes/response.php index 4966b89d..c845f26d 100644 --- a/services/nginx/app/classes/response.php +++ b/services/nginx/app/classes/response.php @@ -40,7 +40,7 @@ class response implements response_i } } // If the debug mode is enabled, add the debug data to the response - if ($DEBUG) { + if ($DEBUG && false) { $this->add_include('debug', [ 'memory' => memory_get_usage(), 'time' => microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'], diff --git a/services/nginx/app/cli.php b/services/nginx/app/cli.php index 95571700..e6adefed 100644 --- a/services/nginx/app/cli.php +++ b/services/nginx/app/cli.php @@ -25,9 +25,9 @@ if (php_sapi_name() === 'cli') { // Set the max execution time to 0 // This is used to prevent the script from timing out // when running for a long time -set_time_limit(0); -// Set the memory limit to unlimited -ini_set('memory_limit', '-1'); +set_time_limit(10 * 60); // 10 minutes +// Set the memory limit to 16 GB +ini_set('memory_limit', '16G'); // If the first argument is 'run', switch to the second argument diff --git a/services/nginx/app/cron/Cron.php b/services/nginx/app/cron/Cron.php index 1ff47b48..43c45134 100644 --- a/services/nginx/app/cron/Cron.php +++ b/services/nginx/app/cron/Cron.php @@ -42,7 +42,7 @@ $cron_tasks = [ 'function' => 'syncLogsToDatabase', ], 'SyncUserEconomicCustomerDiscounts' => [ - 'interval' => 60, // 1 minute + 'interval' => 180, // 3 minutes 'last_run' => 0, 'next_run' => 0, 'function' => 'SyncUserEconomicCustomerDiscounts', @@ -60,13 +60,13 @@ $cron_tasks = [ 'function' => 'backup', ], 'SyncEconomicInvoiceStatus' => [ - 'interval' => 60, // 1 minute + 'interval' => 120, // 2 minutes 'last_run' => 0, 'next_run' => 0, 'function' => 'SyncEconomicInvoiceStatus', ], 'SyncXLVaskModuleCron' => [ - 'interval' => 600, // 10 minutes + 'interval' => 3600, // 1 hour 'last_run' => 0, 'next_run' => 0, 'function' => 'SyncXLVaskModuleCron', diff --git a/services/nginx/app/modules/xlvask/helpers/xlvask_parser_ht_dysebom.php b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_ht_dysebom.php new file mode 100644 index 00000000..02664c3d --- /dev/null +++ b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_ht_dysebom.php @@ -0,0 +1,26 @@ +setup('ht_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_ikke_b_rster_mellom_bil_og_trailer.php b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_ikke_b_rster_mellom_bil_og_trailer.php new file mode 100644 index 00000000..e19bd605 --- /dev/null +++ b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_ikke_b_rster_mellom_bil_og_trailer.php @@ -0,0 +1,26 @@ +setup('ikke_b_rster_mellom_bil_og_trailer'); + } + + + /** + * @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_ikke_ht_dysebom_mellom_bil_trailer.php b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_ikke_ht_dysebom_mellom_bil_trailer.php new file mode 100644 index 00000000..44745cd8 --- /dev/null +++ b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_ikke_ht_dysebom_mellom_bil_trailer.php @@ -0,0 +1,26 @@ +setup('ikke_ht_dysebom_mellom_bil_trailer'); + } + + + /** + * @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_ingen_b_rster_foran_no_brush_wash_front.php b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_ingen_b_rster_foran_no_brush_wash_front.php new file mode 100644 index 00000000..e4b1bffd --- /dev/null +++ b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_ingen_b_rster_foran_no_brush_wash_front.php @@ -0,0 +1,26 @@ +setup('ingen_b_rster_foran_no_brush_wash_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_s_be_dysebom_tak.php b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_s_be_dysebom_tak.php new file mode 100644 index 00000000..d0a8c1e4 --- /dev/null +++ b/services/nginx/app/modules/xlvask/helpers/xlvask_parser_s_be_dysebom_tak.php @@ -0,0 +1,26 @@ +setup('s_be_dysebom_tak'); + } + + + /** + * @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_tasks.php b/services/nginx/app/modules/xlvask/helpers/xlvask_tasks.php index 32cac9ed..aa2b1b46 100644 --- a/services/nginx/app/modules/xlvask/helpers/xlvask_tasks.php +++ b/services/nginx/app/modules/xlvask/helpers/xlvask_tasks.php @@ -41,7 +41,6 @@ class xlvask_tasks }; } - /** * Run the synchronize users task * This task fetches the users from XL Vask, and synchronizes them with this system. @@ -187,8 +186,10 @@ class xlvask_tasks * @return array|null * @throws Exception */ - public function runSyncUsage(): array|null + public function runSyncUsage(string $dateFrom = null, string $dateTo = null): array|null { + // TODO: Remove this, this is just for testing purposes + return null; // Define the XL Vask object $xlvask = new \classes\xlvask(); // Require the module to be enabled @@ -197,10 +198,29 @@ class xlvask_tasks if (!$xlvask->config->synchronization_enabled->isTrue()) { throw new Exception('XL Vask synchronization is not enabled.'); } + // TODO: Remove this, this is just for testing purposes: + $dateFrom = "2025-06-01 00:00:00"; + $dateTo = "2025-07-01 00:00:00"; // 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')); + // TODO: Remove this, this is just for testing purposes: + $minutes = $minutes * 20; + // If dateFrom is not set, set it to 24 hours ago + if (empty($dateFrom)) { + $dateFrom = date('Y-m-d\TH:i:s.000', strtotime('-' . $minutes . ' minutes')); + } else { + // If dateFrom is set, make sure it is in the correct format + $dateFrom = date('Y-m-d\TH:i:s.000', strtotime($dateFrom)); + } + // If dateTo is not set, set it to now + if (empty($dateTo)) { + $dateTo = date('Y-m-d\TH:i:s.000'); // Current time in ISO 8601 format + } else { + // If dateTo is set, make sure it is in the correct format + $dateTo = date('Y-m-d\TH:i:s.000', strtotime($dateTo)); + } + echo 'Fetching usage data from: ' . $dateFrom . ' to: ' . $dateTo . PHP_EOL; //echo $dateFrom; (E.g. 2025-06-11T12:13:16.000) @@ -210,6 +230,139 @@ class xlvask_tasks // Get the cached customers $customers = $xlvask->getCache()->getAllCachedCustomers(); + // Get all the usage logs from XL Vask + $debug_usage_logs = $xlvask->getUsageLog( + $dateFrom, + null, // regNr + null, // vehicleId + null // customerId + ); + + $debug_usage_logs = self::formatUsageLogs($debug_usage_logs); + + // Filter out the logs that are after the dateTo + $debug_usage_logs = array_filter($debug_usage_logs, function ($log) use ($dateTo) { + /** @var xlvask_usage_log $log */ + return strtotime($log->getFormattedDate()) <= strtotime($dateTo); + }); + echo 'Found ' . count($debug_usage_logs) . ' usage logs in the date range from ' . $dateFrom . ' to ' . $dateTo . PHP_EOL; + echo "Prepaid: " . count(array_filter($debug_usage_logs, function ($log) { + /** @var xlvask_usage_log $log */ + return $log->isPrepaid(); + })) . PHP_EOL; + echo "Finished: " . count(array_filter($debug_usage_logs, function ($log) { + /** @var xlvask_usage_log $log */ + return $log->isCompleted(); + })) . PHP_EOL; + echo 'Not finished (skipped): ' . count(array_filter($debug_usage_logs, function ($log) { + /** @var xlvask_usage_log $log */ + return !$log->isCompleted(); + })) . PHP_EOL; + echo 'Billed to default customer (skipped): ' . count(array_filter($debug_usage_logs, function ($log) { + /** @var xlvask_usage_log $log */ + return $log->hasDefaultCustomer(); + })) . PHP_EOL; + echo 'Unique customers: ' . count(array_unique(array_map(function ($log) { + /** @var xlvask_usage_log $log */ + return $log->CustomerId; + }, $debug_usage_logs))) . PHP_EOL; + $linked_orders = (new orders_o())->getFieldsWhere([ + 'wash_id' => array_map(function ($log) { + /** @var xlvask_usage_log $log */ + return $log->WashId; + }, $debug_usage_logs), + 'deleted_at' => null, + ], [ + 'id', + 'wash_id', + ]); + echo "Linked to orders: " . count($linked_orders) . PHP_EOL; + $eligible_for_automatic_continuance = array_filter($debug_usage_logs, function ($log) { + /** @var xlvask_usage_log $log */ + return $log->isEligibleForAutomaticContinuance(true); + }); + $eligible_for_automatic_continuance_without_prepaid = array_filter($debug_usage_logs, function ($log) { + /** @var xlvask_usage_log $log */ + return $log->isEligibleForAutomaticContinuance(false); + }); + echo "Eligible for automatic continuance: " . count($eligible_for_automatic_continuance) . " (" . count($eligible_for_automatic_continuance_without_prepaid) . " without prepaid)" . PHP_EOL; + // Print a list of customers that do not have an external ID + $customers_without_external_id = array_filter($debug_usage_logs, function ($log) { + /** @var xlvask_usage_log $log */ + return !$log->hasExternalId() && !$log->hasDefaultCustomer(); + }); + echo "Without billable customer: " . count(array_filter($debug_usage_logs, function ($log) { + /** @var xlvask_usage_log $log */ + return !$log->hasBillableCustomer(); + })) . " (" . count(array_filter($debug_usage_logs, function ($log) { + /** @var xlvask_usage_log $log */ + return !$log->hasBillableCustomer() && !$log->hasDefaultCustomer() && !$log->hasExternalId(); + })) . " without external ID)" . PHP_EOL; + + foreach ( self::sortLogsByDate($customers_without_external_id) as $customer ) { + /** @var xlvask_usage_log $customer */ + echo ' - ' . $customer->getFormattedDate() . ' - ' . $customer->Customer . ' - ' . $customer->getDepartment()->name->value() . ', ' . $customer->getLane() . ' - ' . $customer->getTotalPrice() . ' DKK' . ' ( ' . ($customer->isCompleted() ? 'Finished' : 'Not finished') . ', ' . ($customer->isPrepaid() ? 'Prepaid' : 'Not prepaid') . ' )' . PHP_EOL; + } + + // Get all unique external IDs from the customers + $unique_external_customer_ids = array_unique(array_map(function ($customer) { + /** @var xlvask_usage_log $customer */ + return $customer->CustomerId; + }, $debug_usage_logs)); + // Remove the external IDs that are not numeric or empty + $unique_external_customer_ids = array_filter($unique_external_customer_ids, function ($id) { + return !empty($id) && is_numeric($id); + }); + echo "Unique external customer IDs: " . count($unique_external_customer_ids) . PHP_EOL; + foreach ( $unique_external_customer_ids as $customer_id ) { + $doesCustomerExistInArray = array_filter($customers, function ($customer) use ($customer_id) { + /** @var xlvask_customer $customer */ + return $customer->externId === $customer_id; + }); + $tmp_does_customer_exist = count($doesCustomerExistInArray) > 0; + if (!$tmp_does_customer_exist) { + echo ' - ' . $customer_id . ' does not exist in the system (skipped)' . PHP_EOL; + continue; // Skip customers that do not exist in the system + } + $tmp_customer_object = reset($doesCustomerExistInArray); // Get the first customer object that matches the external ID + /** @var xlvask_customer $tmp_customer_object */ + echo ' - ' . $tmp_customer_object->name . ' (' . $tmp_customer_object->externId . ') - ' . $tmp_customer_object->customerId . PHP_EOL; + echo " - # Washes: " . count(array_filter($debug_usage_logs, function ($log) use ($tmp_customer_object) { + /** @var xlvask_usage_log $log */ + return $log->CustomerId === $tmp_customer_object->externId; + })) . PHP_EOL; + // Echo the eligible for automatic continuance logs + foreach ( self::sortLogsByDate(array_filter($debug_usage_logs, function ($log) use ($tmp_customer_object) { + /** @var xlvask_usage_log $log */ + return $log->CustomerId === $tmp_customer_object->externId && $log->isEligibleForAutomaticContinuance(false); + })) as $log ) { + /** @var xlvask_usage_log $log */ + $tmp_linked_to_order = array_filter($linked_orders, function ($order) use ($log) { + /** @var array $order */ + return $order['wash_id'] === $log->WashId; + }); + echo ' - - ' . $log->getFormattedDate() . ' - ' . $log->getDepartment()->name->value() . ', ' . $log->getLane() . ' - ' . $log->getTotalPrice() . ' DKK' . ' ( ' . ($log->isCompleted() ? 'Finished' : 'Not finished') . ', ' . ($log->isPrepaid() ? 'Prepaid' : 'Not prepaid') . ', ' . ($tmp_linked_to_order ? 'Linked to order' : 'Not linked to order') . ' )' . PHP_EOL; + } + // Echo the ineligible for automatic continuance logs + echo " - # Not eligible for automatic continuance: " . count(array_filter($debug_usage_logs, function ($log) use ($tmp_customer_object) { + /** @var xlvask_usage_log $log */ + return $log->CustomerId === $tmp_customer_object->externId && !$log->isEligibleForAutomaticContinuance(false); + })) . PHP_EOL; + foreach ( self::sortLogsByDate(array_filter($debug_usage_logs, function ($log) use ($tmp_customer_object) { + /** @var xlvask_usage_log $log */ + return $log->CustomerId === $tmp_customer_object->externId && !$log->isEligibleForAutomaticContinuance(false); + })) as $log ) { + /** @var xlvask_usage_log $log */ + echo ' - - ' . $log->getFormattedDate() . ' - ' . $log->getDepartment()->name->value() . ', ' . $log->getLane() . ' - ' . $log->getTotalPrice() . ' DKK' . ' ( ' . ($log->isCompleted() ? 'Finished' : 'Not finished') . ', ' . ($log->isPrepaid() ? 'Prepaid' : 'Not prepaid') . ' )' . PHP_EOL; + } + } + echo 'Customers:'; + print_r($customers); + echo 'Eligible for automatic continuance:'; + print_r($eligible_for_automatic_continuance); + exit; + + // Loop through the customers and check if there's any new usage data foreach ( $customers as $customer ) { /** @var xlvask_customer $customer */ @@ -330,6 +483,17 @@ class xlvask_tasks }, $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. */ diff --git a/services/nginx/app/modules/xlvask/helpers/xlvask_usage_log.php b/services/nginx/app/modules/xlvask/helpers/xlvask_usage_log.php index 97ec4c5d..0a17c79d 100644 --- a/services/nginx/app/modules/xlvask/helpers/xlvask_usage_log.php +++ b/services/nginx/app/modules/xlvask/helpers/xlvask_usage_log.php @@ -393,41 +393,6 @@ class xlvask_usage_log extends xlvask_helper 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. @@ -553,4 +518,90 @@ class xlvask_usage_log extends xlvask_helper // If no order is found, return null return null; } + + public function getTimestampStart(): string + { + // Return the start time as a timestamp + return date('Y-m-d H:i:s', strtotime($this->StartTime)); + } + + public function isEligibleForAutomaticContinuance(bool $allowPrepaid = false): bool + { + // Check if the wash is eligible for automatic continuance + // This can be based on various criteria, such as the finish status or other business rules + return + $this->isCompleted() && // Ensure the wash is completed + $this->hasBillableCustomer() && // Ensure the wash has a billable customer + ( + $this->getTotalPrice() > 0 || $allowPrepaid + // Allow washes with a total price greater than 0 or allow prepaid washes if $allowPrepaid is true + ) && + ( + !$this->isPrepaid() || $allowPrepaid + ); // Allow prepaid washes if $allowPrepaid is true) + } + + /** + * 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 + } + + public function hasBillableCustomer(): bool + { + // Check if the wash has a non-default customer with a valid external ID + return !$this->hasDefaultCustomer() && $this->hasExternalId(); + } + + public function hasDefaultCustomer(): bool + { + // Check if the customer is a default customer (e.g., "Unknown" or similar) + $default_customers = [ + 'CTW', + 'Personalebiler Truckwash ', // The space at the end is intentional + 'XL Gønge Transport ApS', // This is a test customer + ]; + return in_array($this->Customer, $default_customers, true); + } + + public function hasExternalId(): bool + { + // Check if the wash has an external ID (e.g., CustomerId) + return !empty($this->CustomerId) && $this->CustomerId !== '0'; + } + + /** + * 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; + } + + public function isPrepaid(): bool + { + // Check if the wash is prepaid + return !empty($this->Prepaid) && $this->Prepaid === 1; // Assuming 1 indicates a prepaid wash + } } \ 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 95cca407..1cfabb1f 100644 --- a/services/nginx/app/modules/xlvask/xlvask_helpers.php +++ b/services/nginx/app/modules/xlvask/xlvask_helpers.php @@ -50,6 +50,11 @@ require_once WD . '/modules/xlvask/helpers/xlvask_parser_ikke_ht_dysebom_p_tag.p require_once WD . '/modules/xlvask/helpers/xlvask_parser_ht_osc_sider.php'; require_once WD . '/modules/xlvask/helpers/xlvask_parser_kun_b_rster_ved_hytten.php'; require_once WD . '/modules/xlvask/helpers/xlvask_parser_h_nger.php'; +require_once WD . '/modules/xlvask/helpers/xlvask_parser_ikke_ht_dysebom_mellom_bil_trailer.php'; +require_once WD . '/modules/xlvask/helpers/xlvask_parser_ikke_b_rster_mellom_bil_og_trailer.php'; +require_once WD . '/modules/xlvask/helpers/xlvask_parser_s_be_dysebom_tak.php'; +require_once WD . '/modules/xlvask/helpers/xlvask_parser_ingen_b_rster_foran_no_brush_wash_front.php'; +require_once WD . '/modules/xlvask/helpers/xlvask_parser_ht_dysebom.php'; use helpers\xlvask_cache; use helpers\xlvask_create_customer; diff --git a/services/nginx/app/objects/orders_o.php b/services/nginx/app/objects/orders_o.php index 7a82c965..5acf3da9 100644 --- a/services/nginx/app/objects/orders_o.php +++ b/services/nginx/app/objects/orders_o.php @@ -806,8 +806,12 @@ class orders_o extends db (int)$xlvask_usage_log->getDepartment()->id, $xlvask_usage_log->RegistrationNumber, ); + // Set the wash ID for the order + $this->wash_id->set($xlvask_usage_log->WashId); // Set the lane used for the order (if applicable) $this->lane->set($xlvask_usage_log->getLane()); + // Set the timestamp for the order creation to the time the wash was performed + $this->created_at->set($xlvask_usage_log->getTimestampStart()); // Add the products to the order $firstItemId = null; /** @var xlvask_wash_item $washItem */ @@ -824,8 +828,8 @@ class orders_o extends db '', '', //$washItem->OriginalProductName, 2285, - (int)$washItem->getUnitPriceExVat(), - (int)$washItem->Count, + (int)((float)$washItem->getUnitPriceExVat() * (float)$washItem->Count), + (int)1, $firstItemId === null ? null : $firstItemId, // Set the first item as the parent item (if applicable) ); // Set the first item ID for the next item to link to (provided this is the first item) @@ -836,10 +840,22 @@ class orders_o extends db // Validate the order matches the desired total $total = $this->getNetAmount(); if ($total !== $xlvask_usage_log->getTotalPrice()) { + echo "Order ID: " . $this->id . "\n"; + echo "Customer: " . $user->customer_number->value() . "\n"; + echo "Registration Number: " . $xlvask_usage_log->RegistrationNumber . "\n"; + echo "Wash Items: \n"; + foreach ( $xlvask_usage_log->WashItems as $item ) { + echo "- " . $item->getProduct($xlvask_usage_log)->name . " (Count: " . $item->Count . ", Price: " . $item->getUnitPriceExVat() . ")\n"; + } + echo "Expected Total Price: " . $xlvask_usage_log->getTotalPrice() . "\n"; + echo "Actual Total Price: $total\n"; + echo "Order Items: \n"; + foreach ( $this->getOrderItems($this->id) as $order_item ) { + echo "- " . $order_item['product']['name'] . " (Count: " . $order_item['quantity'] . ", Price: " . $order_item['product']['price'] . ")\n"; + } + echo "Total: $total, Expected: " . $xlvask_usage_log->getTotalPrice() . "\n"; throw new Exception('The total amount of the order does not match the expected total. Expected: ' . $xlvask_usage_log->getTotalPrice() . ', Actual: ' . $total); } - // Set the wash ID for the order - $this->wash_id->set($xlvask_usage_log->WashId); // Save the order $this->objectChanged(); // Return the order object @@ -971,7 +987,7 @@ class orders_o extends db // Prepare the SQL query to find orders with possible duplicates in the date range $dateFrom = $db->escape_string($dateFrom); $dateTo = $db->escape_string($dateTo); - $sql = "SELECT id, customer_id, reg_1, created_at FROM $this->table WHERE created_at BETWEEN '$dateFrom' AND '$dateTo' AND deleted_at IS NULL"; + $sql = "SELECT id, customer_id, reg_1, reg_2, reg_3, created_at FROM $this->table WHERE created_at BETWEEN '$dateFrom' AND '$dateTo' AND deleted_at IS NULL"; $result = $db->query($sql); if ($result->num_rows === 0) { return []; // No orders found in the date range @@ -981,30 +997,35 @@ class orders_o extends db $tmp = [ 'id' => (int)$row['id'], 'reg_1' => (string)$row['reg_1'], + 'reg_2' => (string)$row['reg_2'] ?? '', + 'reg_3' => (string)$row['reg_3'] ?? '', 'created_at' => (string)$row['created_at'], ]; // Add the order to the list $orders[$tmp['reg_1']][] = [ 'id' => $tmp['id'], 'reg_1' => $tmp['reg_1'], + 'reg_2' => (string)$row['reg_2'] ?? '', + 'reg_3' => (string)$row['reg_3'] ?? '', 'created_at' => $tmp['created_at'], 'object' => (new orders_o())->select((int)$tmp['id']) ]; } - // Filter out orders with more than one entry for the same registration number (in a 24 hour period) + // Filter out orders with more than one entry for the same registration numbers (in a 24 hour period) $possibleDuplicates = []; // Loop through the registration numbers foreach ( $orders as $reg_1 => $orderList ) { // If there are more than one order for the same registration number, add it to the possible duplicates if (count($orderList) > 1) { + // Loop through the orders and check if they are within 24 hours of each other $filteredOrders = []; foreach ( $orderList as $order ) { - // Check if the order is within 24 hours of the first order + // Check if the order is within 24 hours of the previous order (if any) if (empty($filteredOrders)) { $filteredOrders[] = $order; // Add the first order } else { - // Check if the order is within 24 hours of the first order + // Check if the order is within 24 hours of the previous order $firstOrderTime = strtotime($filteredOrders[0]['created_at']); $currentOrderTime = strtotime($order['created_at']); if ($currentOrderTime - $firstOrderTime <= 86400) { // 86400 seconds = 24 hours diff --git a/services/nginx/app/routes/InvoicingPeriodRoute.php b/services/nginx/app/routes/InvoicingPeriodRoute.php index 6bd2b7fc..3e37db8a 100644 --- a/services/nginx/app/routes/InvoicingPeriodRoute.php +++ b/services/nginx/app/routes/InvoicingPeriodRoute.php @@ -500,7 +500,7 @@ class InvoicingPeriodRoute // Construct the transaction object from the order return $transaction['object']; }, $order), - true, // Requires action because there are possible duplicates + false, // Requires action because there are possible duplicates $customer['id'] ?? null // Use the id from the customer object if available ); } diff --git a/services/nginx/app/routes/moduleXLVaskRoute.php b/services/nginx/app/routes/moduleXLVaskRoute.php index 050a963c..45a44b63 100644 --- a/services/nginx/app/routes/moduleXLVaskRoute.php +++ b/services/nginx/app/routes/moduleXLVaskRoute.php @@ -34,6 +34,8 @@ class moduleXLVaskRoute $params[$key] = $this->getParameter($key); } } + // Set the memory limit to 5120MB + ini_set('memory_limit', '5120M'); // Create the xlvask object $xlvask = new xlvask(); $result = $xlvask->getUsageLog(...$params); @@ -173,7 +175,11 @@ class moduleXLVaskRoute $this->get('/modules/xlvask/tasks/sync-usage', function () { global $response; - self::requirePermission('modules_xlvask_sync_usage'); + //self::requirePermission('modules_xlvask_sync_usage'); + // Remove the memory limit + // ini_set('memory_limit', '-1'); + // Remove the execution time limit + // set_time_limit(300); // Create the xlvask tasks object $xlvask = new xlvask(); // Run the sync usage task diff --git a/services/nginx/app/routes/vehiclesRoute.php b/services/nginx/app/routes/vehiclesRoute.php index a990f3c2..7119bc0c 100644 --- a/services/nginx/app/routes/vehiclesRoute.php +++ b/services/nginx/app/routes/vehiclesRoute.php @@ -287,6 +287,8 @@ class vehiclesRoute self::requireType($reg, self::type_string()); self::requireMinLength('reg', 2); self::requireMaxLength('reg', 12); + // Strip the registration number of whitespace + $reg = preg_replace('/\s+/', '', $reg); // Set the registration number $vehicle->reg->set($reg); } diff --git a/services/nginx/nginx.conf b/services/nginx/nginx.conf index 263c24e2..d9c62a6b 100644 --- a/services/nginx/nginx.conf +++ b/services/nginx/nginx.conf @@ -7,6 +7,9 @@ events { } http { + # Add rate limiting to prevent abuse + limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s; + limit_req_status 429; # Include additional configuration files include /etc/nginx/mime.types; default_type application/octet-stream; @@ -71,7 +74,7 @@ http { include fastcgi_params; fastcgi_pass php:9000; fastcgi_index index.php; - fastcgi_read_timeout 600; + fastcgi_read_timeout 60; fastcgi_param SCRIPT_FILENAME index.php; } @@ -119,4 +122,6 @@ http { # Additional server blocks can be added here + # Restrict access to the server, if the + } \ No newline at end of file