Enhance xlvask_tasks: introduce date range options and additional debug outputs for runSyncUsage. Add new parsers xlvask_parser_ingen_b_rster_foran_no_brush_wash_front and xlvask_parser_ht_dysebom and integrate into xlvask_helpers. Improve rate limiting and reduce FastCGI timeout in nginx configuration. Adjust execution time and memory limits in cli.php.

This commit is contained in:
Jepp9350
2025-07-14 09:22:32 +02:00
parent edcb6e5926
commit ffd0606ece
8 changed files with 311 additions and 43 deletions
+1 -1
View File
@@ -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'],
+3 -3
View File
@@ -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
@@ -0,0 +1,26 @@
<?php
namespace helpers;
use Exception;
use objects\products_o;
class xlvask_parser_ht_dysebom extends xlvask_product_parser
{
use xlvask_product_parser_t;
public function __construct()
{
$this->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
}
}
@@ -0,0 +1,26 @@
<?php
namespace helpers;
use Exception;
use objects\products_o;
class xlvask_parser_ingen_b_rster_foran_no_brush_wash_front extends xlvask_product_parser
{
use xlvask_product_parser_t;
public function __construct()
{
$this->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
}
}
@@ -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.
*/
@@ -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.
@@ -559,4 +524,84 @@ class xlvask_usage_log extends xlvask_helper
// 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
}
}
@@ -53,6 +53,8 @@ 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;
+6 -1
View File
@@ -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
}