false, 'ERROR' => 'missing_business_id', 'MESSAGE' => 'BusinessID is required']); } try { $qBiz = queryOne("SELECT ID, BeaconShardID, BeaconMajor FROM Businesses WHERE ID = ? LIMIT 1", [$bizId]); if (!$qBiz) { apiAbort(['OK' => false, 'ERROR' => 'invalid_business', 'MESSAGE' => 'Business not found']); } // Already allocated if ((int) ($qBiz['BeaconShardID'] ?? 0) > 0 && $qBiz['BeaconMajor'] !== null) { $qShard = queryOne("SELECT UUID FROM BeaconShards WHERE ID = ?", [(int) $qBiz['BeaconShardID']]); jsonResponse([ 'OK' => true, 'BusinessID' => $bizId, 'BeaconShardUUID' => $qShard['UUID'], 'BeaconMajor' => (int) $qBiz['BeaconMajor'], 'ShardID' => (int) $qBiz['BeaconShardID'], 'AlreadyAllocated' => true, ]); } // Find shard with lowest utilization $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']; $shardUUID = $qShard['UUID']; $qMaxMajor = queryOne(" SELECT COALESCE(MAX(BeaconMajor), -1) AS MaxMajor FROM Businesses WHERE BeaconShardID = ? ", [$shardId]); $nextMajor = (int) $qMaxMajor['MaxMajor'] + 1; if ($nextMajor > 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, $nextMajor, $bizId]); queryTimed("UPDATE BeaconShards SET BusinessCount = BusinessCount + 1 WHERE ID = ?", [$shardId]); jsonResponse([ 'OK' => true, 'BusinessID' => $bizId, 'BeaconShardUUID' => $shardUUID, 'BeaconMajor' => $nextMajor, 'ShardID' => $shardId, 'AlreadyAllocated' => false, ]); } catch (Exception $e) { jsonResponse(['OK' => false, 'ERROR' => 'server_error', 'MESSAGE' => $e->getMessage()]); }