Files
api/services/nginx/app/objects/xlvask_usage_logs_o.php
T
Jeppe Bundgaard 0cca597fdc Fix XLVask usage import dates
Fix XLVask usage-log import metadata and period-scoped Selvvask automation.
2026-07-06 23:49:28 +02:00

305 lines
12 KiB
PHP

<?php
namespace objects;
use classes\db;
use classes\object_property;
use classes\xlvask;
use classes\xlvask_usage_logs_schema_bootstrap;
use Exception;
use helpers\xlvask_customer;
use helpers\xlvask_usage_log;
use helpers\xlvask_vehicle;
use traits\db_object_t;
class xlvask_usage_logs_o extends db
{
use db_object_t;
public object_property $WashId;
public object_property $CustomerId;
public object_property $Customer;
public object_property $VatNumber;
public object_property $Location;
public object_property $Hall;
public object_property $HallId;
public object_property $StartTime;
public object_property $FinishTime;
public object_property $RegistrationNumber;
public object_property $VehicleType;
public object_property $IdentificationType;
public object_property $IdentificationId;
public object_property $Info;
public object_property $Updated;
public object_property $Prepaid;
public object_property $FinishStatus;
public object_property $CustomerGuid;
public object_property $VehicleId;
public object_property $WashItems;
public object_property $ignored_at;
public object_property $ignored_by;
public object_property $ignored_reason;
public function structure(): void
{
xlvask_usage_logs_schema_bootstrap::ensureTables();
$this->setTable('xlvask_usage_logs');
}
/**
* Add a new XL Vask customer
* @param array $data The properties
* @returns void
* @throws Exception If the object was not created successfully
*/
public function add(array $data): void
{
$tmp_id = self::add_object($data);
$this->id = $tmp_id;
self::getObjectProperties();
self::objectChanged();
}
public function getObjectProperties(): void
{
$this->WashId = new object_property($this->table, $this->id, 'WashId', 'string', false);
$this->CustomerId = new object_property($this->table, $this->id, 'CustomerId', 'string', false);
$this->Customer = new object_property($this->table, $this->id, 'Customer', 'string', false);
$this->VatNumber = new object_property($this->table, $this->id, 'VatNumber', 'string', false);
$this->Location = new object_property($this->table, $this->id, 'Location', 'string', false);
$this->Hall = new object_property($this->table, $this->id, 'Hall', 'string', false);
$this->HallId = new object_property($this->table, $this->id, 'HallId', 'string', false);
$this->StartTime = new object_property($this->table, $this->id, 'StartTime', 'string', false);
$this->FinishTime = new object_property($this->table, $this->id, 'FinishTime', 'string', false);
$this->RegistrationNumber = new object_property($this->table, $this->id, 'RegistrationNumber', 'string', false);
$this->VehicleType = new object_property($this->table, $this->id, 'VehicleType', 'string', false);
$this->IdentificationType = new object_property($this->table, $this->id, 'IdentificationType', 'string', false);
$this->IdentificationId = new object_property($this->table, $this->id, 'IdentificationId', 'string', false);
$this->Info = new object_property($this->table, $this->id, 'Info', 'string', false);
$this->Updated = new object_property($this->table, $this->id, 'Updated', 'string', false);
$this->Prepaid = new object_property($this->table, $this->id, 'Prepaid', 'string', false);
$this->FinishStatus = new object_property($this->table, $this->id, 'FinishStatus', 'string', false);
$this->CustomerGuid = new object_property($this->table, $this->id, 'CustomerGuid', 'string', false);
$this->VehicleId = new object_property($this->table, $this->id, 'VehicleId', 'string', false);
$this->WashItems = new object_property($this->table, $this->id, 'WashItems', 'string', false);
$this->ignored_at = new object_property($this->table, $this->id, 'ignored_at', 'datetime', false);
$this->ignored_by = new object_property($this->table, $this->id, 'ignored_by', 'int', false);
$this->ignored_reason = new object_property($this->table, $this->id, 'ignored_reason', 'string', false);
}
public function objectChanged(): void
{
//TODO: Add cache invalidation
}
public function getCachedAmountSummaryFromRow(array $row): array
{
$cached_amount = self::normalizeMoneyValue($row['cached_total_net_amount'] ?? null);
$cached_at = trim((string)($row['cached_amount_at'] ?? ''));
if ($cached_amount !== null && $cached_at !== '') {
return [
'total_net_amount' => $cached_amount,
'primary_product_name' => (string)($row['cached_primary_product_name'] ?? ''),
'cached' => true,
];
}
$summary = self::calculateAmountSummaryFromWashItems($row['WashItems'] ?? []);
$id = (int)($row['id'] ?? 0);
if ($id > 0) {
self::cacheAmountSummary($id, $summary);
}
return [
...$summary,
'cached' => false,
];
}
public static function calculateAmountSummaryFromWashItems(array|string|null $washItems): array
{
if (is_string($washItems)) {
$decoded = json_decode($washItems, true);
$washItems = is_array($decoded) ? $decoded : [];
}
$total = 0.0;
$primaryProductName = '';
foreach (is_array($washItems) ? $washItems : [] as $item) {
if (!is_array($item)) {
continue;
}
if ($primaryProductName === '' && isset($item['OriginalProductName'])) {
$primaryProductName = trim((string)$item['OriginalProductName']);
}
$priceIncVat = self::normalizeMoneyValue($item['PriceIncVat'] ?? null);
$vat = self::normalizeMoneyValue($item['Vat'] ?? 0.0) ?? 0.0;
if ($priceIncVat === null) {
continue;
}
$total += $priceIncVat - $vat;
}
return [
'total_net_amount' => round($total, 2),
'primary_product_name' => $primaryProductName,
];
}
private static function cacheAmountSummary(int $id, array $summary): void
{
global $db;
if (!isset($db) || !is_object($db) || !method_exists($db, 'query')) {
return;
}
$amount = number_format((float)($summary['total_net_amount'] ?? 0.0), 2, '.', '');
$primaryProductName = $db->escape_string((string)($summary['primary_product_name'] ?? ''));
$db->query(
"UPDATE xlvask_usage_logs
SET cached_total_net_amount = {$amount},
cached_primary_product_name = " . ($primaryProductName === '' ? 'NULL' : "'{$primaryProductName}'") . ",
cached_amount_at = NOW()
WHERE id = {$id}"
);
}
private static function normalizeMoneyValue(mixed $value): ?float
{
if ($value === null || $value === '') {
return null;
}
if (is_int($value) || is_float($value)) {
return (float)$value;
}
$normalized = preg_replace('/[^\d,.\-]/', '', (string)$value);
if ($normalized === null || $normalized === '') {
return null;
}
if (str_contains($normalized, ',') && !str_contains($normalized, '.')) {
$normalized = str_replace(',', '.', $normalized);
} else {
$normalized = str_replace(',', '', $normalized);
}
return is_numeric($normalized) ? (float)$normalized : null;
}
/**
* Import the usage logs from XL Vask
* @param string|null $dateFrom Optional import start date or date-time modifier. Defaults to '-7 days'.
* @param string|null $dateTo Optional inclusive import end date.
* @throws Exception If the objects were not successfully added.
* @returns void
*/
public function importUsageLogs(?string $dateFrom = null, ?string $dateTo = null): void
{
if (!empty($this->id)) {
throw new Exception('To prevent issues, having a selected object is not allowed.');
}
$usage_logs = $this->getUsageLogsFromXLVask(
self::formatImportDateFrom($dateFrom) // Example: '2025-05-01T00:00:00.000'
);
$usage_logs = self::filterUsageLogsUntil($usage_logs, $dateTo);
/** @var string[] $known_usage_logIds The XL Vask usage logIds currently known */
$known_usage_logIds = array_map(function ($log) {
return $log['WashId'];
},
self::getFieldsWhere(
['WashId' => array_column($usage_logs, 'WashId')],
['WashId']
));
/** The XL Vask usage logs without a matching WashId in the database */
$new_usage_logs = array_filter($usage_logs, function ($log) use ($known_usage_logIds) {
return !in_array($log->WashId, $known_usage_logIds);
});
unset($known_usage_logIds);
unset($usage_logs);
/** Adding the new usage logs */
foreach ($new_usage_logs as $log) {
if (!$log->isValid()) {
echo $log->formattedDetails();
throw new Exception('A usage log from XL Vask is not valid, have the structure changed?');
}
}
// Actually save the usage log
foreach ($new_usage_logs as $log) {
/** @var xlvask_usage_log $log */
$this->add($log->toArray());
}
unset($new_usage_logs);
}
private static function formatImportDateFrom(?string $dateFrom): string
{
$dateFrom = trim((string)($dateFrom ?? ''));
$timestamp = strtotime($dateFrom === '' ? '-7 days' : $dateFrom);
if ($timestamp === false) {
throw new Exception('Invalid XL Vask usage import dateFrom');
}
return date('Y-m-d\TH:i:s.000', $timestamp);
}
/**
* @param xlvask_usage_log[] $usageLogs
* @return xlvask_usage_log[]
* @throws Exception
*/
private static function filterUsageLogsUntil(array $usageLogs, ?string $dateTo): array
{
$dateTo = trim((string)($dateTo ?? ''));
if ($dateTo === '') {
return $usageLogs;
}
$dateToTimestamp = strtotime($dateTo);
if ($dateToTimestamp === false) {
throw new Exception('Invalid XL Vask usage import dateTo');
}
$inclusiveEndTimestamp = strtotime(date('Y-m-d 23:59:59', $dateToTimestamp));
if ($inclusiveEndTimestamp === false) {
throw new Exception('Invalid XL Vask usage import dateTo');
}
return array_values(array_filter($usageLogs, function (xlvask_usage_log $log) use ($inclusiveEndTimestamp) {
$startTimestamp = strtotime((string)$log->StartTime);
return $startTimestamp !== false && $startTimestamp <= $inclusiveEndTimestamp;
}));
}
/**
* This function retrieves the usage logs from XL Vask
* @param string $fromDate The date from which to retrieve the usage logs, in ISO 8601 format (e.g., '2025-05-01T00:00:00.000')
* @returns xlvask_usage_log[]
* @throws Exception
*/
public function getUsageLogsFromXLVask(string $fromDate): array
{
// Validate the date format
if (!preg_match('/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}$/', $fromDate)) {
throw new Exception('Invalid date format. Expected format: YYYY-MM-DDTHH:MM:SS.SSS (e.g., 2025-05-01T00:00:00.000)');
}
// Set the memory limit to 512MB, as the import can be quite large
ini_set('memory_limit', '512M');
/** Get the vehicles from XL Vask */
$xlvask = new xlvask();
$vehicle_class = $xlvask->new($xlvask->helpers->xlvask_usage_log);
/** @var xlvask_usage_log[] $vehicles */
$vehicles = $vehicle_class->toObjects($xlvask->getUsageLog(
$fromDate, // Example: '2025-05-01T00:00:00.000'
));
return $vehicles;
}
}