- New api/tabs/ directory with 13 endpoints: open, close, cancel, get, getActive, addOrder, increaseAuth, addMember, removeMember, getPresence, approveOrder, rejectOrder, pendingOrders - New api/presence/heartbeat.cfm for beacon-based user presence tracking - New cron/expireTabs.cfm for idle tab expiry and presence cleanup - Modified submit.cfm for tab-aware order submission (skip payment, update running total) - Modified getOrCreateCart.cfm to auto-detect active tab and set TabID on new carts - Modified webhook.cfm to handle tab capture events (metadata type=tab_close) - Modified businesses/get.cfm and updateTabs.cfm with new tab config columns - Updated portal tab settings UI with auth amounts, max members, approval toggle - Added tab and presence endpoints to Application.cfm public allowlist Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
132 lines
4.7 KiB
Text
132 lines
4.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">
|
|
|
|
<cfscript>
|
|
/**
|
|
* Get Tab Details
|
|
* Returns full tab info with orders and members.
|
|
*
|
|
* POST: { TabID: int, UserID: int }
|
|
*/
|
|
|
|
try {
|
|
requestData = deserializeJSON(toString(getHttpRequestData().content));
|
|
tabID = val(requestData.TabID ?: 0);
|
|
userID = val(requestData.UserID ?: 0);
|
|
if (tabID == 0) apiAbort({ "OK": false, "ERROR": "missing_TabID" });
|
|
if (userID == 0) apiAbort({ "OK": false, "ERROR": "missing_UserID" });
|
|
|
|
// Verify user is a member of this tab
|
|
qMember = queryTimed("
|
|
SELECT RoleID FROM TabMembers
|
|
WHERE TabID = :tabID AND UserID = :userID AND StatusID = 1
|
|
LIMIT 1
|
|
", {
|
|
tabID: { value: tabID, cfsqltype: "cf_sql_integer" },
|
|
userID: { value: userID, cfsqltype: "cf_sql_integer" }
|
|
});
|
|
if (qMember.recordCount == 0) apiAbort({ "OK": false, "ERROR": "not_a_member" });
|
|
|
|
isOwner = qMember.RoleID == 1;
|
|
|
|
// Get tab
|
|
qTab = queryTimed("
|
|
SELECT t.*, b.Name AS BusinessName, b.PayfritFee, b.TaxRate,
|
|
sp.Name AS ServicePointName,
|
|
u.FirstName AS OwnerFirstName, u.LastName AS OwnerLastName
|
|
FROM Tabs t
|
|
JOIN Businesses b ON b.ID = t.BusinessID
|
|
LEFT JOIN ServicePoints sp ON sp.ID = t.ServicePointID
|
|
JOIN Users u ON u.ID = t.OwnerUserID
|
|
WHERE t.ID = :tabID LIMIT 1
|
|
", { tabID: { value: tabID, cfsqltype: "cf_sql_integer" } });
|
|
|
|
if (qTab.recordCount == 0) apiAbort({ "OK": false, "ERROR": "tab_not_found" });
|
|
|
|
// Get members
|
|
qMembers = queryTimed("
|
|
SELECT tm.ID, tm.UserID, tm.RoleID, tm.StatusID, tm.JoinedOn,
|
|
u.FirstName, u.LastName, u.ImageExtension
|
|
FROM TabMembers tm
|
|
JOIN Users u ON u.ID = tm.UserID
|
|
WHERE tm.TabID = :tabID AND tm.StatusID = 1
|
|
ORDER BY tm.RoleID, tm.JoinedOn
|
|
", { tabID: { value: tabID, cfsqltype: "cf_sql_integer" } });
|
|
|
|
members = [];
|
|
for (row in qMembers) {
|
|
arrayAppend(members, {
|
|
"UserID": row.UserID,
|
|
"FirstName": row.FirstName,
|
|
"LastName": row.LastName,
|
|
"RoleID": row.RoleID,
|
|
"IsOwner": row.RoleID == 1,
|
|
"ImageExtension": row.ImageExtension ?: "",
|
|
"JoinedOn": toISO8601(row.JoinedOn)
|
|
});
|
|
}
|
|
|
|
// Get orders on this tab
|
|
qOrders = queryTimed("
|
|
SELECT tbo.OrderID, tbo.UserID, tbo.ApprovalStatus, tbo.SubtotalCents, tbo.TaxCents, tbo.AddedOn,
|
|
o.StatusID AS OrderStatusID, o.UUID AS OrderUUID,
|
|
u.FirstName, u.LastName
|
|
FROM TabOrders tbo
|
|
JOIN Orders o ON o.ID = tbo.OrderID
|
|
JOIN Users u ON u.ID = tbo.UserID
|
|
WHERE tbo.TabID = :tabID
|
|
ORDER BY tbo.AddedOn DESC
|
|
", { tabID: { value: tabID, cfsqltype: "cf_sql_integer" } });
|
|
|
|
orders = [];
|
|
for (row in qOrders) {
|
|
// If member (not owner), only show own orders
|
|
if (!isOwner && row.UserID != userID) continue;
|
|
|
|
arrayAppend(orders, {
|
|
"OrderID": row.OrderID,
|
|
"OrderUUID": row.OrderUUID,
|
|
"UserID": row.UserID,
|
|
"UserName": "#row.FirstName# #row.LastName#",
|
|
"ApprovalStatus": row.ApprovalStatus,
|
|
"SubtotalCents": row.SubtotalCents,
|
|
"TaxCents": row.TaxCents,
|
|
"OrderStatusID": row.OrderStatusID,
|
|
"AddedOn": toISO8601(row.AddedOn)
|
|
});
|
|
}
|
|
|
|
apiAbort({
|
|
"OK": true,
|
|
"TAB": {
|
|
"ID": qTab.ID,
|
|
"UUID": qTab.UUID,
|
|
"BusinessID": qTab.BusinessID,
|
|
"BusinessName": qTab.BusinessName,
|
|
"OwnerUserID": qTab.OwnerUserID,
|
|
"OwnerName": "#qTab.OwnerFirstName# #qTab.OwnerLastName#",
|
|
"ServicePointID": val(qTab.ServicePointID),
|
|
"ServicePointName": qTab.ServicePointName ?: "",
|
|
"StatusID": qTab.StatusID,
|
|
"AuthAmountCents": qTab.AuthAmountCents,
|
|
"RunningTotalCents": qTab.RunningTotalCents,
|
|
"RemainingCents": qTab.AuthAmountCents - qTab.RunningTotalCents,
|
|
"TipAmountCents": val(qTab.TipAmountCents),
|
|
"FinalCaptureCents": val(qTab.FinalCaptureCents),
|
|
"PaymentStatus": qTab.PaymentStatus,
|
|
"OpenedOn": toISO8601(qTab.OpenedOn),
|
|
"ClosedOn": isDate(qTab.ClosedOn) ? toISO8601(qTab.ClosedOn) : "",
|
|
"IsOwner": isOwner,
|
|
"PayfritFee": val(qTab.PayfritFee),
|
|
"TaxRate": val(qTab.TaxRate)
|
|
},
|
|
"MEMBERS": members,
|
|
"ORDERS": orders
|
|
});
|
|
|
|
} catch (any e) {
|
|
apiAbort({ "OK": false, "ERROR": "server_error", "MESSAGE": e.message });
|
|
}
|
|
</cfscript>
|