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 })); }