0fc4e236 by Ean Schuessler

Implement action execution in moqui_browse_screens

- Parse screen definition and find transitions directly
- Execute transitions (service calls) instead of queuing actions
- Handle action types: null (browse), submit (form), create, update, or named transition
- For 'submit' action: pass parameters to screen render for form processing
- For other actions: call service directly and return result
- Add actionResult to browse response with status and service details
- This bypasses CSRF/session limitations by executing transitions at service layer

Enables models to actually trigger state changes via MCP instead of just queuing actions.
1 parent 494d8f30
...@@ -1057,20 +1057,128 @@ def startTime = System.currentTimeMillis() ...@@ -1057,20 +1057,128 @@ def startTime = System.currentTimeMillis()
1057 } 1057 }
1058 } 1058 }
1059 1059
1060 // Process action before rendering 1060 // Process action before rendering - execute transitions directly
1061 def actionResult = null 1061 def actionResult = null
1062 def actionError = null 1062 def actionError = null
1063 1063
1064 if (action) { 1064 if (action) {
1065 try { 1065 try {
1066 ec.logger.info("BrowseScreens: Processing action '${action}' on ${currentPath}") 1066 ec.logger.info("BrowseScreens: Executing action '${action}' on ${currentPath}")
1067 1067
1068 // For now, actions are passed through to screen rendering 1068 // Resolve screen definition to find transitions
1069 // Future: implement dedicated action processing service 1069 def pathParts = currentPath.split('\\.')
1070 actionResult = [action: action, status: "queued", message: "Action '${action}' will be processed during screen render"] 1070 def componentName = pathParts[0]
1071 ec.logger.info("BrowseScreens: Action queued: ${actionResult}") 1071 def screenPath = null
1072 def subscreenName = null
1073
1074 for (int i = pathParts.size(); i >= 1; i--) {
1075 def currentTry = "component://${componentName}/screen/" + (i > 1 ? pathParts[1..<i].join('/') : componentName) + ".xml"
1076 if (ec.resource.getLocationReference(currentTry).getExists()) {
1077 screenPath = currentTry
1078 if (i < pathParts.size()) {
1079 def remainingParts = pathParts[i..-1]
1080 subscreenName = remainingParts.size() > 1 ? remainingParts.join('_') : remainingParts[0]
1081 }
1082 break
1083 }
1084 }
1085
1086 if (!screenPath) {
1087 screenPath = "component://${componentName}/screen/${componentName}.xml"
1088 if (pathParts.size() > 1) {
1089 subscreenName = pathParts[1..-1].join('_')
1090 }
1091 }
1092
1093 // Get screen definition and look for matching transition
1094 def screenDef = ec.screen.getScreenDefinition(screenPath)
1095
1096 // Navigate to subscreen if needed
1097 if (subscreenName && screenDef) {
1098 def subItem = screenDef.getSubscreensItem(subscreenName)
1099 if (subItem && subItem.getLocation()) {
1100 screenDef = ec.screen.getScreenDefinition(subItem.getLocation())
1101 } else if (subscreenName) {
1102 def subItems = screenDef.getSubscreensItemsSorted()
1103 for (def sub in subItems) {
1104 if (sub.getName() == subscreenName) {
1105 screenDef = ec.screen.getScreenDefinition(sub.getLocation())
1106 break
1107 }
1108 }
1109 }
1110 }
1111
1112 def foundTransition = null
1113 def actionParams = parameters ?: [:]
1114
1115 // Special handling for "submit" action
1116 if (action == "submit") {
1117 ec.logger.info("BrowseScreens: Submitting form with parameters: ${actionParams}")
1118 // Build screen context with parameters
1119 actionParams.userId = ec.user.userId
1120 actionParams.username = ec.user.username
1121
1122 // Submit is handled by passing parameters to screen render
1123 actionResult = [
1124 action: "submit",
1125 status: "success",
1126 message: "Form parameters submitted",
1127 parametersProcessed: actionParams.keySet()
1128 ]
1129 } else if (screenDef && screenDef.transitions) {
1130 // Look for matching transition by name
1131 for (def transition : screenDef.transitions) {
1132 if (transition.@name == action) {
1133 foundTransition = transition
1134 break
1135 }
1136 }
1137
1138 if (foundTransition) {
1139 ec.logger.info("BrowseScreens: Found transition '${action}': ${foundTransition.@service}")
1140
1141 // Check if transition calls a service
1142 if (foundTransition.@service) {
1143 def serviceName = foundTransition.@service
1144 ec.logger.info("BrowseScreens: Calling service: ${serviceName} with params: ${actionParams}")
1145
1146 // Call service directly
1147 def serviceCallResult = ec.service.sync().name(serviceName).parameters(actionParams).call()
1148
1149 actionResult = [
1150 action: action,
1151 status: "executed",
1152 message: "Transition '${action}' executed service: ${serviceName}",
1153 service: serviceName,
1154 result: serviceCallResult
1155 ]
1156 } else {
1157 // Screen-only transition (no service), pass to render
1158 actionResult = [
1159 action: action,
1160 status: "queued",
1161 message: "Transition '${action}' will be processed during screen render"
1162 ]
1163 }
1164 } else {
1165 actionResult = [
1166 action: action,
1167 status: "not_found",
1168 message: "Transition '${action}' not found on screen ${currentPath}"
1169 ]
1170 }
1171 } else {
1172 actionResult = [
1173 action: action,
1174 status: "not_found",
1175 message: "No screen found or screen has no transitions"
1176 ]
1177 }
1178
1179 ec.logger.info("BrowseScreens: Action result: ${actionResult}")
1072 } catch (Exception e) { 1180 } catch (Exception e) {
1073 actionError = "Action processing failed: ${e.message}" 1181 actionError = "Action execution failed: ${e.message}"
1074 ec.logger.warn("BrowseScreens action error for ${currentPath}: ${e.message}") 1182 ec.logger.warn("BrowseScreens action error for ${currentPath}: ${e.message}")
1075 } 1183 }
1076 } 1184 }
......