This repository has been archived on 2026-03-21. You can view files and clone it, but cannot push or open issues or pull requests.
payfrit-biz/api/tasks/createChat.cfm
John Mizerek 1210249f54 Normalize database column and table names across entire codebase
Update all SQL queries, query result references, and ColdFusion code to match
the renamed database schema. Tables use plural CamelCase, PKs are all `ID`,
column prefixes stripped (e.g. BusinessName→Name, UserFirstName→FirstName).

Key changes:
- Strip table-name prefixes from all column references (Businesses, Users,
  Addresses, Hours, Menus, Categories, Items, Stations, Orders,
  OrderLineItems, Tasks, TaskCategories, TaskRatings, QuickTaskTemplates,
  ScheduledTaskDefinitions, ChatMessages, Beacons, ServicePoints, Employees,
  VisitorTrackings, ApiPerfLogs, tt_States, tt_Days, tt_AddressTypes,
  tt_OrderTypes, tt_TaskTypes)
- Rename PK references from {TableName}ID to ID in all queries
- Rewrite 7 admin beacon files to use ServicePoints.BeaconID instead of
  dropped lt_Beacon_Businesses_ServicePoints link table
- Rewrite beacon assignment files (list, save, delete) for new schema
- Fix FK references incorrectly changed to ID (OrderLineItems.OrderID,
  Categories.MenuID, Tasks.CategoryID, ServicePoints.BeaconID)
- Update Addresses: AddressLat→Latitude, AddressLng→Longitude
- Update Users: UserPassword→Password, UserIsEmailVerified→IsEmailVerified,
  UserIsActive→IsActive, UserBalance→Balance, etc.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 15:39:12 -08:00

231 lines
8.4 KiB
Text

