147 lines
4.9 KiB
PHP
147 lines
4.9 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use PDO;
|
|
use RuntimeException;
|
|
|
|
final class RfidService
|
|
{
|
|
public function __construct(
|
|
private readonly PDO $pdo,
|
|
private readonly AuditService $audit
|
|
) {
|
|
}
|
|
|
|
public function createDevice(int $tenantId, string $name, ?string $location, int $actorUserId): string
|
|
{
|
|
if (trim($name) === '') {
|
|
throw new RuntimeException('Bitte einen Namen für den Kartenleser angeben.');
|
|
}
|
|
|
|
$token = bin2hex(random_bytes(20));
|
|
$statement = $this->pdo->prepare(
|
|
'INSERT INTO rfid_devices (tenant_id, name, location_label, device_token, status)
|
|
VALUES (:tenant_id, :name, :location_label, :device_token, "active")'
|
|
);
|
|
$statement->execute([
|
|
'tenant_id' => $tenantId,
|
|
'name' => trim($name),
|
|
'location_label' => $location,
|
|
'device_token' => $token,
|
|
]);
|
|
$deviceId = (int) $this->pdo->lastInsertId();
|
|
|
|
$this->audit->log($tenantId, $actorUserId, 'rfid.device_created', 'rfid_device', $deviceId, 'success');
|
|
|
|
return $token;
|
|
}
|
|
|
|
public function assignTag(int $tenantId, int $memberId, string $uid, ?string $label, int $actorUserId): void
|
|
{
|
|
if (trim($uid) === '') {
|
|
throw new RuntimeException('Die Karten-UID darf nicht leer sein.');
|
|
}
|
|
|
|
$memberStatement = $this->pdo->prepare(
|
|
'SELECT id FROM members WHERE tenant_id = :tenant_id AND id = :id AND status = "active" LIMIT 1'
|
|
);
|
|
$memberStatement->execute([
|
|
'tenant_id' => $tenantId,
|
|
'id' => $memberId,
|
|
]);
|
|
|
|
if (!$memberStatement->fetchColumn()) {
|
|
throw new RuntimeException('Das ausgewählte Mitglied ist für Karten nicht verfügbar.');
|
|
}
|
|
|
|
$statement = $this->pdo->prepare(
|
|
'INSERT INTO rfid_tags (tenant_id, member_id, uid_hash, label, status)
|
|
VALUES (:tenant_id, :member_id, :uid_hash, :label, "active")'
|
|
);
|
|
$statement->execute([
|
|
'tenant_id' => $tenantId,
|
|
'member_id' => $memberId,
|
|
'uid_hash' => hash('sha256', trim($uid)),
|
|
'label' => $label,
|
|
]);
|
|
|
|
$this->audit->log($tenantId, $actorUserId, 'rfid.tag_assigned', 'rfid_tag', (int) $this->pdo->lastInsertId(), 'success');
|
|
}
|
|
|
|
public function ingest(array $payload): void
|
|
{
|
|
$deviceToken = trim((string) ($payload['device_token'] ?? ''));
|
|
$uid = trim((string) ($payload['uid'] ?? ''));
|
|
$externalEventId = trim((string) ($payload['event_id'] ?? ''));
|
|
|
|
if ($deviceToken === '' || $uid === '') {
|
|
throw new RuntimeException('device_token und uid sind Pflichtfelder.');
|
|
}
|
|
|
|
$deviceStatement = $this->pdo->prepare(
|
|
'SELECT d.id, d.tenant_id, d.status
|
|
FROM rfid_devices d
|
|
WHERE d.device_token = :device_token
|
|
LIMIT 1'
|
|
);
|
|
$deviceStatement->execute(['device_token' => $deviceToken]);
|
|
$device = $deviceStatement->fetch();
|
|
|
|
if (!$device) {
|
|
throw new RuntimeException('Der Kartenleser ist unbekannt.');
|
|
}
|
|
|
|
if (($device['status'] ?? '') !== 'active') {
|
|
throw new RuntimeException('Der Kartenleser ist nicht aktiv.');
|
|
}
|
|
|
|
$status = 'received';
|
|
$tagStatement = $this->pdo->prepare(
|
|
'SELECT id FROM rfid_tags WHERE tenant_id = :tenant_id AND uid_hash = :uid_hash LIMIT 1'
|
|
);
|
|
$tagStatement->execute([
|
|
'tenant_id' => $device['tenant_id'],
|
|
'uid_hash' => hash('sha256', $uid),
|
|
]);
|
|
|
|
if ($tagStatement->fetch()) {
|
|
$status = 'linked';
|
|
}
|
|
|
|
$statement = $this->pdo->prepare(
|
|
'INSERT INTO rfid_events (
|
|
tenant_id,
|
|
device_id,
|
|
external_event_id,
|
|
uid_hash,
|
|
payload_json,
|
|
status,
|
|
received_at
|
|
) VALUES (
|
|
:tenant_id,
|
|
:device_id,
|
|
:external_event_id,
|
|
:uid_hash,
|
|
:payload_json,
|
|
:status,
|
|
:received_at
|
|
)'
|
|
);
|
|
$statement->execute([
|
|
'tenant_id' => $device['tenant_id'],
|
|
'device_id' => $device['id'],
|
|
'external_event_id' => $externalEventId !== '' ? $externalEventId : null,
|
|
'uid_hash' => hash('sha256', $uid),
|
|
'payload_json' => json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
|
|
'status' => $status,
|
|
'received_at' => gmdate('Y-m-d H:i:s'),
|
|
]);
|
|
|
|
$this->audit->log((int) $device['tenant_id'], null, 'rfid.event_received', 'rfid_event', (int) $this->pdo->lastInsertId(), 'success', [
|
|
'status' => $status,
|
|
'device_id' => (int) $device['id'],
|
|
]);
|
|
}
|
|
}
|