- save.cfm: Auto-allocates shard+Major to business, creates ServicePoint with Minor - list.cfm: Lists ServicePoints with BeaconMinor (removed legacy Beacons table) - list_all.cfm: Returns shard UUIDs instead of legacy beacon UUIDs - lookup.cfm: Removed legacy UUID lookup, shard-only resolution Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
194 lines
5.7 KiB
Text
194 lines
5.7 KiB
Text
<cfsetting showdebugoutput="false">
|
|
<cfsetting enablecfoutputonly="true">
|
|
|
|
<cfcontent type="application/json; charset=utf-8" reset="true">
|
|
<cfheader name="Cache-Control" value="no-store">
|
|
|
|
<cftry>
|
|
<cfscript>
|
|
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
|
|
}));
|
|
</cfscript>
|
|
|
|
<cfcatch type="any">
|
|
<cfheader statuscode="200" statustext="OK">
|
|
<cfcontent type="application/json; charset=utf-8" reset="true">
|
|
<cfoutput>#serializeJSON({ OK=false, ERROR="server_error", MESSAGE=cfcatch.message, DETAIL=cfcatch.detail })#</cfoutput>
|
|
</cfcatch>
|
|
</cftry>
|