<cfsetting showdebugoutput="false">
<cfsetting enablecfoutputonly="true">
<cfcontent type="application/json; charset=utf-8" reset="true">
<cfscript>
// Customer initiates a chat with staff
// Input: BusinessID, ServicePointID, OrderID (optional), UserID (optional), Message (optional initial message)
// Output: { OK: true, TaskID: ... }
function apiAbort(required struct payload) {
writeOutput(serializeJSON(payload));
abort;
}
function readJsonBody() {
var raw = getHttpRequestData().content;
if (isNull(raw)) raw = "";
if (!len(trim(raw))) return {};
try {
var data = deserializeJSON(raw);
if (isStruct(data)) return data;
} catch (any e) {}
return {};
}
try {
data = readJsonBody();
businessID = val(structKeyExists(data, "BusinessID") ? data.BusinessID : 0);
servicePointID = val(structKeyExists(data, "ServicePointID") ? data.ServicePointID : 0);
orderID = val(structKeyExists(data, "OrderID") ? data.OrderID : 0);
initialMessage = trim(structKeyExists(data, "Message") ? data.Message : "");
userID = val(structKeyExists(data, "UserID") ? data.UserID : 0);
if (businessID == 0) {
apiAbort({ "OK": false, "ERROR": "missing_params", "MESSAGE": "BusinessID is required" });
}
// ServicePointID = 0 is allowed for remote chats (non-dine-in users)
// In that case, we use userID to match existing chats
// Check for existing open chat at this service point
// An open chat is one where TaskTypeID=2 (Chat) and CompletedOn IS NULL
forceNew = structKeyExists(data, "ForceNew") && data.ForceNew == true;
if (!forceNew) {
// Look for any active chat for this business
// Check by: order match, service point match, OR user match (for remote chats)
existingChat = queryExecute("
SELECT t.ID, t.CreatedOn,
(SELECT MAX(cm.CreatedOn) FROM ChatMessages cm WHERE cm.TaskID = t.ID) as LastMessageTime
FROM Tasks t
LEFT JOIN ChatMessages cm2 ON cm2.TaskID = t.ID AND cm2.SenderUserID = :userID
WHERE t.BusinessID = :businessID
AND t.TaskTypeID = 2
AND t.CompletedOn IS NULL
AND (
(t.OrderID = :orderID AND :orderID > 0)
OR (t.SourceType = 'servicepoint' AND t.SourceID = :servicePointID AND :servicePointID > 0)
OR (t.SourceType = 'user' AND t.SourceID = :userID AND :userID > 0)
OR (cm2.SenderUserID = :userID AND :userID > 0)
)
ORDER BY t.CreatedOn DESC
LIMIT 1
", {
businessID: { value: businessID, cfsqltype: "cf_sql_integer" },
servicePointID: { value: servicePointID, cfsqltype: "cf_sql_integer" },
orderID: { value: orderID, cfsqltype: "cf_sql_integer" },
userID: { value: userID, cfsqltype: "cf_sql_integer" }
}, { datasource: "payfrit" });
if (existingChat.recordCount > 0) {
// Check if chat is stale (more than 30 minutes since last message, or 30 min since creation if no messages)
lastActivity = existingChat.LastMessageTime;
if (isNull(lastActivity) || !isDate(lastActivity)) {
lastActivity = existingChat.CreatedOn;
}
chatAge = dateDiff("n", lastActivity, now());
if (chatAge > 30) {
// Auto-close stale chat
queryExecute("
UPDATE Tasks SET CompletedOn = NOW()
WHERE TaskID = :taskID
", { taskID: { value: existingChat.ID, cfsqltype: "cf_sql_integer" } }, { datasource: "payfrit" });
} else {
// Return existing chat
apiAbort({
"OK": true,
"TaskID": existingChat.ID,
"MESSAGE": "Rejoined existing chat",
"EXISTING": true
});
}
}
}
// Get service point info (table name) - only if dine-in
tableName = "";
if (servicePointID > 0) {
spQuery = queryExecute("
SELECT Name FROM ServicePoints WHERE ID = :spID
", { spID: { value: servicePointID, cfsqltype: "cf_sql_integer" } }, { datasource: "payfrit" });
tableName = spQuery.recordCount ? spQuery.Name : "Table ##" & servicePointID;
}
// Get user name if available
userName = "";
if (userID > 0) {
userQuery = queryExecute("
SELECT FirstName FROM Users WHERE ID = :userID
", { userID: { value: userID, cfsqltype: "cf_sql_integer" } }, { datasource: "payfrit" });
if (userQuery.recordCount && len(trim(userQuery.FirstName))) {
userName = userQuery.FirstName;
}
}
// Create task title - different format for dine-in vs remote
if (servicePointID > 0) {
// Dine-in: "Chat - UserName (TableName)" or "Chat - TableName"
taskTitle = "Chat - " & tableName;
if (len(userName)) {
taskTitle = "Chat - " & userName & " (" & tableName & ")";
}
} else {
// Remote: "Chat - UserName (Remote)" or "Remote Chat"
if (len(userName)) {
taskTitle = "Chat - " & userName & " (Remote)";
} else {
taskTitle = "Remote Chat";
}
}
taskDetails = "Customer initiated chat";
if (len(initialMessage)) {
taskDetails = initialMessage;
}
// Look up or create a "Chat" category for this business
catQuery = queryExecute("
SELECT ID FROM TaskCategories
WHERE BusinessID = :businessID AND Name = 'Chat'
LIMIT 1
", { businessID: { value: businessID, cfsqltype: "cf_sql_integer" } }, { datasource: "payfrit" });
if (catQuery.recordCount == 0) {
// Create the category (blue color for chat)
queryExecute("
INSERT INTO TaskCategories (BusinessID, Name, Color)
VALUES (:businessID, 'Chat', '##2196F3')
", { businessID: { value: businessID, cfsqltype: "cf_sql_integer" } }, { datasource: "payfrit" });
catResult = queryExecute("SELECT LAST_INSERT_ID() as newID", [], { datasource: "payfrit" });
categoryID = catResult.newID;
} else {
categoryID = catQuery.ID;
}
// Determine source type and ID based on dine-in vs remote
if (servicePointID > 0) {
sourceType = "servicepoint";
sourceID = servicePointID;
} else {
sourceType = "user";
sourceID = userID;
}
// Insert task with TaskTypeID = 2 (Chat)
queryExecute("
INSERT INTO Tasks (
BusinessID,
CategoryID,
OrderID,
TaskTypeID,
Title,
Details,
ClaimedByUserID,
SourceType,
SourceID,
CreatedOn
) VALUES (
:businessID,
:categoryID,
:orderID,
2,
:title,
:details,
0,
:sourceType,
:sourceID,
NOW()
)
", {
businessID: { value: businessID, cfsqltype: "cf_sql_integer" },
categoryID: { value: categoryID, cfsqltype: "cf_sql_integer" },
orderID: { value: orderID > 0 ? orderID : javaCast("null", ""), cfsqltype: "cf_sql_integer", null: orderID == 0 },
title: { value: taskTitle, cfsqltype: "cf_sql_varchar" },
details: { value: taskDetails, cfsqltype: "cf_sql_varchar" },
sourceType: { value: sourceType, cfsqltype: "cf_sql_varchar" },
sourceID: { value: sourceID, cfsqltype: "cf_sql_integer" }
}, { datasource: "payfrit" });
// Get the new task ID
result = queryExecute("SELECT LAST_INSERT_ID() as newID", [], { datasource: "payfrit" });
taskID = result.newID;
// If there's an initial message, save it
if (len(initialMessage) && userID > 0) {
queryExecute("
INSERT INTO ChatMessages (TaskID, SenderUserID, SenderType, MessageBody)
VALUES (:taskID, :userID, 'customer', :message)
", {
taskID: { value: taskID, cfsqltype: "cf_sql_integer" },
userID: { value: userID, cfsqltype: "cf_sql_integer" },
message: { value: initialMessage, cfsqltype: "cf_sql_varchar" }
}, { datasource: "payfrit" });
}
apiAbort({
"OK": true,
"TaskID": taskID,
"MESSAGE": "Chat started"
});
} catch (any e) {
apiAbort({
"OK": false,
"ERROR": "server_error",
"MESSAGE": e.message
});
}
</cfscript>