Files
api/services/nginx/app/objects/plate_scanners_o.php
T
Jeppe B 6d888a455d Automate XL-Vask invoice-period resolution (#340)
Deploy the revision-aware XL-Vask import and guarded autopilot infrastructure. Automatic actions remain fail-closed pending production readiness, calibration, dry-run, and canary gates.
2026-08-03 15:33:55 +02:00

240 lines
7.2 KiB
PHP

<?php
namespace objects;
use classes\db;
use classes\object_property;
use Exception;
use traits\db_object_t;
class plate_scanners_o extends db
{
use db_object_t;
public object_property $department_id;
public object_property $lane_id;
public object_property $name;
public object_property $notes;
public object_property $api_key;
private static bool $schemaInitialized = false;
public function structure(): void
{
self::ensureSchema();
$this->setTable('plate_scanners');
}
public function objectChanged(): void
{
// No need to invalidate the cache, since the plate_scanners object is not cached
}
public function getPlateScannerById(int $id): plate_scanners_o
{
global $db;
// Get the record from the database
$sql = "SELECT * FROM $this->table WHERE id = $id";
$result = $db->query($sql);
if ($result->num_rows > 0) {
$this->id = $id;
$this->getObjectProperties();
}
return $this;
}
public function getObjectProperties(): void
{
$this->department_id = new object_property($this->table, $this->id, 'department_id', 'int');
$this->lane_id = new object_property($this->table, $this->id, 'lane_id', 'int');
$this->name = new object_property($this->table, $this->id, 'name', 'string');
$this->notes = new object_property($this->table, $this->id, 'notes', 'string');
$this->api_key = new object_property($this->table, $this->id, 'api_key', 'string');
}
public function add(int $department_id, string $name, string $notes, ?int $lane_id = null): void
{
global $db, $response;
try {
// Generate an API key
$api_key = bin2hex(random_bytes(32));
$lane_id = $this->normalizeLaneId($department_id, $lane_id);
// Avoid SQL injection
$name = $db->escape_string($name);
$notes = $db->escape_string($notes);
$laneValue = $lane_id === null ? 'NULL' : (string)$lane_id;
// Create a new record in the database
$sql = "INSERT INTO $this->table (department_id, lane_id, name, notes, api_key) VALUES ($department_id, $laneValue, '$name', '$notes', '$api_key')";
$db->query($sql);
// Get the id of the new record
$this->id = $db->insert_id();
// Set the values of the object properties
$this->getObjectProperties();
} catch (\Exception $e) {
$response->error($e->getMessage());
}
}
public function edit(
int $id,
int $department_id,
string $name,
string $notes,
?int $lane_id = null,
bool $laneIdProvided = false
): void
{
global $response;
try {
$this->select($id);
// Use object_property setters so cached field values are invalidated before we serialize the scanner.
$this->department_id->set($department_id);
$this->name->set($name);
$this->notes->set($notes);
if ($laneIdProvided) {
$lane_id = $this->normalizeLaneId($department_id, $lane_id);
$this->lane_id->set($lane_id);
}
} catch (\Exception $e) {
$response->error($e->getMessage());
}
}
public function getPlateScannerByApiKey(mixed $token): plate_scanners_o
{
global $db;
// Avoid SQL injection
$token = $db->escape_string($token);
// Get the record from the database
$sql = "SELECT * FROM $this->table WHERE api_key = '$token'";
$result = $db->query($sql);
if ($result->num_rows > 0) {
$this->id = $result->fetch_assoc()['id'];
$this->getObjectProperties();
}
return $this;
}
/**
* @return array{id:int,department_id:int,lane_id:int|null,name:string,notes:string,api_key:string}
* @throws Exception
*/
public function asArray(): array
{
$this->requireSelected();
return [
'id' => (int)$this->id,
'department_id' => (int)$this->department_id->value(),
'lane_id' => $this->lane_id->value() === null ? null : (int)$this->lane_id->value(),
'name' => (string)$this->name->value(),
'notes' => (string)$this->notes->value(),
'api_key' => (string)$this->api_key->value(),
];
}
/**
* @return array<int,plate_scanners_o>
* @throws Exception
*/
public function getDepartmentScanners(int $departmentId): array
{
$scanners = [];
$rows = self::getFieldsWhere([
'department_id' => $departmentId,
], ['id']);
foreach ($rows as $row) {
$scanner = (new plate_scanners_o())->select((int)$row['id']);
if ($scanner->exists()) {
$scanners[] = $scanner;
}
}
return $scanners;
}
/**
* @throws Exception
*/
public function rotateApiKey(int $id): array
{
$scanner = $this->select($id);
if (!$scanner->exists()) {
throw new Exception('Number plate scanner not found');
}
$newApiKey = bin2hex(random_bytes(32));
$scanner->api_key->set($newApiKey);
return $scanner->asArray();
}
private function normalizeLaneId(int $departmentId, ?int $laneId): ?int
{
if ($laneId === null || $laneId <= 0) {
return null;
}
$lane = (new department_lanes_o())->select($laneId);
if (!$lane->exists()) {
throw new Exception('Department lane not found');
}
if ((int)$lane->department->value() !== $departmentId) {
throw new Exception('The lane does not belong to the number plate scanner department');
}
return (int)$lane->id;
}
private static function ensureSchema(): void
{
if (self::$schemaInitialized) {
return;
}
global $db;
if (!self::tableHasColumn('plate_scanners', 'lane_id')) {
$db->query("ALTER TABLE `plate_scanners` ADD COLUMN `lane_id` INT NULL AFTER `department_id`");
}
self::$schemaInitialized = true;
}
/** Explicit mutation-path compatibility bootstrap for XL Vask hall scoping. */
public static function ensureHallIdSchemaForMutation(): void
{
global $db;
if (!self::tableHasColumn('plate_scanners', 'HallId')) {
$db->query("ALTER TABLE `plate_scanners` ADD COLUMN `HallId` VARCHAR(191) NULL AFTER `lane_id`");
}
}
private static function tableHasColumn(string $table, string $column): bool
{
global $db;
$table = $db->escape_string($table);
$column = $db->escape_string($column);
$database = $db->escape_string($db->getDatabase());
$result = $db->query(
"SELECT COUNT(*) AS c
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = '$database'
AND TABLE_NAME = '$table'
AND COLUMN_NAME = '$column'"
);
if (!$result) {
return false;
}
$row = $result->fetch_assoc();
return ((int)($row['c'] ?? 0)) > 0;
}
}