Wire XL Vask usage-log sync, order linking, and order creation (#353)

Wires the three broken paths in the cron-driven XL Vask integration:

- runSyncUsage() now calls the revision-aware importUsageLogsWithSummary() on xlvask_usage_logs_o (was a no-op stub; the upstream API was never queried for washes).
- linkImportedUsageLogsToOrders() persists xlvask_potential_order_matches rows so the accept/compare/link/deny UI has data to render.
- When automatic_order_creation_enabled is on and the wash qualifies, falls through to createOrderFromWash() → orders_o::addXLVaskOrder().
- Removes the obsolete TODO in RunXLVaskModuleCron.php.
- Returns linked + orders_created alongside the existing counters so ops can observe the pipeline.
This commit is contained in:
Jeppe B
2026-08-09 13:38:27 +02:00
committed by GitHub
parent 0aaf32efa4
commit b107ba649c
2 changed files with 120 additions and 4 deletions
@@ -19,7 +19,6 @@ $xlvask = new xlvask;
try { try {
if ($xlvask->config->enabled->isTrue() && $xlvask->config->synchronization_enabled->isTrue()) { if ($xlvask->config->enabled->isTrue() && $xlvask->config->synchronization_enabled->isTrue()) {
$xlvask->getTasks()->runCronTasks(); $xlvask->getTasks()->runCronTasks();
// TODO: Add synchronization for usage logs and vehicles.
} }
} catch (Exception $e) { } catch (Exception $e) {
// This is automatically running, but an XL Vask module failure here // This is automatically running, but an XL Vask module failure here
@@ -11,6 +11,7 @@ use objects\plate_scanners_o;
use objects\users_o; use objects\users_o;
use objects\xlvask_customers_o; use objects\xlvask_customers_o;
use objects\xlvask_potential_order_matches_o; use objects\xlvask_potential_order_matches_o;
use objects\xlvask_usage_logs_o;
use objects\xlvask_vehicles_o; use objects\xlvask_vehicles_o;
class xlvask_tasks class xlvask_tasks
@@ -271,11 +272,127 @@ class xlvask_tasks
} }
/** /**
* Usage synchronization is intentionally disabled until the XL Vask integration is completed. * Synchronize XL Vask usage logs.
*
* Pulls finished washes from XL Vask for the given window and persists them
* via the revision-aware {@see xlvask_usage_logs_o::importUsageLogsWithSummary()}.
* Potential order matches are also recorded so the linked-order UI has the
* data it needs to offer accept/deny actions.
*
* @param string|null $dateFrom Optional import start date or date-time modifier. Defaults to the last 7 days.
* @param string|null $dateTo Optional inclusive import end date.
* @return array{
* fetched:int,
* new:int,
* updated:int,
* unchanged:int,
* invalid:int,
* errors:array<int,array<string,string>>,
* linked:int,
* orders_created:int
* } Summary of the import run.
* @throws Exception If the XL Vask module is not enabled.
*/ */
public function runSyncUsage(?string $dateFrom = null, ?string $dateTo = null): void public function runSyncUsage(?string $dateFrom = null, ?string $dateTo = null): array
{ {
unset($dateFrom, $dateTo); $xlvask = new \classes\xlvask();
$xlvask->requireModuleEnabled();
if (!$xlvask->config->synchronization_enabled->isTrue()) {
return [
'fetched' => 0, 'new' => 0, 'updated' => 0, 'unchanged' => 0,
'invalid' => 0, 'errors' => [], 'linked' => 0, 'orders_created' => 0,
];
}
$summary = (new xlvask_usage_logs_o())->importUsageLogsWithSummary($dateFrom, $dateTo);
$linkSummary = $this->linkImportedUsageLogsToOrders($dateFrom, $dateTo);
return [
...$summary,
'linked' => $linkSummary['linked'],
'orders_created' => $linkSummary['orders_created'],
];
}
/**
* Find imported XL Vask usage logs that look like they belong to an existing order
* (same registration, department, and a window around the wash time) and record
* them as potential order matches so they show up in the linking UI.
*
* Optionally creates the missing order when automatic order creation is enabled,
* the customer has an external ID, and the wash qualifies for automatic continuance.
*
* @return array{linked:int, orders_created:int}
*/
private function linkImportedUsageLogsToOrders(?string $dateFrom, ?string $dateTo): array
{
global $db;
$xlvask = new \classes\xlvask();
$orders_o = new orders_o();
$matches = new xlvask_potential_order_matches_o();
$linked = 0;
$ordersCreated = 0;
$dateFromSql = $dateFrom !== null && $dateFrom !== ''
? "'" . $db->escape_string(xlvask_usage_logs_o::formatImportDateFrom($dateFrom)) . "'"
: 'DATE_SUB(NOW(), INTERVAL 7 DAY)';
$dateToSql = $dateTo !== null && $dateTo !== ''
? "'" . $db->escape_string((string)$dateTo) . " 23:59:59'"
: 'NOW()';
$sql = "SELECT * FROM xlvask_usage_logs
WHERE StartTime >= {$dateFromSql}
AND StartTime <= {$dateToSql}
AND FinishStatus = 1
AND WashId IS NOT NULL AND TRIM(WashId) <> ''
ORDER BY id ASC";
$result = $db->query($sql);
if ($result === false || $result->num_rows === 0) {
return ['linked' => 0, 'orders_created' => 0];
}
$rows = $db->fetch_all($result);
$createEnabled = $xlvask->config->automatic_order_creation_enabled->isTrue();
foreach ($rows as $row) {
$log = (new $xlvask->helpers->xlvask_usage_log())->setProperties($row);
$washId = (string)$log->WashId;
if ($washId === '' || $log->hasDefaultCustomer() || !$log->hasExternalId()) {
continue;
}
if ($matches->doesWashPotentialOrderMatchExist($washId)) {
continue; // already linked or explicitly ignored
}
$existingOrder = $log->getPotentialOrder();
if ($existingOrder !== null && (int)$existingOrder->id > 0) {
$matches->add(
$washId,
(int)$existingOrder->id,
(string)$log->CustomerId,
(int)$log->CustomerId,
(int)$log->getDepartment()->id,
);
$linked++;
continue;
}
// No matching order — try to create one if automatic creation is on.
if (!$createEnabled || !$log->isEligibleForAutomaticContinuance(false)) {
continue;
}
try {
$customer = $log->getCustomer();
$order = (new self())->createOrderFromWash($log, $customer);
if ($order !== null) {
$ordersCreated++;
}
} catch (Exception $e) {
error_log('[xlvask-tasks] auto-create order failed for wash ' . $washId . ': ' . $e->getMessage());
}
}
return ['linked' => $linked, 'orders_created' => $ordersCreated];
} }
private static function formatUsageLogs(array $getUsageLog): array private static function formatUsageLogs(array $getUsageLog): array