Files
kaffeekasse-saas/app/Services/RfidService.php
T
2026-06-15 18:36:57 +02:00

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 Geraetenamen 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 ausgewaehlte Mitglied ist fuer RFID nicht verfuegbar.');
}
$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('Das RFID-Geraet ist unbekannt.');
}
if (($device['status'] ?? '') !== 'active') {
throw new RuntimeException('Das RFID-Geraet 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'],
]);
}
}