573 lines
24 KiB
PHP
573 lines
24 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
|
|
{
|
|
$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,
|
|
];
|
|
}
|
|
|
|
/** Read-only amount projection for GET routes. */
|
|
public function getAmountSummaryReadOnly(array $row): array
|
|
{
|
|
$cachedAmount = self::normalizeMoneyValue($row['cached_total_net_amount'] ?? null);
|
|
$cachedAt = trim((string)($row['cached_amount_at'] ?? ''));
|
|
if ($cachedAmount !== null && $cachedAt !== '') {
|
|
return [
|
|
'total_net_amount' => $cachedAmount,
|
|
'primary_product_name' => (string)($row['cached_primary_product_name'] ?? ''),
|
|
'cached' => true,
|
|
];
|
|
}
|
|
|
|
return [...self::calculateAmountSummaryFromWashItems($row['WashItems'] ?? []), '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
|
|
{
|
|
$this->importUsageLogsWithSummary($dateFrom, $dateTo);
|
|
}
|
|
|
|
/**
|
|
* Revision-aware and idempotent XL Vask import.
|
|
*
|
|
* @return array{fetched:int,new:int,updated:int,unchanged:int,invalid:int,errors:array<int,array<string,string>>}
|
|
*/
|
|
public function importUsageLogsWithSummary(?string $dateFrom = null, ?string $dateTo = null, array $allowedHallIds = []): array
|
|
{
|
|
global $db;
|
|
if (!empty($this->id)) {
|
|
throw new Exception('To prevent issues, having a selected object is not allowed.');
|
|
}
|
|
xlvask_usage_logs_schema_bootstrap::ensureTables();
|
|
$usage_logs = $this->getUsageLogsFromXLVask(
|
|
self::formatImportDateFrom($dateFrom) // Example: '2025-05-01T00:00:00.000'
|
|
);
|
|
$usage_logs = self::filterUsageLogsUntil($usage_logs, $dateTo);
|
|
$upstreamFetched = count($usage_logs);
|
|
$allowedHallIds = array_values(array_unique(array_filter(array_map(
|
|
static fn(mixed $id): string => trim((string)$id),
|
|
$allowedHallIds
|
|
), static fn(string $id): bool => $id !== '')));
|
|
if ($allowedHallIds !== []) {
|
|
$usage_logs = array_values(array_filter(
|
|
$usage_logs,
|
|
static fn(xlvask_usage_log $log): bool => in_array(trim((string)$log->HallId), $allowedHallIds, true)
|
|
));
|
|
}
|
|
|
|
$washIds = array_values(array_filter(array_map(
|
|
static fn(xlvask_usage_log $log): string => trim((string)$log->WashId),
|
|
$usage_logs
|
|
)));
|
|
$knownRows = $washIds === [] ? [] : self::getFieldsWhere(
|
|
['WashId' => $washIds],
|
|
['id', 'WashId', 'source_hash', 'source_revision', 'expected_version']
|
|
);
|
|
$knownByWashId = [];
|
|
foreach ($knownRows as $knownRow) {
|
|
$knownByWashId[(string)$knownRow['WashId']] = $knownRow;
|
|
}
|
|
|
|
$summary = [
|
|
'fetched' => count($usage_logs),
|
|
'upstream_fetched' => $upstreamFetched,
|
|
'new' => 0,
|
|
'updated' => 0,
|
|
'unchanged' => 0,
|
|
'invalid' => 0,
|
|
'errors' => [],
|
|
];
|
|
|
|
foreach ($usage_logs as $log) {
|
|
$washId = trim((string)$log->WashId);
|
|
if ($washId === '' || !$log->isValid()) {
|
|
$summary['invalid']++;
|
|
$summary['errors'][] = [
|
|
'wash_id' => $washId,
|
|
'error' => 'XL Vask usage log failed structural validation.',
|
|
];
|
|
if ($washId !== '' && isset($knownByWashId[$washId])) {
|
|
$this->markImportState((int)$knownByWashId[$washId]['id'], 'invalid', 'XL Vask-kildedata kunne ikke valideres.');
|
|
}
|
|
continue;
|
|
}
|
|
|
|
$payload = self::normalizeSourcePayload($log->toArray());
|
|
$sourceHash = self::sourceHashForAutomation($payload);
|
|
$sourceRevision = trim((string)($payload['Updated'] ?? '')) ?: $sourceHash;
|
|
$known = $knownByWashId[$washId] ?? null;
|
|
|
|
if ($known === null) {
|
|
$this->add($payload);
|
|
$this->updateSourceMetadata((int)$this->id, $sourceHash, $sourceRevision, 'new', true);
|
|
$knownByWashId[$washId] = ['id' => (int)$this->id, 'WashId' => $washId, 'source_hash' => $sourceHash];
|
|
$summary['new']++;
|
|
continue;
|
|
}
|
|
|
|
$existingHash = trim((string)($known['source_hash'] ?? ''));
|
|
if ($existingHash !== '' && hash_equals($existingHash, $sourceHash)) {
|
|
$this->updateSourceMetadata((int)$known['id'], $sourceHash, $sourceRevision, 'unchanged', false);
|
|
$summary['unchanged']++;
|
|
continue;
|
|
}
|
|
|
|
$before = $known;
|
|
$connection = $db->conn();
|
|
$connection->begin_transaction();
|
|
try {
|
|
$this->updateExistingSourceRow((int)$known['id'], $payload, $sourceHash, $sourceRevision);
|
|
$this->supersedeSuggestionsForSourceRevision((int)$known['id']);
|
|
$this->recordSourceRevision((int)$known['id'], $washId, $before, $payload, $sourceHash, $sourceRevision);
|
|
$connection->commit();
|
|
} catch (\Throwable $throwable) {
|
|
$connection->rollback();
|
|
throw $throwable;
|
|
}
|
|
$knownByWashId[$washId]['source_hash'] = $sourceHash;
|
|
$summary['updated']++;
|
|
}
|
|
|
|
return $summary;
|
|
}
|
|
|
|
public static function sourceHashForAutomation(array $payload): string
|
|
{
|
|
$payload = self::normalizeSourcePayload($payload);
|
|
$encoded = json_encode(
|
|
self::sortSourceValue($payload),
|
|
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION
|
|
);
|
|
if ($encoded === false) {
|
|
throw new Exception('Could not build XL Vask source hash.');
|
|
}
|
|
|
|
return hash('sha256', $encoded);
|
|
}
|
|
|
|
private static function normalizeSourcePayload(array $payload): array
|
|
{
|
|
$allowed = [
|
|
'WashId', 'CustomerId', 'Customer', 'VatNumber', 'Location', 'Hall', 'HallId',
|
|
'StartTime', 'FinishTime', 'RegistrationNumber', 'VehicleType', 'IdentificationType',
|
|
'IdentificationId', 'Info', 'Updated', 'Prepaid', 'FinishStatus', 'CustomerGuid',
|
|
'VehicleId', 'WashItems',
|
|
];
|
|
$payload = array_intersect_key($payload, array_flip($allowed));
|
|
if (isset($payload['WashItems']) && is_string($payload['WashItems'])) {
|
|
$decoded = json_decode($payload['WashItems'], true);
|
|
if (is_array($decoded)) {
|
|
$payload['WashItems'] = $decoded;
|
|
}
|
|
}
|
|
if (isset($payload['WashItems']) && is_array($payload['WashItems'])) {
|
|
$payload['WashItems'] = array_values($payload['WashItems']);
|
|
usort($payload['WashItems'], static function (mixed $left, mixed $right): int {
|
|
$leftJson = json_encode(self::sortSourceValue($left), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION) ?: '';
|
|
$rightJson = json_encode(self::sortSourceValue($right), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION) ?: '';
|
|
return strcmp($leftJson, $rightJson);
|
|
});
|
|
}
|
|
|
|
return $payload;
|
|
}
|
|
|
|
private static function sortSourceValue(mixed $value): mixed
|
|
{
|
|
if (!is_array($value)) {
|
|
return $value;
|
|
}
|
|
$sorted = array_map(static fn(mixed $item): mixed => self::sortSourceValue($item), $value);
|
|
$isList = $sorted === [] || array_keys($sorted) === range(0, count($sorted) - 1);
|
|
if (!$isList) {
|
|
ksort($sorted, SORT_STRING);
|
|
}
|
|
return $sorted;
|
|
}
|
|
|
|
private function updateExistingSourceRow(int $id, array $payload, string $sourceHash, string $sourceRevision): void
|
|
{
|
|
global $db;
|
|
$assignments = [];
|
|
foreach ($payload as $column => $value) {
|
|
$assignments[] = '`' . str_replace('`', '', $column) . '` = ' . $this->sqlValue($value);
|
|
}
|
|
$assignments[] = "source_hash = '" . $db->escape_string($sourceHash) . "'";
|
|
$assignments[] = "source_revision = '" . $db->escape_string($sourceRevision) . "'";
|
|
$assignments[] = 'source_observed_at = NOW()';
|
|
$assignments[] = 'source_stable_since = NOW()';
|
|
$assignments[] = 'source_observation_count = 1';
|
|
$assignments[] = "import_state = 'updated'";
|
|
$assignments[] = "resolution_state = 'needs_review'";
|
|
$assignments[] = "certainty = 'none'";
|
|
$assignments[] = "planned_action = 'recheck'";
|
|
$assignments[] = "state_reason = 'XL Vask-kildedata blev opdateret.'";
|
|
$assignments[] = 'expected_version = expected_version + 1';
|
|
$assignments[] = 'cached_total_net_amount = NULL';
|
|
$assignments[] = 'cached_primary_product_name = NULL';
|
|
$assignments[] = 'cached_amount_at = NULL';
|
|
if ($db->query('UPDATE xlvask_usage_logs SET ' . implode(', ', $assignments) . " WHERE id = {$id}") === false
|
|
|| $db->conn()->affected_rows !== 1) {
|
|
throw new Exception('Could not atomically update the XL Vask source row.');
|
|
}
|
|
}
|
|
|
|
private function supersedeSuggestionsForSourceRevision(int $usageLogId): void
|
|
{
|
|
global $db;
|
|
if ($db->query(
|
|
"UPDATE xlvask_automation_suggestions
|
|
SET status = 'superseded', updated_at = NOW()
|
|
WHERE usage_log_id = {$usageLogId} AND status <> 'superseded'"
|
|
) === false) {
|
|
throw new Exception('Could not invalidate stale XL Vask automation suggestions.');
|
|
}
|
|
}
|
|
|
|
private function updateSourceMetadata(int $id, string $sourceHash, string $sourceRevision, string $state, bool $new): void
|
|
{
|
|
global $db;
|
|
$sourceHash = $db->escape_string($sourceHash);
|
|
$sourceRevision = $db->escape_string($sourceRevision);
|
|
$state = $db->escape_string($state);
|
|
$stable = $new ? 'NOW()' : "COALESCE(source_stable_since, NOW())";
|
|
$observations = $new ? '1' : 'GREATEST(1, source_observation_count) + 1';
|
|
$db->query(
|
|
"UPDATE xlvask_usage_logs SET
|
|
source_hash = '{$sourceHash}', source_revision = '{$sourceRevision}',
|
|
source_observed_at = NOW(), source_stable_since = {$stable},
|
|
source_observation_count = {$observations}, import_state = '{$state}'
|
|
WHERE id = {$id}"
|
|
);
|
|
}
|
|
|
|
private function markImportState(int $id, string $state, string $reason): void
|
|
{
|
|
global $db;
|
|
$state = $db->escape_string($state);
|
|
$reason = $db->escape_string($reason);
|
|
$connection = $db->conn();
|
|
$connection->begin_transaction();
|
|
try {
|
|
if ($db->query(
|
|
"UPDATE xlvask_usage_logs SET import_state = '{$state}', resolution_state = 'failed',
|
|
certainty = 'none', planned_action = 'none', state_reason = '{$reason}',
|
|
source_stable_since = NULL, source_observation_count = 0, source_observed_at = NOW(),
|
|
expected_version = expected_version + 1 WHERE id = {$id}"
|
|
) === false || $db->conn()->affected_rows !== 1) {
|
|
throw new Exception('Could not atomically mark invalid XL Vask source data.');
|
|
}
|
|
$this->supersedeSuggestionsForSourceRevision($id);
|
|
$connection->commit();
|
|
} catch (\Throwable $throwable) {
|
|
$connection->rollback();
|
|
throw $throwable;
|
|
}
|
|
}
|
|
|
|
private function recordSourceRevision(
|
|
int $id,
|
|
string $washId,
|
|
array $before,
|
|
array $after,
|
|
string $sourceHash,
|
|
string $sourceRevision
|
|
): void {
|
|
global $db;
|
|
$beforeJson = $db->escape_string(json_encode([
|
|
'source_hash' => (string)($before['source_hash'] ?? ''),
|
|
'source_revision' => (string)($before['source_revision'] ?? ''),
|
|
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '{}');
|
|
$afterJson = $db->escape_string(json_encode([
|
|
'source_hash' => $sourceHash,
|
|
'source_revision' => $sourceRevision,
|
|
'observed_fields' => array_values(array_keys($after)),
|
|
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '{}');
|
|
$washId = $db->escape_string($washId);
|
|
$sourceHash = $db->escape_string($sourceHash);
|
|
$sourceRevision = $db->escape_string($sourceRevision);
|
|
if ($db->query(
|
|
"INSERT INTO xlvask_automation_audit
|
|
(usage_log_id, wash_id, event_type, policy_version, input_hash, source_revision, before_json, after_json)
|
|
VALUES ({$id}, '{$washId}', 'source_updated', 'xlvask-autopilot-v1', '{$sourceHash}', '{$sourceRevision}', '{$beforeJson}', '{$afterJson}')"
|
|
) === false || $db->conn()->affected_rows !== 1) {
|
|
throw new Exception('Could not record the XL Vask source revision audit event.');
|
|
}
|
|
}
|
|
|
|
private function sqlValue(mixed $value): string
|
|
{
|
|
global $db;
|
|
if ($value === null) {
|
|
return 'NULL';
|
|
}
|
|
if (is_bool($value)) {
|
|
return $value ? '1' : '0';
|
|
}
|
|
if (is_int($value) || is_float($value)) {
|
|
return (string)$value;
|
|
}
|
|
if (is_array($value)) {
|
|
$value = json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '[]';
|
|
}
|
|
return "'" . $db->escape_string((string)$value) . "'";
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|