Files
api/services/nginx/app/modules/xlvask/helpers/xlvask_wash_item.php
T
Jeppe Bundgaard af3b0666fb Add import tasks and new parsers to XL Vask module
- Introduced `runImportTasks` method for vehicle, user, and usage log imports.
- Added new product parsers: `ht_b_rstel_s`, `ht_turbo`, and `ikke_b_rster_p_kabinen`.
- Implemented simulation and duplicate detection for usage logs via order objects.
- Updated usage log handling to include default customer checks and user association.
- Enhanced request handling with fast-link functionality for usage orders.
2025-07-21 10:32:56 +02:00

369 lines
17 KiB
PHP

<?php
namespace helpers;
use classes\slack;
use Exception;
use objects\products_o;
class xlvask_wash_item extends xlvask_helper
{
/**
* Example of a wash item object:
* [WashItemId] => 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;
}
/**
* 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)) {
$this->slackMessage('The OriginalProductName is not set. Please provide a valid product name.');
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)) {
$this->slackMessage("No parser found for the OriginalProductName: {$this->OriginalProductName} (Parsed as: {$this->getParserName()})" . PHP_EOL .
'WashItemId: ' . $this->WashItemId . PHP_EOL .
'ExternalProductId: ' . $this->ExternalProductId . PHP_EOL .
'ExternalProductName: ' . $this->ExternalProductName . PHP_EOL .
'OriginalProductName: ' . $this->OriginalProductName . PHP_EOL .
'Unit: ' . $this->Unit . PHP_EOL .
'UnitPrice: ' . $this->UnitPrice . PHP_EOL .
'Count: ' . $this->Count . PHP_EOL .
'Discount: ' . $this->Discount . PHP_EOL .
'PriceExVat: ' . $this->PriceExVat . PHP_EOL .
'Vat: ' . $this->Vat . PHP_EOL .
'PriceIncVat: ' . $this->PriceIncVat . PHP_EOL);
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) {
$this->slackMessage('The xlvask product parser class is not an instance of xlvask_product_parser. Please ensure it implements the correct interface.');
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);
}
private function slackMessage(string $message, $function = null, $class_name = null): void
{
// Get the class name and function name if not provided
$class_name = $class_name ?? __CLASS__;
$function_name = $function ?? debug_backtrace()[1]['function'] ?? 'unknown_function';
// Log the error message to the console
//error_log("Error in {$class_name}::{$function_name} - {$message}");
// Send an error message to Slack
$slack = new slack();
$slack->send_message(
$message,
$class_name . '::' . $function_name,
);
}
/**
* 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", "ingen_bu00f8rster_foran_no_brush_wash_front" => "ingen_b_rster_foran_no_brush_wash_front"
// Remove all non-alphanumeric characters and replace spaces with underscores
if (empty($this->OriginalProductName)) {
$this->slackMessage('Experienced an error parsing a product name: MISSING_ORIGINAL_PRODUCT_NAME ( WashItemId: ' . $this->WashItemId . ' )');
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 === '') {
$this->slackMessage('Experienced an error parsing a product name: EMPTY_ORIGINAL_PRODUCT_NAME_AFTER_PROCESSING ( WashItemId: ' . $this->WashItemId . ' )');
throw new Exception('The OriginalProductName is empty after processing. Please provide a valid product name.');
}
// Check if there's any "uxxxx" pattern in the string and replace it with the "_" character
$result = preg_replace('/u([0-9a-fA-F]{4})/', '_', $result);
// Remove any special characters that might still be present
$result = preg_replace('/[^a-z0-9_]/i', '', $result);
// Remove any double underscores
$result = preg_replace('/_+/', '_', $result);
return strtolower($result);
}
/**
* Check if the wash item should be included in the order.
* This method checks if the wash item has a count above zero or if the price excluding VAT is greater than zero.
* @note This method was added to accommodate the need to check if the wash item should be included in the order.
* @return bool
* @see getPriceExVat()
* @see isCountAboveZero()
*/
public function shouldIncludeInOrder(): bool
{
// Check if the wash item should be included in the order
// This is based on whether the count is above zero or the price excluding VAT is greater than zero.
return ($this->isCountAboveZero() || $this->getPriceExVat() > 0);
}
/**
* Check if the count of the wash item is above zero.
* @note This method was added to accommodate the need to check if the count is above zero. (Since some, but not all wash items can have a count of 0, which is not valid for processing)
* @return bool
*/
public function isCountAboveZero(): bool
{
// Check if the count is above zero
return is_numeric($this->Count) && (float)$this->Count > 0;
}
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;
}
public function getUnitPriceExVat(): float
{
// Calculate the unit price excluding VAT
if (!is_numeric($this->Count) || $this->Count <= 0) {
// If Count is not numeric or less than or equal to zero, return 0
return 0.0;
}
return $this->getPriceExVat() / (float)$this->Count;
}
}