- 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.
369 lines
17 KiB
PHP
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;
|
|
}
|
|
} |