function apiAbort(obj) { writeOutput(serializeJSON(obj)); abort; } function readJsonBody() { raw = toString(getHttpRequestData().content); if (isNull(raw) || len(trim(raw)) EQ 0) return {}; try { parsed = deserializeJSON(raw); } catch(any e) { apiAbort({ OK=false, ERROR="bad_json", MESSAGE="Invalid JSON body" }); } if (!isStruct(parsed)) return {}; return parsed; } function normStr(v) { if (isNull(v)) return ""; return trim(toString(v)); } data = readJsonBody(); if (!structKeyExists(request, "BusinessID") || !isNumeric(request.BusinessID) || request.BusinessID LTE 0) { apiAbort({ OK=false, ERROR="no_business_selected" }); } businessID = request.BusinessID; // Get business and check if it has a shard assigned qBiz = queryExecute( "SELECT ID, Name, BeaconShardID, BeaconMajor FROM Businesses WHERE ID = ? LIMIT 1", [ { value=businessID, cfsqltype="cf_sql_integer" } ], { datasource="payfrit" } ); if (qBiz.recordCount EQ 0) { apiAbort({ OK=false, ERROR="invalid_business", MESSAGE="Business not found" }); } // If business doesn't have a shard, allocate one shardID = qBiz.BeaconShardID; major = qBiz.BeaconMajor; if (isNull(shardID) || val(shardID) EQ 0) { // Find an unassigned shard qFreeShard = queryExecute( "SELECT bs.ID FROM BeaconShards bs WHERE bs.IsActive = 1 AND bs.ID NOT IN (SELECT BeaconShardID FROM Businesses WHERE BeaconShardID IS NOT NULL) ORDER BY bs.ID LIMIT 1", [], { datasource="payfrit" } ); if (qFreeShard.recordCount EQ 0) { apiAbort({ OK=false, ERROR="no_shards_available", MESSAGE="No beacon shards available" }); } shardID = qFreeShard.ID; // Find next available major for this shard (in case multiple businesses share a shard in future) qMaxMajor = queryExecute( "SELECT COALESCE(MAX(BeaconMajor), 0) AS MaxMajor FROM Businesses WHERE BeaconShardID = ?", [ { value=shardID, cfsqltype="cf_sql_integer" } ], { datasource="payfrit" } ); major = val(qMaxMajor.MaxMajor) + 1; // Assign shard to business queryExecute( "UPDATE Businesses SET BeaconShardID = ?, BeaconMajor = ? WHERE ID = ?", [ { value=shardID, cfsqltype="cf_sql_integer" }, { value=major, cfsqltype="cf_sql_smallint" }, { value=businessID, cfsqltype="cf_sql_integer" } ], { datasource="payfrit" } ); } // Get the shard UUID qShard = queryExecute( "SELECT UUID FROM BeaconShards WHERE ID = ?", [ { value=shardID, cfsqltype="cf_sql_integer" } ], { datasource="payfrit" } ); shardUUID = qShard.UUID; // Service point handling spName = structKeyExists(data, "Name") ? normStr(data.Name) : ""; if (len(spName) EQ 0) { apiAbort({ OK=false, ERROR="missing_name", MESSAGE="Service point name is required" }); } servicePointID = 0; if (structKeyExists(data, "ServicePointID") && isNumeric(data.ServicePointID) && int(data.ServicePointID) GT 0) { servicePointID = int(data.ServicePointID); } // Check if we're updating an existing service point or creating new if (servicePointID GT 0) { // Update existing service point qSP = queryExecute( "SELECT ID, BeaconMinor FROM ServicePoints WHERE ID = ? AND BusinessID = ? LIMIT 1", [ { value=servicePointID, cfsqltype="cf_sql_integer" }, { value=businessID, cfsqltype="cf_sql_integer" } ], { datasource="payfrit" } ); if (qSP.recordCount EQ 0) { apiAbort({ OK=false, ERROR="invalid_service_point", MESSAGE="Service point not found" }); } minor = qSP.BeaconMinor; // If no minor assigned yet, get next available if (isNull(minor)) { qMaxMinor = queryExecute( "SELECT COALESCE(MAX(BeaconMinor), 0) AS MaxMinor FROM ServicePoints WHERE BusinessID = ?", [ { value=businessID, cfsqltype="cf_sql_integer" } ], { datasource="payfrit" } ); minor = val(qMaxMinor.MaxMinor) + 1; } // Update service point queryExecute( "UPDATE ServicePoints SET Name = ?, BeaconMinor = ?, IsActive = 1 WHERE ID = ?", [ { value=spName, cfsqltype="cf_sql_varchar" }, { value=minor, cfsqltype="cf_sql_smallint" }, { value=servicePointID, cfsqltype="cf_sql_integer" } ], { datasource="payfrit" } ); } else { // Create new service point with next available minor qMaxMinor = queryExecute( "SELECT COALESCE(MAX(BeaconMinor), 0) AS MaxMinor FROM ServicePoints WHERE BusinessID = ?", [ { value=businessID, cfsqltype="cf_sql_integer" } ], { datasource="payfrit" } ); minor = val(qMaxMinor.MaxMinor) + 1; queryExecute( "INSERT INTO ServicePoints (BusinessID, Name, TypeID, IsActive, BeaconMinor, SortOrder) VALUES (?, ?, 1, 1, ?, ?)", [ { value=businessID, cfsqltype="cf_sql_integer" }, { value=spName, cfsqltype="cf_sql_varchar" }, { value=minor, cfsqltype="cf_sql_smallint" }, { value=minor, cfsqltype="cf_sql_integer" } ], { datasource="payfrit" } ); qNewSP = queryExecute("SELECT LAST_INSERT_ID() AS ID", [], { datasource="payfrit" }); servicePointID = qNewSP.ID; } // Return the beacon configuration for programming writeOutput(serializeJSON({ OK = true, ERROR = "", ServicePointID = servicePointID, ServicePointName = spName, BusinessID = businessID, BusinessName = qBiz.Name, ShardID = shardID, UUID = shardUUID, Major = major, Minor = minor })); #serializeJSON({ OK=false, ERROR="server_error", MESSAGE=cfcatch.message, DETAIL=cfcatch.detail })#