33352aca by Ean Schuessler

Extract actual entity IDs from row links for service calls

When pseudoId differs from the internal productId (e.g., DEMO_VAR_GR_SM_COPY
vs 100204), models were using the display ID for service calls causing FK errors.

Now extracts the actual entity ID from the row's link URL and includes it in
the row data:
- compact: {id: 'DEMO_VAR_GR_SM_COPY', productId: '100204', link: '...'}
- aria: {ref: 'DEMO_VAR_GR_SM_COPY', productId: '100204', ...}

This is a generic solution that works for any entity type by parsing the
primary key parameter from the Edit link URL.

Fixes QA issue where models used pseudoId for applyProductFeatures causing
'record does not exist' referential integrity errors.
1 parent 7f95b312
...@@ -975,20 +975,41 @@ def convertToAriaTree = { semanticState, targetScreenPath -> ...@@ -975,20 +975,41 @@ def convertToAriaTree = { semanticState, targetScreenPath ->
975 gridNode.children = [] 975 gridNode.children = []
976 listData.each { row -> 976 listData.each { row ->
977 def rowNode = [role: "row"] 977 def rowNode = [role: "row"]
978 // Extract key identifying info - prefer specific IDs over generic productId 978 // Extract display ID - prefer human-readable pseudoId for display
979 // For features: productFeatureId, for assocs: toProductId, etc. 979 def displayId = row.pseudoId ?: row.toProductId ?: row.productFeatureId ?:
980 def id = row.pseudoId ?: row.toProductId ?: row.productFeatureId ?:
981 row.partyId ?: row.orderId ?: row.id 980 row.partyId ?: row.orderId ?: row.id
982 // Avoid using productId as ref when it's the same for all rows (e.g., feature list) 981 // Avoid using productId as ref when it's the same for all rows (e.g., feature list)
983 if (!id && row.productId) { 982 if (!displayId && row.productId) {
984 // Only use productId if there's no better identifier 983 // Only use productId if there's no better identifier
985 id = row.productId 984 displayId = row.productId
986 } 985 }
987 def name = row.combinedName ?: row.name ?: row.productName ?: 986 def name = row.combinedName ?: row.name ?: row.productName ?:
988 row.description ?: row.abbrev 987 row.description ?: row.abbrev
989 if (id) rowNode.ref = id 988 if (displayId) rowNode.ref = displayId
990 if (name) rowNode.name = name 989 if (name) rowNode.name = name
991 if (id && name && id != name) rowNode.description = id 990 if (displayId && name && displayId != name) rowNode.description = displayId
991
992 // Extract the primary key from the row's link URL (if any)
993 // This is the actual entity ID that services need, which may differ from pseudoId
994 // Match by displayId in path OR by link text (handles pseudoId != productId case)
995 def rowLink = data.links?.find { link ->
996 link.type == "navigation" && link.path?.contains("Edit") && (
997 link.path?.contains(displayId?.toString()) ||
998 link.text == displayId?.toString() ||
999 link.text == name
1000 )
1001 }
1002 if (rowLink?.path) {
1003 def matcher = rowLink.path =~ /(\w+Id)=([^&]+)/
1004 if (matcher.find()) {
1005 def paramName = matcher.group(1)
1006 def paramValue = matcher.group(2)
1007 if (paramValue && paramValue != displayId?.toString()) {
1008 rowNode[paramName] = paramValue
1009 }
1010 }
1011 }
1012
992 gridNode.children << rowNode 1013 gridNode.children << rowNode
993 } 1014 }
994 } 1015 }
...@@ -1211,17 +1232,16 @@ def convertToCompactFormat = { semanticState, targetScreenPath -> ...@@ -1211,17 +1232,16 @@ def convertToCompactFormat = { semanticState, targetScreenPath ->
1211 gridInfo.rows = listData.take(10).collect { row -> 1232 gridInfo.rows = listData.take(10).collect { row ->
1212 def rowInfo = [:] 1233 def rowInfo = [:]
1213 1234
1214 // Get identifying info - prefer specific IDs over generic productId 1235 // Get display ID - prefer human-readable pseudoId for display
1215 // For features: productFeatureId, for assocs: toProductId, etc. 1236 def displayId = row.pseudoId ?: row.toProductId ?: row.productFeatureId ?:
1216 def id = row.pseudoId ?: row.toProductId ?: row.productFeatureId ?:
1217 row.partyId ?: row.orderId ?: row.communicationEventId ?: row.id 1237 row.partyId ?: row.orderId ?: row.communicationEventId ?: row.id
1218 // Avoid using productId as id when it's the same for all rows (e.g., feature list) 1238 // Avoid using productId as displayId when it's the same for all rows (e.g., feature list)
1219 if (!id && row.productId) id = row.productId 1239 if (!displayId && row.productId) displayId = row.productId
1220 def name = row.combinedName ?: row.productName ?: row.organizationName ?: 1240 def name = row.combinedName ?: row.productName ?: row.organizationName ?:
1221 row.subject ?: row.name ?: row.description ?: row.abbrev 1241 row.subject ?: row.name ?: row.description ?: row.abbrev
1222 1242
1223 if (id) rowInfo.id = id 1243 if (displayId) rowInfo.id = displayId
1224 if (name && name != id) rowInfo.name = name 1244 if (name && name != displayId) rowInfo.name = name
1225 1245
1226 // Add key display values (2-3 additional fields beyond id/name) 1246 // Add key display values (2-3 additional fields beyond id/name)
1227 // Priority: status, type, date, amount, role, class fields 1247 // Priority: status, type, date, amount, role, class fields
...@@ -1263,14 +1283,32 @@ def convertToCompactFormat = { semanticState, targetScreenPath -> ...@@ -1263,14 +1283,32 @@ def convertToCompactFormat = { semanticState, targetScreenPath ->
1263 1283
1264 if (extraFields) rowInfo.data = extraFields 1284 if (extraFields) rowInfo.data = extraFields
1265 1285
1266 // Find link for this row 1286 // Find link for this row and extract the primary key for service calls
1287 // Match by displayId in path OR by link text (handles pseudoId != productId case)
1267 def rowLinks = data.links?.findAll { link -> 1288 def rowLinks = data.links?.findAll { link ->
1268 link.path?.contains(id?.toString()) && link.type == "navigation" 1289 link.type == "navigation" && (
1290 link.path?.contains(displayId?.toString()) ||
1291 link.text == displayId?.toString() ||
1292 link.text == name
1293 )
1269 } 1294 }
1270 if (rowLinks && rowLinks.size() > 0) { 1295 if (rowLinks && rowLinks.size() > 0) {
1271 // Pick the most relevant link (edit/view) 1296 // Pick the most relevant link (edit/view)
1272 def editLink = rowLinks.find { it.path?.contains("Edit") } 1297 def editLink = rowLinks.find { it.path?.contains("Edit") }
1273 rowInfo.link = (editLink ?: rowLinks[0]).path 1298 def linkPath = (editLink ?: rowLinks[0]).path
1299 rowInfo.link = linkPath
1300
1301 // Extract the primary key from the link URL (e.g., productId=100204)
1302 // This is the actual entity ID that services need, which may differ from pseudoId
1303 def matcher = linkPath =~ /(\w+Id)=([^&]+)/
1304 if (matcher.find()) {
1305 def paramName = matcher.group(1)
1306 def paramValue = matcher.group(2)
1307 // Only include if it differs from the display ID (avoids redundancy)
1308 if (paramValue && paramValue != displayId?.toString()) {
1309 rowInfo[paramName] = paramValue
1310 }
1311 }
1274 } 1312 }
1275 1313
1276 rowInfo 1314 rowInfo
......