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']); } try { $qBiz = queryOne("SELECT ID, Name, BeaconShardID, BeaconMajor FROM Businesses WHERE ID = ? LIMIT 1", [$bizId]); if (!$qBiz) { apiAbort(['OK' => false, 'ERROR' => 'invalid_business', 'MESSAGE' => 'Business not found']); } $shardId = (int) ($qBiz['BeaconShardID'] ?? 0); $beaconMajor = (int) ($qBiz['BeaconMajor'] ?? 0); // Allocate shard if needed if ($shardId <= 0 || $qBiz['BeaconMajor'] === null) { $qShard = queryOne(" SELECT ID, UUID, BusinessCount FROM BeaconShards WHERE IsActive = 1 AND BusinessCount < MaxBusinesses ORDER BY BusinessCount ASC LIMIT 1 ", []); if (!$qShard) { apiAbort(['OK' => false, 'ERROR' => 'no_available_shards', 'MESSAGE' => 'All beacon shards are at capacity']); } $shardId = (int) $qShard['ID']; $qMaxMajor = queryOne(" SELECT COALESCE(MAX(BeaconMajor), -1) AS MaxMajor FROM Businesses WHERE BeaconShardID = ? ", [$shardId]); $beaconMajor = (int) $qMaxMajor['MaxMajor'] + 1; if ($beaconMajor > 65535) { apiAbort(['OK' => false, 'ERROR' => 'shard_full', 'MESSAGE' => 'Shard has reached maximum major value']); } queryTimed(" UPDATE Businesses SET BeaconShardID = ?, BeaconMajor = ? WHERE ID = ? AND (BeaconShardID IS NULL OR BeaconMajor IS NULL) ", [$shardId, $beaconMajor, $bizId]); queryTimed("UPDATE BeaconShards SET BusinessCount = BusinessCount + 1 WHERE ID = ?", [$shardId]); } $qShardUUID = queryOne("SELECT UUID FROM BeaconShards WHERE ID = ?", [$shardId]); // Get service point and allocate Minor if needed $qSP = queryOne("SELECT ID, BusinessID, Name, BeaconMinor 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' => 'access_denied', 'MESSAGE' => 'Service point does not belong to this business']); } if ($qSP['BeaconMinor'] !== null) { $beaconMinor = (int) $qSP['BeaconMinor']; } else { $qMaxMinor = queryOne(" SELECT COALESCE(MAX(BeaconMinor), -1) AS MaxMinor FROM ServicePoints WHERE BusinessID = ? ", [$bizId]); $beaconMinor = (int) $qMaxMinor['MaxMinor'] + 1; if ($beaconMinor > 65535) { apiAbort(['OK' => false, 'ERROR' => 'business_full', 'MESSAGE' => 'Business has reached maximum service points (65535)']); } queryTimed("UPDATE ServicePoints SET BeaconMinor = ? WHERE ID = ? AND BeaconMinor IS NULL", [$beaconMinor, $spId]); } jsonResponse([ 'OK' => true, 'UUID' => $qShardUUID['UUID'], 'Major' => $beaconMajor, 'Minor' => $beaconMinor, 'MeasuredPower' => -100, 'AdvInterval' => 2, 'TxPower' => 1, 'ServicePointName' => $qSP['Name'], 'BusinessName' => $qBiz['Name'], ]); } catch (Exception $e) { jsonResponse(['OK' => false, 'ERROR' => 'server_error', 'MESSAGE' => $e->getMessage()]); }