- Move menu manager button to toolbar next to Save Menu for visibility - Implement server-side photo upload for menu items - Strip base64 data URLs from save payload to reduce size - Add scheduled tasks, quick tasks, ratings, and task categories APIs - Add vertical support and brand color features Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
144 lines
6.7 KiB
Text
144 lines
6.7 KiB
Text
<cfsetting showdebugoutput="false">
|
|
<cfcontent type="application/json; charset=utf-8">
|
|
<cfscript>
|
|
function readJsonBody() {
|
|
var raw = getHttpRequestData().content;
|
|
if (isNull(raw) || len(trim(raw)) == 0) return {};
|
|
try {
|
|
var data = deserializeJSON(raw);
|
|
return isStruct(data) ? data : {};
|
|
} catch (any e) {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
try {
|
|
data = readJsonBody();
|
|
|
|
// Token can come from URL param or JSON body
|
|
token = "";
|
|
if (structKeyExists(url, "token")) token = trim(url.token);
|
|
if (len(token) == 0 && structKeyExists(data, "token")) token = trim(data.token);
|
|
|
|
if (len(token) == 0) {
|
|
writeOutput(serializeJSON({ "OK": false, "ERROR": "missing_token", "MESSAGE": "Rating token is required." }));
|
|
abort;
|
|
}
|
|
|
|
// Look up the rating by token
|
|
qRating = queryExecute("
|
|
SELECT r.*, t.TaskTitle,
|
|
u_for.UserFirstName AS ForUserFirstName, u_for.UserLastName AS ForUserLastName,
|
|
u_by.UserFirstName AS ByUserFirstName, u_by.UserLastName AS ByUserLastName
|
|
FROM TaskRatings r
|
|
JOIN Tasks t ON t.TaskID = r.TaskRatingTaskID
|
|
LEFT JOIN Users u_for ON u_for.UserID = r.TaskRatingForUserID
|
|
LEFT JOIN Users u_by ON u_by.UserID = r.TaskRatingByUserID
|
|
WHERE r.TaskRatingAccessToken = ?
|
|
LIMIT 1
|
|
", [{ value = token, cfsqltype = "cf_sql_varchar" }]);
|
|
|
|
if (qRating.recordCount == 0) {
|
|
writeOutput(serializeJSON({ "OK": false, "ERROR": "invalid_token", "MESSAGE": "Rating not found or link is invalid." }));
|
|
abort;
|
|
}
|
|
|
|
// Check if expired
|
|
if (qRating.TaskRatingExpiresOn < now()) {
|
|
writeOutput(serializeJSON({ "OK": false, "ERROR": "expired", "MESSAGE": "This rating link has expired." }));
|
|
abort;
|
|
}
|
|
|
|
// Check if already completed
|
|
if (len(trim(qRating.TaskRatingCompletedOn)) > 0) {
|
|
writeOutput(serializeJSON({ "OK": false, "ERROR": "already_submitted", "MESSAGE": "This rating has already been submitted." }));
|
|
abort;
|
|
}
|
|
|
|
// If GET request (or no rating data), return rating info for display
|
|
isSubmission = structKeyExists(data, "onTime") || structKeyExists(data, "completedScope")
|
|
|| structKeyExists(data, "requiredFollowup") || structKeyExists(data, "continueAllow")
|
|
|| structKeyExists(data, "prepared") || structKeyExists(data, "respectful")
|
|
|| structKeyExists(data, "wouldAutoAssign");
|
|
|
|
if (!isSubmission) {
|
|
// Return rating details for UI to display
|
|
result = {
|
|
"OK": true,
|
|
"RatingID": qRating.TaskRatingID,
|
|
"Direction": qRating.TaskRatingDirection,
|
|
"TaskTitle": qRating.TaskTitle,
|
|
"ForUserName": trim(qRating.ForUserFirstName & " " & qRating.ForUserLastName),
|
|
"ExpiresOn": dateTimeFormat(qRating.TaskRatingExpiresOn, "yyyy-mm-dd HH:nn:ss")
|
|
};
|
|
|
|
// Include appropriate questions based on direction
|
|
if (qRating.TaskRatingDirection == "customer_rates_worker" || qRating.TaskRatingDirection == "admin_rates_worker") {
|
|
result["Questions"] = {
|
|
"onTime": "Was the worker on time?",
|
|
"completedScope": "Was the scope completed?",
|
|
"requiredFollowup": "Was follow-up required?",
|
|
"continueAllow": "Continue to allow these tasks?"
|
|
};
|
|
} else if (qRating.TaskRatingDirection == "worker_rates_customer") {
|
|
result["Questions"] = {
|
|
"prepared": "Was the customer prepared?",
|
|
"completedScope": "Was the scope clear?",
|
|
"respectful": "Was the customer respectful?",
|
|
"wouldAutoAssign": "Would you serve this customer again?"
|
|
};
|
|
}
|
|
|
|
writeOutput(serializeJSON(result));
|
|
abort;
|
|
}
|
|
|
|
// Process submission based on direction
|
|
if (qRating.TaskRatingDirection == "customer_rates_worker" || qRating.TaskRatingDirection == "admin_rates_worker") {
|
|
queryExecute("
|
|
UPDATE TaskRatings SET
|
|
TaskRatingOnTime = ?,
|
|
TaskRatingCompletedScope = ?,
|
|
TaskRatingRequiredFollowup = ?,
|
|
TaskRatingContinueAllow = ?,
|
|
TaskRatingCompletedOn = NOW()
|
|
WHERE TaskRatingID = ?
|
|
", [
|
|
{ value = structKeyExists(data,"onTime") ? (data.onTime ? 1 : 0) : javaCast("null",""), cfsqltype = "cf_sql_tinyint", null = !structKeyExists(data,"onTime") },
|
|
{ value = structKeyExists(data,"completedScope") ? (data.completedScope ? 1 : 0) : javaCast("null",""), cfsqltype = "cf_sql_tinyint", null = !structKeyExists(data,"completedScope") },
|
|
{ value = structKeyExists(data,"requiredFollowup") ? (data.requiredFollowup ? 1 : 0) : javaCast("null",""), cfsqltype = "cf_sql_tinyint", null = !structKeyExists(data,"requiredFollowup") },
|
|
{ value = structKeyExists(data,"continueAllow") ? (data.continueAllow ? 1 : 0) : javaCast("null",""), cfsqltype = "cf_sql_tinyint", null = !structKeyExists(data,"continueAllow") },
|
|
{ value = qRating.TaskRatingID, cfsqltype = "cf_sql_integer" }
|
|
]);
|
|
} else if (qRating.TaskRatingDirection == "worker_rates_customer") {
|
|
queryExecute("
|
|
UPDATE TaskRatings SET
|
|
TaskRatingPrepared = ?,
|
|
TaskRatingCompletedScope = ?,
|
|
TaskRatingRespectful = ?,
|
|
TaskRatingWouldAutoAssign = ?,
|
|
TaskRatingCompletedOn = NOW()
|
|
WHERE TaskRatingID = ?
|
|
", [
|
|
{ value = structKeyExists(data,"prepared") ? (data.prepared ? 1 : 0) : javaCast("null",""), cfsqltype = "cf_sql_tinyint", null = !structKeyExists(data,"prepared") },
|
|
{ value = structKeyExists(data,"completedScope") ? (data.completedScope ? 1 : 0) : javaCast("null",""), cfsqltype = "cf_sql_tinyint", null = !structKeyExists(data,"completedScope") },
|
|
{ value = structKeyExists(data,"respectful") ? (data.respectful ? 1 : 0) : javaCast("null",""), cfsqltype = "cf_sql_tinyint", null = !structKeyExists(data,"respectful") },
|
|
{ value = structKeyExists(data,"wouldAutoAssign") ? (data.wouldAutoAssign ? 1 : 0) : javaCast("null",""), cfsqltype = "cf_sql_tinyint", null = !structKeyExists(data,"wouldAutoAssign") },
|
|
{ value = qRating.TaskRatingID, cfsqltype = "cf_sql_integer" }
|
|
]);
|
|
}
|
|
|
|
writeOutput(serializeJSON({
|
|
"OK": true,
|
|
"MESSAGE": "Rating submitted successfully. Thank you for your feedback!"
|
|
}));
|
|
|
|
} catch (any e) {
|
|
writeOutput(serializeJSON({
|
|
"OK": false,
|
|
"ERROR": "server_error",
|
|
"MESSAGE": "Error submitting rating",
|
|
"DETAIL": e.message
|
|
}));
|
|
}
|
|
</cfscript>
|