- Add beacon-sharding API endpoints for scalable iBeacon addressing (64 shard UUIDs × 65k businesses = ~4.2M capacity) - Fix callServer.cfm to save UserID when creating Call Server tasks - Fix getDetails.cfm to return customer info from Task.UserID when Order.UserID is null (for tasks without orders) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
225 lines
6.4 KiB
Text
225 lines
6.4 KiB
Text
<cfsetting showdebugoutput="false">
|
|
<cfsetting enablecfoutputonly="true">
|
|
|
|
<cfcontent type="application/json; charset=utf-8" reset="true">
|
|
<cfheader name="Cache-Control" value="no-store">
|
|
|
|
<!---
|
|
ResolveServicePoint API
|
|
=======================
|
|
Resolves a beacon's (UUID, Major, Minor) to a ServicePoint.
|
|
Used by customer apps to identify which table/location they're at.
|
|
|
|
Request:
|
|
POST /api/beacon-sharding/resolve_servicepoint.cfm
|
|
{ "UUID": "f7826da6-4fa2-4e98-8024-bc5b71e0893e", "Major": 42, "Minor": 7 }
|
|
|
|
Or for batch resolution:
|
|
{ "Beacons": [
|
|
{ "UUID": "...", "Major": 42, "Minor": 7 },
|
|
{ "UUID": "...", "Major": 42, "Minor": 8 }
|
|
]
|
|
}
|
|
|
|
Response (single):
|
|
{
|
|
"OK": true,
|
|
"ServicePointID": 456,
|
|
"ServicePointName": "Table 7",
|
|
"ServicePointCode": "T7",
|
|
"BusinessID": 123,
|
|
"BusinessName": "Joe's Diner"
|
|
}
|
|
|
|
Response (batch):
|
|
{
|
|
"OK": true,
|
|
"Results": [
|
|
{ "UUID": "...", "Major": 42, "Minor": 7, "ServicePointID": 456, ... },
|
|
{ "UUID": "...", "Major": 42, "Minor": 8, "ServicePointID": null, "Error": "not_found" }
|
|
]
|
|
}
|
|
--->
|
|
|
|
<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));
|
|
}
|
|
|
|
function resolveSingleServicePoint(uuid, major, minor) {
|
|
// First find the business by UUID + Major
|
|
var qBiz = queryExecute(
|
|
"SELECT b.ID AS BusinessID, b.Name AS BusinessName
|
|
FROM Businesses b
|
|
JOIN BeaconShards bs ON b.BeaconShardID = bs.ID
|
|
WHERE bs.UUID = ? AND b.BeaconMajor = ?
|
|
LIMIT 1",
|
|
[
|
|
{ value=uuid, cfsqltype="cf_sql_varchar" },
|
|
{ value=major, cfsqltype="cf_sql_smallint" }
|
|
],
|
|
{ datasource="payfrit" }
|
|
);
|
|
|
|
if (qBiz.recordCount EQ 0) {
|
|
return { Found=false, Error="business_not_found" };
|
|
}
|
|
|
|
// Then find the service point by BusinessID + Minor
|
|
var qSP = queryExecute(
|
|
"SELECT ID, Name, Code, TypeID, Description
|
|
FROM ServicePoints
|
|
WHERE BusinessID = ? AND BeaconMinor = ? AND IsActive = 1
|
|
LIMIT 1",
|
|
[
|
|
{ value=qBiz.BusinessID, cfsqltype="cf_sql_integer" },
|
|
{ value=minor, cfsqltype="cf_sql_smallint" }
|
|
],
|
|
{ datasource="payfrit" }
|
|
);
|
|
|
|
if (qSP.recordCount EQ 0) {
|
|
return {
|
|
Found = false,
|
|
Error = "servicepoint_not_found",
|
|
BusinessID = qBiz.BusinessID,
|
|
BusinessName = qBiz.BusinessName
|
|
};
|
|
}
|
|
|
|
return {
|
|
Found = true,
|
|
ServicePointID = qSP.ID,
|
|
ServicePointName = qSP.Name,
|
|
ServicePointCode = qSP.Code,
|
|
ServicePointTypeID = qSP.TypeID,
|
|
ServicePointDescription = qSP.Description,
|
|
BusinessID = qBiz.BusinessID,
|
|
BusinessName = qBiz.BusinessName
|
|
};
|
|
}
|
|
|
|
data = readJsonBody();
|
|
|
|
// Check for batch request
|
|
if (structKeyExists(data, "Beacons") && isArray(data.Beacons)) {
|
|
results = [];
|
|
for (beacon in data.Beacons) {
|
|
uuid = normStr(structKeyExists(beacon, "UUID") ? beacon.UUID : "");
|
|
major = structKeyExists(beacon, "Major") && isNumeric(beacon.Major) ? int(beacon.Major) : 0;
|
|
minor = structKeyExists(beacon, "Minor") && isNumeric(beacon.Minor) ? int(beacon.Minor) : -1;
|
|
|
|
if (len(uuid) EQ 0 || major LTE 0 || minor LT 0) {
|
|
arrayAppend(results, { UUID=uuid, Major=major, Minor=minor, ServicePointID=javaCast("null",""), Error="invalid_params" });
|
|
continue;
|
|
}
|
|
|
|
resolved = resolveSingleServicePoint(uuid, major, minor);
|
|
if (resolved.Found) {
|
|
arrayAppend(results, {
|
|
UUID = uuid,
|
|
Major = major,
|
|
Minor = minor,
|
|
ServicePointID = resolved.ServicePointID,
|
|
ServicePointName = resolved.ServicePointName,
|
|
ServicePointCode = resolved.ServicePointCode,
|
|
BusinessID = resolved.BusinessID,
|
|
BusinessName = resolved.BusinessName
|
|
});
|
|
} else {
|
|
var errResult = { UUID=uuid, Major=major, Minor=minor, ServicePointID=javaCast("null",""), Error=resolved.Error };
|
|
if (structKeyExists(resolved, "BusinessID")) {
|
|
errResult.BusinessID = resolved.BusinessID;
|
|
errResult.BusinessName = resolved.BusinessName;
|
|
}
|
|
arrayAppend(results, errResult);
|
|
}
|
|
}
|
|
|
|
writeOutput(serializeJSON({ OK=true, COUNT=arrayLen(results), Results=results }));
|
|
abort;
|
|
}
|
|
|
|
// Single request
|
|
uuid = normStr(structKeyExists(data, "UUID") ? data.UUID : "");
|
|
major = 0;
|
|
minor = -1;
|
|
if (structKeyExists(data, "Major") && isNumeric(data.Major)) {
|
|
major = int(data.Major);
|
|
}
|
|
if (structKeyExists(data, "Minor") && isNumeric(data.Minor)) {
|
|
minor = int(data.Minor);
|
|
}
|
|
|
|
// Also check URL params
|
|
if (len(uuid) EQ 0 && structKeyExists(url, "UUID")) {
|
|
uuid = normStr(url.UUID);
|
|
}
|
|
if (major LTE 0 && structKeyExists(url, "Major") && isNumeric(url.Major)) {
|
|
major = int(url.Major);
|
|
}
|
|
if (minor LT 0 && structKeyExists(url, "Minor") && isNumeric(url.Minor)) {
|
|
minor = int(url.Minor);
|
|
}
|
|
|
|
if (len(uuid) EQ 0) {
|
|
apiAbort({ OK=false, ERROR="missing_uuid", MESSAGE="UUID is required" });
|
|
}
|
|
if (major LTE 0) {
|
|
apiAbort({ OK=false, ERROR="missing_major", MESSAGE="Major is required" });
|
|
}
|
|
if (minor LT 0) {
|
|
apiAbort({ OK=false, ERROR="missing_minor", MESSAGE="Minor is required" });
|
|
}
|
|
|
|
resolved = resolveSingleServicePoint(uuid, major, minor);
|
|
|
|
if (!resolved.Found) {
|
|
errResponse = { OK=false, ERROR=resolved.Error };
|
|
if (structKeyExists(resolved, "BusinessID")) {
|
|
errResponse.BusinessID = resolved.BusinessID;
|
|
errResponse.BusinessName = resolved.BusinessName;
|
|
errResponse.MESSAGE = "Service point not found for this business";
|
|
} else {
|
|
errResponse.MESSAGE = "No business found for this beacon";
|
|
}
|
|
apiAbort(errResponse);
|
|
}
|
|
</cfscript>
|
|
|
|
<cfoutput>#serializeJSON({
|
|
OK = true,
|
|
ServicePointID = resolved.ServicePointID,
|
|
ServicePointName = resolved.ServicePointName,
|
|
ServicePointCode = resolved.ServicePointCode,
|
|
ServicePointTypeID = resolved.ServicePointTypeID,
|
|
ServicePointDescription = resolved.ServicePointDescription,
|
|
BusinessID = resolved.BusinessID,
|
|
BusinessName = resolved.BusinessName
|
|
})#</cfoutput>
|
|
|
|
<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>
|