// Create or update a task type for a business // Input: BusinessID (required), TaskTypeName (required), TaskTypeDescription (optional), TaskTypeIcon (optional), TaskTypeID (optional - for update) // Output: { OK: true, TASK_TYPE_ID: ... } 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(); // Get BusinessID businessID = 0; if (structKeyExists(data, "BusinessID") && isNumeric(data.BusinessID)) { businessID = int(data.BusinessID); } if (businessID == 0) { apiAbort({ "OK": false, "ERROR": "missing_params", "MESSAGE": "BusinessID is required" }); } // Get TaskTypeName taskTypeName = ""; if (structKeyExists(data, "TaskTypeName")) { taskTypeName = trim(toString(data.TaskTypeName)); } if (!len(taskTypeName)) { apiAbort({ "OK": false, "ERROR": "missing_params", "MESSAGE": "TaskTypeName is required" }); } if (len(taskTypeName) > 45) { apiAbort({ "OK": false, "ERROR": "invalid_params", "MESSAGE": "TaskTypeName must be 45 characters or less" }); } // Get TaskTypeDescription (optional) taskTypeDescription = ""; if (structKeyExists(data, "TaskTypeDescription")) { taskTypeDescription = trim(toString(data.TaskTypeDescription)); } if (len(taskTypeDescription) > 100) { apiAbort({ "OK": false, "ERROR": "invalid_params", "MESSAGE": "TaskTypeDescription must be 100 characters or less" }); } // Get TaskTypeIcon (optional, default to 'notifications') taskTypeIcon = "notifications"; if (structKeyExists(data, "TaskTypeIcon") && len(trim(toString(data.TaskTypeIcon)))) { taskTypeIcon = trim(toString(data.TaskTypeIcon)); } if (len(taskTypeIcon) > 30) { apiAbort({ "OK": false, "ERROR": "invalid_params", "MESSAGE": "TaskTypeIcon must be 30 characters or less" }); } // Get TaskTypeColor (optional, default to purple) taskTypeColor = "##9C27B0"; if (structKeyExists(data, "TaskTypeColor") && len(trim(toString(data.TaskTypeColor)))) { taskTypeColor = trim(toString(data.TaskTypeColor)); // Ensure it starts with # (incoming JSON has single #, we store single #) if (left(taskTypeColor, 1) != chr(35)) { taskTypeColor = chr(35) & taskTypeColor; } } if (len(taskTypeColor) > 7) { apiAbort({ "OK": false, "ERROR": "invalid_params", "MESSAGE": "TaskTypeColor must be a valid hex color" }); } // Get TaskTypeCategoryID (optional - links to TaskCategories for task creation) taskTypeCategoryID = javaCast("null", ""); if (structKeyExists(data, "TaskTypeCategoryID") && isNumeric(data.TaskTypeCategoryID) && data.TaskTypeCategoryID > 0) { taskTypeCategoryID = int(data.TaskTypeCategoryID); } else if (structKeyExists(data, "CategoryID") && isNumeric(data.CategoryID) && data.CategoryID > 0) { taskTypeCategoryID = int(data.CategoryID); } // Get TaskTypeID (optional - for update) taskTypeID = 0; if (structKeyExists(data, "TaskTypeID") && isNumeric(data.TaskTypeID)) { taskTypeID = int(data.TaskTypeID); } if (taskTypeID > 0) { // UPDATE - verify it belongs to this business qCheck = queryTimed(" SELECT ID FROM tt_TaskTypes WHERE ID = :taskTypeID AND BusinessID = :businessID ", { taskTypeID: { value: taskTypeID, cfsqltype: "cf_sql_integer" }, businessID: { value: businessID, cfsqltype: "cf_sql_integer" } }, { datasource: "payfrit" }); if (qCheck.recordCount == 0) { apiAbort({ "OK": false, "ERROR": "not_found", "MESSAGE": "Task type not found or does not belong to this business" }); } queryTimed(" UPDATE tt_TaskTypes SET Name = :taskTypeName, Description = :taskTypeDescription, Icon = :taskTypeIcon, Color = :taskTypeColor, TaskCategoryID = :categoryID WHERE ID = :taskTypeID ", { taskTypeName: { value: taskTypeName, cfsqltype: "cf_sql_varchar" }, taskTypeDescription: { value: taskTypeDescription, cfsqltype: "cf_sql_varchar", null: !len(taskTypeDescription) }, taskTypeIcon: { value: taskTypeIcon, cfsqltype: "cf_sql_varchar" }, taskTypeColor: { value: taskTypeColor, cfsqltype: "cf_sql_varchar" }, categoryID: { value: taskTypeCategoryID, cfsqltype: "cf_sql_integer", null: isNull(taskTypeCategoryID) }, taskTypeID: { value: taskTypeID, cfsqltype: "cf_sql_integer" } }, { datasource: "payfrit" }); apiAbort({ "OK": true, "TASK_TYPE_ID": taskTypeID, "MESSAGE": "Task type updated" }); } else { // INSERT new task type queryTimed(" INSERT INTO tt_TaskTypes (Name, Description, Icon, Color, BusinessID, TaskCategoryID) VALUES (:taskTypeName, :taskTypeDescription, :taskTypeIcon, :taskTypeColor, :businessID, :categoryID) ", { taskTypeName: { value: taskTypeName, cfsqltype: "cf_sql_varchar" }, taskTypeDescription: { value: taskTypeDescription, cfsqltype: "cf_sql_varchar", null: !len(taskTypeDescription) }, taskTypeIcon: { value: taskTypeIcon, cfsqltype: "cf_sql_varchar" }, taskTypeColor: { value: taskTypeColor, cfsqltype: "cf_sql_varchar" }, businessID: { value: businessID, cfsqltype: "cf_sql_integer" }, categoryID: { value: taskTypeCategoryID, cfsqltype: "cf_sql_integer", null: isNull(taskTypeCategoryID) } }, { datasource: "payfrit" }); qNew = queryTimed("SELECT LAST_INSERT_ID() as newID", [], { datasource: "payfrit" }); newID = qNew.newID; apiAbort({ "OK": true, "TASK_TYPE_ID": newID, "MESSAGE": "Task type created" }); } } catch (any e) { apiAbort({ "OK": false, "ERROR": "server_error", "MESSAGE": e.message }); }