false, 'ERROR' => 'missing_hardware_id', 'MESSAGE' => 'HardwareId is required']); } $bizId = (int) ($data['BusinessID'] ?? 0); if ($bizId <= 0) apiAbort(['OK' => false, 'ERROR' => 'missing_business_id', 'MESSAGE' => 'BusinessID is required']); $spId = (int) ($data['ServicePointID'] ?? 0); if ($spId <= 0) apiAbort(['OK' => false, 'ERROR' => 'missing_servicepoint_id', 'MESSAGE' => 'ServicePointID is required']); $beaconUUID = trim($data['UUID'] ?? ''); if ($beaconUUID === '') apiAbort(['OK' => false, 'ERROR' => 'missing_uuid', 'MESSAGE' => 'UUID is required']); $major = (int) ($data['Major'] ?? 0); $minor = (int) ($data['Minor'] ?? 0); $txPower = isset($data['TxPower']) && is_numeric($data['TxPower']) ? (int) $data['TxPower'] : null; $advInterval = isset($data['AdvertisingInterval']) && is_numeric($data['AdvertisingInterval']) ? (int) $data['AdvertisingInterval'] : null; $firmwareVersion = trim($data['FirmwareVersion'] ?? ''); try { // Verify business namespace $qBiz = queryOne(" SELECT b.ID, b.BeaconShardID, b.BeaconMajor, bs.UUID AS ShardUUID FROM Businesses b LEFT JOIN BeaconShards bs ON b.BeaconShardID = bs.ID WHERE b.ID = ? LIMIT 1 ", [$bizId]); if (!$qBiz) apiAbort(['OK' => false, 'ERROR' => 'invalid_business', 'MESSAGE' => 'Business not found']); if (strcasecmp($qBiz['ShardUUID'] ?? '', $beaconUUID) !== 0) { apiAbort(['OK' => false, 'ERROR' => 'uuid_mismatch', 'MESSAGE' => 'UUID does not match business\'s assigned shard', 'ExpectedUUID' => $qBiz['ShardUUID'], 'ProvidedUUID' => $beaconUUID]); } if ((int) $qBiz['BeaconMajor'] !== $major) { apiAbort(['OK' => false, 'ERROR' => 'major_mismatch', 'MESSAGE' => 'Major does not match business\'s assigned value', 'ExpectedMajor' => (int) $qBiz['BeaconMajor'], 'ProvidedMajor' => $major]); } // Verify service point $qSP = queryOne("SELECT ID, BusinessID, BeaconMinor, Name FROM ServicePoints WHERE ID = ? LIMIT 1", [$spId]); if (!$qSP) apiAbort(['OK' => false, 'ERROR' => 'invalid_servicepoint', 'MESSAGE' => 'Service point not found']); if ((int) $qSP['BusinessID'] !== $bizId) { apiAbort(['OK' => false, 'ERROR' => 'servicepoint_business_mismatch', 'MESSAGE' => 'Service point does not belong to this business']); } if ($qSP['BeaconMinor'] === null) { queryTimed("UPDATE ServicePoints SET BeaconMinor = ? WHERE ID = ?", [$minor, $spId]); } elseif ((int) $qSP['BeaconMinor'] !== $minor) { apiAbort(['OK' => false, 'ERROR' => 'minor_mismatch', 'MESSAGE' => 'Minor does not match service point\'s assigned value', 'ExpectedMinor' => (int) $qSP['BeaconMinor'], 'ProvidedMinor' => $minor]); } // Upsert BeaconHardware $qExisting = queryOne("SELECT ID, Status FROM BeaconHardware WHERE HardwareId = ? LIMIT 1", [$hardwareId]); if ($qExisting) { $setClauses = ["BusinessID = ?", "ServicePointID = ?", "ShardUUID = ?", "Major = ?", "Minor = ?", "Status = 'assigned'"]; $params = [$bizId, $spId, $beaconUUID, $major, $minor]; if ($txPower !== null) { $setClauses[] = "TxPower = ?"; $params[] = $txPower; } if ($advInterval !== null) { $setClauses[] = "AdvertisingInterval = ?"; $params[] = $advInterval; } if ($firmwareVersion !== '') { $setClauses[] = "FirmwareVersion = ?"; $params[] = $firmwareVersion; } $setClauses[] = "UpdatedAt = NOW()"; $params[] = $hardwareId; queryTimed("UPDATE BeaconHardware SET " . implode(', ', $setClauses) . " WHERE HardwareId = ?", $params); $hwId = (int) $qExisting['ID']; } else { $cols = ['HardwareId', 'BusinessID', 'ServicePointID', 'ShardUUID', 'Major', 'Minor', 'Status']; $vals = ['?', '?', '?', '?', '?', '?', "'assigned'"]; $params = [$hardwareId, $bizId, $spId, $beaconUUID, $major, $minor]; if ($txPower !== null) { $cols[] = 'TxPower'; $vals[] = '?'; $params[] = $txPower; } if ($advInterval !== null) { $cols[] = 'AdvertisingInterval'; $vals[] = '?'; $params[] = $advInterval; } if ($firmwareVersion !== '') { $cols[] = 'FirmwareVersion'; $vals[] = '?'; $params[] = $firmwareVersion; } queryTimed("INSERT INTO BeaconHardware (" . implode(', ', $cols) . ") VALUES (" . implode(', ', $vals) . ")", $params); $hwId = (int) lastInsertId(); } jsonResponse([ 'OK' => true, 'BeaconHardwareID' => $hwId, 'HardwareId' => $hardwareId, 'BusinessID' => $bizId, 'ServicePointID' => $spId, 'ServicePointName' => $qSP['Name'], 'UUID' => $beaconUUID, 'Major' => $major, 'Minor' => $minor, 'Status' => 'assigned', ]); } catch (Exception $e) { jsonResponse(['OK' => false, 'ERROR' => 'server_error', 'MESSAGE' => $e->getMessage()]); }