pdo->prepare( 'INSERT INTO rfid_devices (tenant_id, name, location_label, device_token, status) VALUES (:tenant_id, :name, :location_label, :device_token, "planned")' ); $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.'); } $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.'); } $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'], ]); } }