实现 M002 V3 字段矩阵 room_items 迁移
This commit is contained in:
@@ -546,7 +546,7 @@ V3 字段说明:
|
||||
- 业务 event 能派生到稳定路由时,复用现有订单 / 任务 / 任务卡创建链路。
|
||||
- event 判别字段不完整、显式携带 `contract_errors`、根 `missing_fields`、不完整 `manual_review` 或不完整 parent split 候选时,写入 `adapter_contract_error` transition,不创建订单和任务;同一邮件其他 sibling event 继续处理。
|
||||
- `unhandled_current_intents[]` 写入 `UNHANDLED_CURRENT_INTENT` transition,不返回 `adapter_error_code`。
|
||||
- type-known manual review 第一版在同一业务任务卡解阻;当前字段矩阵仍使用扁平字段,`/extracted_fields/room_items/{index}/pms_room_type_code` 会兼容映射到 `extracted_fields.pms_room_type_code`。
|
||||
- type-known manual review 第一版在同一业务任务卡解阻;New Booking 房型字段主路径已迁移为 `room_items[0]`,例如 `/extracted_fields/room_items/0/pms_room_type_code`。旧扁平字段仍可作为过渡提交 key,但响应会归一化为 P0 主 `field_path`。
|
||||
|
||||
`source_message.source_message_id` 缺失时返回 `HTTP 400`,响应体不使用通用错误包装:
|
||||
|
||||
|
||||
@@ -264,7 +264,7 @@ V2 一条 `ai_task_results[]` item 对应一行。M002 V3 后,`message_events[
|
||||
| `hotel_id` | `VARCHAR(64)` | 酒店或业务上下文 |
|
||||
| `task_id` | `BIGINT` | 所属任务 |
|
||||
| `task_card_type` | `VARCHAR(64)` | 任务卡类型 |
|
||||
| `field_contract_version` | `VARCHAR(64)` | 字段契约版本,第一版可写 `code-v1` |
|
||||
| `field_contract_version` | `VARCHAR(64)` | 字段契约版本,当前 P0 字段矩阵写 `20260711-p0` |
|
||||
| `ai_payload_json` | `LONGTEXT` | 任务卡使用的 AI 原始 JSON 快照 |
|
||||
| `draft_payload_json` | `LONGTEXT` | 用户编辑草稿,可为空 |
|
||||
| `confirmed_payload_json` | `LONGTEXT` | 用户确认后的最终 payload |
|
||||
|
||||
@@ -4,7 +4,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 任务卡草稿保存和最终确认请求。第一版 field_values 使用矩阵 field_path 作为 key,不使用 write_path 生成 OPERA 参数。
|
||||
* 任务卡草稿保存和最终确认请求。field_values 支持矩阵 field_path、RFC 6901 pointer 和旧扁平字段兼容 key。
|
||||
*/
|
||||
public record ReservationTaskPayloadMutationRequest(
|
||||
@JsonProperty("field_values")
|
||||
|
||||
@@ -6,7 +6,8 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
* 同卡人工复核字段修正结果。返回 JSON Pointer 与矩阵 field_path 的对应关系。
|
||||
*
|
||||
* @param fieldPointer 请求中的 JSON Pointer
|
||||
* @param fieldPath 后端矩阵 field_path
|
||||
* @param fieldPath 后端矩阵主 field_path
|
||||
* @param legacyFieldPath 旧扁平字段路径,仅用于前端过渡兼容
|
||||
* @param value 人工修正后的字段值
|
||||
*/
|
||||
public record ManualReviewResolutionFieldOverrideResult(
|
||||
@@ -14,6 +15,8 @@ public record ManualReviewResolutionFieldOverrideResult(
|
||||
String fieldPointer,
|
||||
@JsonProperty("field_path")
|
||||
String fieldPath,
|
||||
@JsonProperty("legacy_field_path")
|
||||
String legacyFieldPath,
|
||||
Object value
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -11,7 +11,9 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
* @param taskType 任务类型白名单值
|
||||
* @param taskSubtype 任务 subtype 白名单值
|
||||
* @param displayArea 前端展示区域
|
||||
* @param fieldPath 字段路径
|
||||
* @param fieldPath 后端矩阵主字段路径,P0 起 room_items 使用显式 0 下标路径
|
||||
* @param fieldPointer RFC 6901 JSON Pointer,前端复核和编辑定位优先使用
|
||||
* @param legacyFieldPath 旧扁平字段路径,仅用于前端过渡兼容
|
||||
* @param displayName 字段展示名称
|
||||
* @param visible 是否可见
|
||||
* @param editable 是否可编辑
|
||||
@@ -47,6 +49,10 @@ public record ReservationTaskFieldResult(
|
||||
String displayArea,
|
||||
@JsonProperty("field_path")
|
||||
String fieldPath,
|
||||
@JsonProperty("field_pointer")
|
||||
String fieldPointer,
|
||||
@JsonProperty("legacy_field_path")
|
||||
String legacyFieldPath,
|
||||
@JsonProperty("display_name")
|
||||
String displayName,
|
||||
String visible,
|
||||
|
||||
@@ -4,7 +4,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 任务卡草稿保存或最终确认结果。payload 为第一版 field_path 简单结构,供前端回显和后续 OPERA 转换使用。
|
||||
* 任务卡草稿保存或最终确认结果。payload 同时包含主 field_values、兼容 legacy_field_values 和嵌套 effective_payload。
|
||||
*/
|
||||
public record ReservationTaskPayloadMutationResult(
|
||||
@JsonProperty("task_id")
|
||||
|
||||
@@ -20,7 +20,7 @@ public class ReservationTaskCardEntity {
|
||||
private Long taskId;
|
||||
/** 任务卡类型。 */
|
||||
private String taskCardType;
|
||||
/** 字段契约版本,第一版使用 code-v1。 */
|
||||
/** 字段契约版本,当前 P0 字段矩阵使用 20260711-p0。 */
|
||||
private String fieldContractVersion;
|
||||
/** 任务卡使用的 AI 原始 JSON 快照。 */
|
||||
private String aiPayloadJson;
|
||||
|
||||
@@ -50,7 +50,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
@Service
|
||||
public class ReservationAiTaskIntakeServiceImpl implements ReservationAiTaskIntakeService {
|
||||
|
||||
private static final String FIELD_CONTRACT_VERSION = "code-v1";
|
||||
private static final String FIELD_CONTRACT_VERSION = "20260711-p0";
|
||||
private static final String BATCH_KEY_PREFIX = "superagent-task-result-batch:v1";
|
||||
private static final String ITEM_KEY_PREFIX = "superagent-task-result-item:v1";
|
||||
private static final String SOURCE_MESSAGE_ONLY_RESULT_TYPE = "source_message_only";
|
||||
|
||||
@@ -48,6 +48,8 @@ import cn.nianxx.thhotel.workflows.reservation.service.ReservationTaskCardFieldD
|
||||
import cn.nianxx.thhotel.workflows.reservation.service.ReservationTaskWorkflowService;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
@@ -79,7 +81,7 @@ public class ReservationTaskWorkflowServiceImpl implements ReservationTaskWorkfl
|
||||
private static final String ACTION_OPERA_OPERATION_RETRY = "OPERA_OPERATION_RETRY";
|
||||
private static final String ACTOR_TYPE_USER = "USER";
|
||||
private static final String ACTOR_ID_LOCAL = "local-user";
|
||||
private static final String PAYLOAD_SCHEMA_VERSION = "field_path-v1";
|
||||
private static final String PAYLOAD_SCHEMA_VERSION = "field_matrix-p0-room-items-v1";
|
||||
private static final String PAYLOAD_SOURCE_DRAFT = "TASK_DRAFT";
|
||||
private static final String PAYLOAD_SOURCE_CONFIRMATION = "TASK_CONFIRMATION";
|
||||
private static final String PAYLOAD_SOURCE_MANUAL_REVIEW_RESOLUTION = "MANUAL_REVIEW_RESOLUTION";
|
||||
@@ -202,8 +204,14 @@ public class ReservationTaskWorkflowServiceImpl implements ReservationTaskWorkfl
|
||||
ensureTaskEditable(task);
|
||||
|
||||
JsonNode aiPayload = parseJson(taskCard.aiPayloadJson());
|
||||
Map<String, Object> submittedValues = normalizeFieldValues(request == null ? null : request.fieldValues());
|
||||
Map<String, Object> draftValues = fieldValuesFromPayloadJson(taskCard.draftPayloadJson());
|
||||
List<ReservationTaskCardFieldDefinition> allDefinitions =
|
||||
fieldDefinitionProvider.listDefinitions(task.taskCardType(), task.resultType());
|
||||
Map<String, Object> submittedValues = normalizeSubmittedFieldValues(
|
||||
allDefinitions,
|
||||
request == null ? null : request.fieldValues());
|
||||
Map<String, Object> draftValues = normalizeStoredFieldValues(
|
||||
allDefinitions,
|
||||
fieldValuesFromPayloadJson(taskCard.draftPayloadJson()));
|
||||
Map<String, Object> mergedDraftValues = mergeFieldValues(draftValues, submittedValues);
|
||||
List<ReservationTaskCardFieldDefinition> activeDefinitions =
|
||||
activeDefinitions(task, aiPayload, mergedDraftValues);
|
||||
@@ -244,13 +252,19 @@ public class ReservationTaskWorkflowServiceImpl implements ReservationTaskWorkfl
|
||||
ensureGenericConfirmAllowed(task, taskCard);
|
||||
|
||||
JsonNode aiPayload = parseJson(taskCard.aiPayloadJson());
|
||||
Map<String, Object> submittedValues = normalizeFieldValues(request == null ? null : request.fieldValues());
|
||||
Map<String, Object> draftValues = fieldValuesFromPayloadJson(taskCard.draftPayloadJson());
|
||||
List<ReservationTaskCardFieldDefinition> allDefinitions =
|
||||
fieldDefinitionProvider.listDefinitions(task.taskCardType(), task.resultType());
|
||||
Map<String, Object> submittedValues = normalizeSubmittedFieldValues(
|
||||
allDefinitions,
|
||||
request == null ? null : request.fieldValues());
|
||||
Map<String, Object> draftValues = normalizeStoredFieldValues(
|
||||
allDefinitions,
|
||||
fieldValuesFromPayloadJson(taskCard.draftPayloadJson()));
|
||||
Map<String, Object> editedValues = mergeFieldValues(draftValues, submittedValues);
|
||||
List<ReservationTaskCardFieldDefinition> activeDefinitions = activeDefinitions(task, aiPayload, editedValues);
|
||||
List<String> validationErrors = validateSubmittedFields(activeDefinitions, submittedValues);
|
||||
validationErrors.addAll(validateDefinitionValues(task, activeDefinitions, aiPayload, editedValues,
|
||||
definitionFieldPaths(activeDefinitions), true));
|
||||
definitionFieldPaths(activeDefinitions), true, true));
|
||||
validationErrors.addAll(validateDateRange(activeDefinitions, aiPayload, editedValues));
|
||||
if (!validationErrors.isEmpty()) {
|
||||
throw validationError(validationErrors);
|
||||
@@ -818,6 +832,7 @@ public class ReservationTaskWorkflowServiceImpl implements ReservationTaskWorkfl
|
||||
results.add(new ManualReviewResolutionFieldOverrideResult(
|
||||
fieldPointer,
|
||||
definition.fieldPath(),
|
||||
legacyFieldPathFor(definition.fieldPath()),
|
||||
request.value()));
|
||||
}
|
||||
return results;
|
||||
@@ -869,7 +884,7 @@ public class ReservationTaskWorkflowServiceImpl implements ReservationTaskWorkfl
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 RFC 6901 JSON Pointer 转换为可能的矩阵 field_path,兼容数组下标到 [] 的映射。
|
||||
* 将 RFC 6901 JSON Pointer 转换为可能的矩阵 field_path,兼容数组下标到 [] 与旧扁平路径。
|
||||
*/
|
||||
private List<String> fieldPathCandidatesFromPointer(String fieldPointer) {
|
||||
String[] rawTokens = fieldPointer.substring(1).split("/", -1);
|
||||
@@ -898,22 +913,27 @@ public class ReservationTaskWorkflowServiceImpl implements ReservationTaskWorkfl
|
||||
if (!collapsed.equals(candidates.get(0))) {
|
||||
candidates.add(collapsed);
|
||||
}
|
||||
String legacyAlias = p0ReviewPointerLegacyAlias(candidates.get(0), collapsed);
|
||||
if (legacyAlias != null && !candidates.contains(legacyAlias)) {
|
||||
candidates.add(legacyAlias);
|
||||
for (String legacyAlias : p0PointerFieldPathAliases(candidates.get(0), collapsed)) {
|
||||
if (!candidates.contains(legacyAlias)) {
|
||||
candidates.add(legacyAlias);
|
||||
}
|
||||
}
|
||||
return candidates;
|
||||
}
|
||||
|
||||
/**
|
||||
* 兼容 0711 P0 fixtures 中已迁移为数组结构、但当前后端矩阵仍是扁平字段的复核 pointer。
|
||||
* 兼容 P0 room_items[] 和旧扁平字段在复核指针中的双向过渡。
|
||||
*/
|
||||
private String p0ReviewPointerLegacyAlias(String plainFieldPath, String collapsedFieldPath) {
|
||||
if ("extracted_fields.room_items.0.pms_room_type_code".equals(plainFieldPath)
|
||||
&& "extracted_fields.room_items[].pms_room_type_code".equals(collapsedFieldPath)) {
|
||||
return "extracted_fields.pms_room_type_code";
|
||||
private List<String> p0PointerFieldPathAliases(String plainFieldPath, String collapsedFieldPath) {
|
||||
String legacyAlias = legacyFieldPathFor(plainFieldPath);
|
||||
if (legacyAlias != null) {
|
||||
return List.of(legacyAlias);
|
||||
}
|
||||
return null;
|
||||
String canonicalAlias = canonicalP0RoomItemFieldPath(plainFieldPath);
|
||||
if (canonicalAlias != null) {
|
||||
return List.of(canonicalAlias);
|
||||
}
|
||||
return List.of();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1053,14 +1073,82 @@ public class ReservationTaskWorkflowServiceImpl implements ReservationTaskWorkfl
|
||||
}
|
||||
|
||||
/**
|
||||
* 标准化请求中的 field_values。空请求按空草稿处理。
|
||||
* 标准化请求中的 field_values。支持前端传矩阵 field_path、RFC 6901 pointer 或旧扁平字段。
|
||||
*/
|
||||
private Map<String, Object> normalizeFieldValues(Map<String, Object> rawFieldValues) {
|
||||
private Map<String, Object> normalizeSubmittedFieldValues(
|
||||
List<ReservationTaskCardFieldDefinition> definitions,
|
||||
Map<String, Object> rawFieldValues) {
|
||||
Map<String, Object> normalized = new LinkedHashMap<>();
|
||||
if (rawFieldValues == null) {
|
||||
return normalized;
|
||||
}
|
||||
rawFieldValues.forEach((fieldPath, value) -> normalized.put(fieldPath == null ? "" : fieldPath.trim(), value));
|
||||
Map<String, ReservationTaskCardFieldDefinition> definitionByPath = definitionByPath(definitions);
|
||||
List<String> errors = new ArrayList<>();
|
||||
rawFieldValues.forEach((rawFieldPath, value) -> {
|
||||
String fieldPath = rawFieldPath == null ? "" : rawFieldPath.trim();
|
||||
String canonicalFieldPath = canonicalSubmittedFieldPath(definitionByPath, fieldPath);
|
||||
if (normalized.containsKey(canonicalFieldPath)) {
|
||||
errors.add(fieldPath + ": 与其他提交字段指向同一字段 " + canonicalFieldPath + "。");
|
||||
return;
|
||||
}
|
||||
normalized.put(canonicalFieldPath, value);
|
||||
});
|
||||
if (!errors.isEmpty()) {
|
||||
throw validationError(errors);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标准化已保存草稿里的历史字段 key,避免旧 payload 影响 P0 新矩阵校验。
|
||||
*/
|
||||
private Map<String, Object> normalizeStoredFieldValues(
|
||||
List<ReservationTaskCardFieldDefinition> definitions,
|
||||
Map<String, Object> storedFieldValues) {
|
||||
Map<String, ReservationTaskCardFieldDefinition> definitionByPath = definitionByPath(definitions);
|
||||
Map<String, Object> normalized = new LinkedHashMap<>();
|
||||
storedFieldValues.forEach((fieldPath, value) -> {
|
||||
String canonicalFieldPath = canonicalSubmittedFieldPath(definitionByPath, fieldPath);
|
||||
normalized.put(canonicalFieldPath, value);
|
||||
});
|
||||
return normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建字段定义索引,供提交字段和复核 pointer 映射使用。
|
||||
*/
|
||||
private Map<String, ReservationTaskCardFieldDefinition> definitionByPath(
|
||||
List<ReservationTaskCardFieldDefinition> definitions) {
|
||||
Map<String, ReservationTaskCardFieldDefinition> definitionByPath = new LinkedHashMap<>();
|
||||
definitions.forEach(definition -> definitionByPath.put(definition.fieldPath(), definition));
|
||||
return definitionByPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将提交字段 key 转成当前矩阵主 field_path;未知字段保留原值,后续由矩阵校验报错。
|
||||
*/
|
||||
private String canonicalSubmittedFieldPath(
|
||||
Map<String, ReservationTaskCardFieldDefinition> definitionByPath,
|
||||
String fieldPathOrPointer) {
|
||||
String normalized = trimToNull(fieldPathOrPointer);
|
||||
if (normalized == null) {
|
||||
return "";
|
||||
}
|
||||
if (normalized.startsWith("/")) {
|
||||
for (String candidate : fieldPathCandidatesFromPointer(normalized)) {
|
||||
if (definitionByPath.containsKey(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
if (definitionByPath.containsKey(normalized)) {
|
||||
return normalized;
|
||||
}
|
||||
String canonicalAlias = canonicalP0RoomItemFieldPath(normalized);
|
||||
if (canonicalAlias != null && definitionByPath.containsKey(canonicalAlias)) {
|
||||
return canonicalAlias;
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
@@ -1240,7 +1328,7 @@ public class ReservationTaskWorkflowServiceImpl implements ReservationTaskWorkfl
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成第一版任务卡 payload JSON。当前按 field_path 保存,后续 OPERA 转换层再消费。
|
||||
* 生成任务卡 payload JSON。field_values 保留矩阵主路径,effective_payload 给后续 OPERA 映射使用。
|
||||
*/
|
||||
private String toPayloadJson(String generatedFrom, Map<String, Object> fieldValues, LocalDateTime now) {
|
||||
Map<String, Object> payload = new LinkedHashMap<>();
|
||||
@@ -1248,6 +1336,8 @@ public class ReservationTaskWorkflowServiceImpl implements ReservationTaskWorkfl
|
||||
payload.put("generated_from", generatedFrom);
|
||||
payload.put("generated_at", now.toString());
|
||||
payload.put("field_values", new LinkedHashMap<>(fieldValues));
|
||||
payload.put("legacy_field_values", legacyFieldValues(fieldValues));
|
||||
payload.put("effective_payload", effectivePayload(fieldValues));
|
||||
try {
|
||||
return objectMapper.writeValueAsString(payload);
|
||||
} catch (Exception exception) {
|
||||
@@ -1255,6 +1345,98 @@ public class ReservationTaskWorkflowServiceImpl implements ReservationTaskWorkfl
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成旧扁平字段兼容值,供前端过渡读取;主业务逻辑不再依赖这些 key。
|
||||
*/
|
||||
private Map<String, Object> legacyFieldValues(Map<String, Object> fieldValues) {
|
||||
Map<String, Object> legacyValues = new LinkedHashMap<>();
|
||||
fieldValues.forEach((fieldPath, value) -> {
|
||||
String legacyFieldPath = legacyFieldPathFor(fieldPath);
|
||||
if (legacyFieldPath != null) {
|
||||
legacyValues.put(legacyFieldPath, value);
|
||||
}
|
||||
});
|
||||
return legacyValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按矩阵 field_path 生成嵌套 effective payload,后续 OPERA 参数组装优先消费该结构。
|
||||
*/
|
||||
private Object effectivePayload(Map<String, Object> fieldValues) {
|
||||
ObjectNode root = objectMapper.createObjectNode();
|
||||
fieldValues.forEach((fieldPath, value) -> setEffectivePayloadValue(root, fieldPath, objectMapper.valueToTree(value)));
|
||||
return objectMapper.convertValue(root, Object.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将单个点号 field_path 写入嵌套 JSON,支持 room_items.0 这类显式数组下标。
|
||||
*/
|
||||
private void setEffectivePayloadValue(ObjectNode root, String fieldPath, JsonNode valueNode) {
|
||||
if (trimToNull(fieldPath) == null) {
|
||||
return;
|
||||
}
|
||||
setEffectivePayloadValue(root, fieldPath.split("\\."), 0, valueNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归写入嵌套 effective payload。
|
||||
*/
|
||||
private void setEffectivePayloadValue(JsonNode current, String[] rawParts, int index, JsonNode valueNode) {
|
||||
if (index >= rawParts.length) {
|
||||
return;
|
||||
}
|
||||
String part = rawParts[index].replace("[]", "");
|
||||
boolean last = index == rawParts.length - 1;
|
||||
if (current instanceof ObjectNode objectNode) {
|
||||
if (last) {
|
||||
objectNode.set(part, valueNode);
|
||||
return;
|
||||
}
|
||||
String nextPart = rawParts[index + 1].replace("[]", "");
|
||||
JsonNode child = objectNode.path(part);
|
||||
if (child.isMissingNode() || child.isNull() || !matchesExpectedContainer(child, nextPart)) {
|
||||
child = isNumericToken(nextPart) ? objectMapper.createArrayNode() : objectMapper.createObjectNode();
|
||||
objectNode.set(part, child);
|
||||
}
|
||||
setEffectivePayloadValue(child, rawParts, index + 1, valueNode);
|
||||
return;
|
||||
}
|
||||
if (current instanceof ArrayNode arrayNode) {
|
||||
if (!isNumericToken(part)) {
|
||||
return;
|
||||
}
|
||||
int arrayIndex = Integer.parseInt(part);
|
||||
ensureArraySize(arrayNode, arrayIndex);
|
||||
if (last) {
|
||||
arrayNode.set(arrayIndex, valueNode);
|
||||
return;
|
||||
}
|
||||
String nextPart = rawParts[index + 1].replace("[]", "");
|
||||
JsonNode child = arrayNode.get(arrayIndex);
|
||||
if (child == null || child.isNull() || !matchesExpectedContainer(child, nextPart)) {
|
||||
child = isNumericToken(nextPart) ? objectMapper.createArrayNode() : objectMapper.createObjectNode();
|
||||
arrayNode.set(arrayIndex, child);
|
||||
}
|
||||
setEffectivePayloadValue(child, rawParts, index + 1, valueNode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前 JSON 容器是否匹配下一段路径需要的对象或数组。
|
||||
*/
|
||||
private boolean matchesExpectedContainer(JsonNode child, String nextPart) {
|
||||
return isNumericToken(nextPart) ? child.isArray() : child.isObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* 扩展数组到指定下标,缺位填 null。
|
||||
*/
|
||||
private void ensureArraySize(ArrayNode arrayNode, int index) {
|
||||
while (arrayNode.size() <= index) {
|
||||
arrayNode.addNull();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将已保存 payload JSON 转为响应对象。空 payload 返回 null,避免前端误认为已有内容。
|
||||
*/
|
||||
@@ -1732,11 +1914,16 @@ public class ReservationTaskWorkflowServiceImpl implements ReservationTaskWorkfl
|
||||
if (editedValues.containsKey(fieldPath)) {
|
||||
return editedValues.get(fieldPath);
|
||||
}
|
||||
for (String aliasPath : p0ValuePathCandidates(fieldPath)) {
|
||||
if (editedValues.containsKey(aliasPath)) {
|
||||
return editedValues.get(aliasPath);
|
||||
}
|
||||
}
|
||||
Object value = valueAt(aiPayload, fieldPath);
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
for (String legacyPath : p0LegacyValuePathCandidates(fieldPath)) {
|
||||
for (String legacyPath : p0ValuePathCandidates(fieldPath)) {
|
||||
Object legacyValue = valueAt(aiPayload, legacyPath);
|
||||
if (legacyValue != null) {
|
||||
return legacyValue;
|
||||
@@ -1746,9 +1933,9 @@ public class ReservationTaskWorkflowServiceImpl implements ReservationTaskWorkfl
|
||||
}
|
||||
|
||||
/**
|
||||
* 0711 P0 已将房型字段迁移到 room_items[];当前矩阵仍使用扁平字段,读取时做最小别名兼容。
|
||||
* 0711 P0 已将房型字段迁移到 room_items[];读取时保留旧扁平字段双向兼容。
|
||||
*/
|
||||
private List<String> p0LegacyValuePathCandidates(String fieldPath) {
|
||||
private List<String> p0ValuePathCandidates(String fieldPath) {
|
||||
String safeFieldPath = fieldPath == null ? "" : fieldPath;
|
||||
return switch (safeFieldPath) {
|
||||
case "extracted_fields.room_type" -> List.of(
|
||||
@@ -1757,10 +1944,66 @@ public class ReservationTaskWorkflowServiceImpl implements ReservationTaskWorkfl
|
||||
case "extracted_fields.room_quantity" -> List.of("extracted_fields.room_items.0.room_quantity");
|
||||
case "extracted_fields.pms_room_type_code" -> List.of(
|
||||
"extracted_fields.room_items.0.pms_room_type_code");
|
||||
case "extracted_fields.room_items.0.room_type_raw" -> List.of(
|
||||
"extracted_fields.room_type",
|
||||
"extracted_fields.room_items.0.room_type_normalized");
|
||||
case "extracted_fields.room_items.0.room_quantity" -> List.of("extracted_fields.room_quantity");
|
||||
case "extracted_fields.room_items.0.pms_room_type_code" -> List.of(
|
||||
"extracted_fields.pms_room_type_code");
|
||||
default -> List.of();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 旧扁平字段转 P0 room_items[0] 主字段路径。未迁移字段返回 null。
|
||||
*/
|
||||
private String canonicalP0RoomItemFieldPath(String fieldPath) {
|
||||
return switch (fieldPath == null ? "" : fieldPath) {
|
||||
case "extracted_fields.room_quantity" -> "extracted_fields.room_items.0.room_quantity";
|
||||
case "extracted_fields.room_type" -> "extracted_fields.room_items.0.room_type_raw";
|
||||
case "extracted_fields.pms_room_type_code" -> "extracted_fields.room_items.0.pms_room_type_code";
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* P0 room_items[0] 主字段转旧扁平字段路径,用于前端兼容显示。
|
||||
*/
|
||||
private String legacyFieldPathFor(String fieldPath) {
|
||||
return switch (fieldPath == null ? "" : fieldPath) {
|
||||
case "extracted_fields.room_items.0.room_quantity" -> "extracted_fields.room_quantity";
|
||||
case "extracted_fields.room_items.0.room_type_raw" -> "extracted_fields.room_type";
|
||||
case "extracted_fields.room_items.0.pms_room_type_code" -> "extracted_fields.pms_room_type_code";
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 将矩阵 field_path 转为 RFC 6901 JSON Pointer,供前端编辑和复核定位。
|
||||
*/
|
||||
private String fieldPointerFor(String fieldPath) {
|
||||
String normalized = trimToNull(fieldPath);
|
||||
if (normalized == null) {
|
||||
return null;
|
||||
}
|
||||
StringBuilder pointer = new StringBuilder();
|
||||
for (String rawPart : normalized.split("\\.")) {
|
||||
String part = rawPart.replace("[]", "");
|
||||
if (part.isBlank()) {
|
||||
continue;
|
||||
}
|
||||
pointer.append('/').append(escapeJsonPointerToken(part));
|
||||
}
|
||||
return pointer.isEmpty() ? null : pointer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 转义 JSON Pointer token。
|
||||
*/
|
||||
private String escapeJsonPointerToken(String token) {
|
||||
return token.replace("~", "~0").replace("/", "~1");
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断值是否为空。false 和 0 是有效值,不当作空。
|
||||
*/
|
||||
@@ -1903,6 +2146,8 @@ public class ReservationTaskWorkflowServiceImpl implements ReservationTaskWorkfl
|
||||
definition.taskSubtype(),
|
||||
definition.displayArea(),
|
||||
definition.fieldPath(),
|
||||
fieldPointerFor(definition.fieldPath()),
|
||||
legacyFieldPathFor(definition.fieldPath()),
|
||||
definition.displayName(),
|
||||
definition.visible(),
|
||||
definition.editable(),
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
UPDATE workflow_reservation_task_card
|
||||
SET field_contract_version = '20260711-p0'
|
||||
WHERE field_contract_version = 'code-v1';
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "code-v1",
|
||||
"version": "20260711-p0",
|
||||
"source": "docs/import/20260706/任务卡展示编辑矩阵.xlsx",
|
||||
"row_count": 158,
|
||||
"fields": [
|
||||
@@ -242,7 +242,7 @@
|
||||
"task_subtype": "new_fit_reservation / new_group_block / new_allotment_control_block",
|
||||
"applicable_scenario": "normal task",
|
||||
"display_area": "房量房型",
|
||||
"field_path": "extracted_fields.room_quantity",
|
||||
"field_path": "extracted_fields.room_items.0.room_quantity",
|
||||
"display_name": "房量",
|
||||
"field_source": "AI输出/人工确认",
|
||||
"visible": "是",
|
||||
@@ -258,10 +258,10 @@
|
||||
"default_value_source": "AI提取值",
|
||||
"display_condition": "始终展示",
|
||||
"validation_rule": "正整数;extra bed 不计入房量",
|
||||
"write_path": "confirmed_payload_json.extracted_fields.room_quantity",
|
||||
"write_path": "confirmed_payload_json.extracted_fields.room_items[0].room_quantity",
|
||||
"opera_write_participation": "是",
|
||||
"opera_parameter_mapping": "Opera room quantity/block allocation quantity",
|
||||
"notes": "数字输入。",
|
||||
"notes": "P0 起主路径使用 room_items[0];旧 extracted_fields.room_quantity 仅作为兼容提交字段。",
|
||||
"row_number": 11
|
||||
},
|
||||
{
|
||||
@@ -271,7 +271,7 @@
|
||||
"task_subtype": "new_fit_reservation / new_group_block / new_allotment_control_block",
|
||||
"applicable_scenario": "normal task",
|
||||
"display_area": "房量房型",
|
||||
"field_path": "extracted_fields.room_type",
|
||||
"field_path": "extracted_fields.room_items.0.room_type_raw",
|
||||
"display_name": "房型原文",
|
||||
"field_source": "AI输出/人工确认",
|
||||
"visible": "是",
|
||||
@@ -287,10 +287,10 @@
|
||||
"default_value_source": "AI提取值",
|
||||
"display_condition": "始终展示",
|
||||
"validation_rule": "文本非空;保留原始房型线索",
|
||||
"write_path": "confirmed_payload_json.extracted_fields.room_type",
|
||||
"write_path": "confirmed_payload_json.extracted_fields.room_items[0].room_type_raw",
|
||||
"opera_write_participation": "是",
|
||||
"opera_parameter_mapping": "房型映射输入",
|
||||
"notes": "可人工修正原文。",
|
||||
"notes": "P0 起保留 room_type_raw 原文;旧 extracted_fields.room_type 仅作为兼容提交字段。",
|
||||
"row_number": 12
|
||||
},
|
||||
{
|
||||
@@ -300,7 +300,7 @@
|
||||
"task_subtype": "new_fit_reservation / new_group_block / new_allotment_control_block",
|
||||
"applicable_scenario": "normal task",
|
||||
"display_area": "房量房型",
|
||||
"field_path": "extracted_fields.pms_room_type_code",
|
||||
"field_path": "extracted_fields.room_items.0.pms_room_type_code",
|
||||
"display_name": "Opera房型代码",
|
||||
"field_source": "AI输出/人工确认",
|
||||
"visible": "是",
|
||||
@@ -316,10 +316,10 @@
|
||||
"default_value_source": "room_type_mapping_rules唯一命中",
|
||||
"display_condition": "涉及房型时展示",
|
||||
"validation_rule": "必须命中房型映射规则或人工确认",
|
||||
"write_path": "confirmed_payload_json.extracted_fields.pms_room_type_code",
|
||||
"write_path": "confirmed_payload_json.extracted_fields.room_items[0].pms_room_type_code",
|
||||
"opera_write_participation": "是",
|
||||
"opera_parameter_mapping": "Opera roomTypeCode",
|
||||
"notes": "SUITE无床型时可能标记 requires_downstream_hard_validation。",
|
||||
"notes": "P0 起使用 room_items[0].pms_room_type_code 作为复核和编辑主路径;旧 extracted_fields.pms_room_type_code 仅作为兼容提交字段。",
|
||||
"row_number": 13
|
||||
},
|
||||
{
|
||||
|
||||
@@ -140,7 +140,7 @@ class SuperAgentTaskResultControllerTest {
|
||||
SELECT COUNT(*)
|
||||
FROM workflow_reservation_task_card
|
||||
WHERE ai_payload_json LIKE '%New Booking%'
|
||||
AND field_contract_version = 'code-v1'
|
||||
AND field_contract_version = '20260711-p0'
|
||||
""", Long.class);
|
||||
|
||||
org.assertj.core.api.Assertions.assertThat(transitionCount).isEqualTo(1L);
|
||||
@@ -1416,7 +1416,7 @@ class SuperAgentTaskResultControllerTest {
|
||||
.andExpect(jsonPath("$.source_received_at").value(matchesPattern(UTC_INSTANT_PATTERN)))
|
||||
.andExpect(jsonPath("$.external_conversation_id").value("thread-mail-detail-matrix-001"))
|
||||
.andExpect(jsonPath("$.conversation_message_count").value(1))
|
||||
.andExpect(jsonPath("$.field_contract_version").value("code-v1"))
|
||||
.andExpect(jsonPath("$.field_contract_version").value("20260711-p0"))
|
||||
.andExpect(jsonPath("$.fields.length()").value(18))
|
||||
.andExpect(jsonPath("$.fields[?(@.field_path=='case_keys.confirmation_number')].display_name")
|
||||
.value(contains("Confirmation No.")))
|
||||
@@ -1461,8 +1461,10 @@ class SuperAgentTaskResultControllerTest {
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.task_id").value(taskId))
|
||||
.andExpect(jsonPath("$.task_status").value("PENDING_CONFIRM"))
|
||||
.andExpect(jsonPath("$.draft_payload.field_values['extracted_fields.room_quantity']").value(3))
|
||||
.andExpect(jsonPath("$.draft_payload.field_values['extracted_fields.pms_room_type_code']").value("RM3"));
|
||||
.andExpect(jsonPath("$.draft_payload.field_values['extracted_fields.room_items.0.room_quantity']").value(3))
|
||||
.andExpect(jsonPath("$.draft_payload.field_values['extracted_fields.room_items.0.pms_room_type_code']").value("RM3"))
|
||||
.andExpect(jsonPath("$.draft_payload.legacy_field_values['extracted_fields.room_quantity']").value(3))
|
||||
.andExpect(jsonPath("$.draft_payload.legacy_field_values['extracted_fields.pms_room_type_code']").value("RM3"));
|
||||
|
||||
String draftPayloadJson = jdbcTemplate.queryForObject("""
|
||||
SELECT draft_payload_json
|
||||
@@ -1474,6 +1476,7 @@ class SuperAgentTaskResultControllerTest {
|
||||
FROM workflow_reservation_task_card
|
||||
WHERE task_id = ?
|
||||
""", String.class, Long.valueOf(taskId));
|
||||
assertThat(draftPayloadJson).contains("\"extracted_fields.room_items.0.room_quantity\":3");
|
||||
assertThat(draftPayloadJson).contains("\"extracted_fields.room_quantity\":3");
|
||||
assertThat(aiPayloadJson).contains("\"room_quantity\":2");
|
||||
}
|
||||
@@ -1503,8 +1506,12 @@ class SuperAgentTaskResultControllerTest {
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.task_id").value(taskId))
|
||||
.andExpect(jsonPath("$.task_status").value("READY"))
|
||||
.andExpect(jsonPath("$.confirmed_payload.field_values['extracted_fields.room_quantity']").value(4))
|
||||
.andExpect(jsonPath("$.confirmed_payload.field_values['extracted_fields.pms_room_type_code']").value("RM4"))
|
||||
.andExpect(jsonPath("$.confirmed_payload.field_values['extracted_fields.room_items.0.room_quantity']").value(4))
|
||||
.andExpect(jsonPath("$.confirmed_payload.field_values['extracted_fields.room_items.0.pms_room_type_code']").value("RM4"))
|
||||
.andExpect(jsonPath("$.confirmed_payload.legacy_field_values['extracted_fields.room_quantity']").value(4))
|
||||
.andExpect(jsonPath("$.confirmed_payload.legacy_field_values['extracted_fields.pms_room_type_code']").value("RM4"))
|
||||
.andExpect(jsonPath("$.confirmed_payload.effective_payload.extracted_fields.room_items[0].room_quantity").value(4))
|
||||
.andExpect(jsonPath("$.confirmed_payload.effective_payload.extracted_fields.room_items[0].pms_room_type_code").value("RM4"))
|
||||
.andExpect(jsonPath("$.confirmed_payload.field_values['extracted_fields.arrival_date']").value("2026-08-01"))
|
||||
.andExpect(jsonPath("$.opera_operations.length()").value(2))
|
||||
.andExpect(jsonPath("$.opera_operations[0].operation_sequence").value(1))
|
||||
@@ -1544,6 +1551,7 @@ class SuperAgentTaskResultControllerTest {
|
||||
""", Long.class, Long.valueOf(taskId));
|
||||
assertThat(taskStatus).isEqualTo("READY");
|
||||
assertThat(confirmedAtCount).isEqualTo(1L);
|
||||
assertThat(confirmedPayloadJson).contains("\"extracted_fields.room_items.0.room_quantity\":4");
|
||||
assertThat(confirmedPayloadJson).contains("\"extracted_fields.room_quantity\":4");
|
||||
assertThat(auditCount).isEqualTo(1L);
|
||||
}
|
||||
@@ -1744,7 +1752,8 @@ class SuperAgentTaskResultControllerTest {
|
||||
"""))
|
||||
.andExpect(status().isBadRequest())
|
||||
.andExpect(jsonPath("$.error_code").value("TASK_FIELD_VALIDATION_FAILED"))
|
||||
.andExpect(jsonPath("$.details[0]").value(containsString("extracted_fields.pms_room_type_code")));
|
||||
.andExpect(jsonPath("$.details[0]")
|
||||
.value(containsString("extracted_fields.room_items.0.pms_room_type_code")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1854,10 +1863,16 @@ class SuperAgentTaskResultControllerTest {
|
||||
.andExpect(jsonPath("$.review_resolution.field_overrides[0].field_pointer")
|
||||
.value("/extracted_fields/pms_room_type_code"))
|
||||
.andExpect(jsonPath("$.review_resolution.field_overrides[0].field_path")
|
||||
.value("extracted_fields.room_items.0.pms_room_type_code"))
|
||||
.andExpect(jsonPath("$.review_resolution.field_overrides[0].legacy_field_path")
|
||||
.value("extracted_fields.pms_room_type_code"))
|
||||
.andExpect(jsonPath("$.review_resolution.field_overrides[0].value").value("RM3"))
|
||||
.andExpect(jsonPath("$.review_resolution.resolved_at").value(matchesPattern(UTC_INSTANT_PATTERN)))
|
||||
.andExpect(jsonPath("$.confirmed_payload.field_values['extracted_fields.pms_room_type_code']")
|
||||
.andExpect(jsonPath("$.confirmed_payload.field_values['extracted_fields.room_items.0.pms_room_type_code']")
|
||||
.value("RM3"))
|
||||
.andExpect(jsonPath("$.confirmed_payload.legacy_field_values['extracted_fields.pms_room_type_code']")
|
||||
.value("RM3"))
|
||||
.andExpect(jsonPath("$.confirmed_payload.effective_payload.extracted_fields.room_items[0].pms_room_type_code")
|
||||
.value("RM3"))
|
||||
.andExpect(jsonPath("$.opera_operations.length()").value(2));
|
||||
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package cn.nianxx.thhotel.workflows.reservation.control;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
@@ -348,6 +351,73 @@ class SuperAgentTaskResultP0FixtureRegressionTest {
|
||||
assertThat(taskCount).isEqualTo(3L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSaveDraftAndConfirmRoomItemsFromP0FieldPaths() throws Exception {
|
||||
ObjectNode event = fixture("row_multiple_derived.json").path("message_events").get(0).deepCopy();
|
||||
useEventGroupCode(event, "CHILD-ROW-FIELD-001");
|
||||
((ObjectNode) event.path("extracted_fields")).put("parent_group_code", "PARENT-ROW-FIELD-001");
|
||||
String externalId = "p0-room-items-field-migration-001";
|
||||
captureSourceMessage(externalId);
|
||||
|
||||
MvcResult result = mockMvc.perform(signedPost(
|
||||
businessRoot(externalId, event),
|
||||
"nonce-p0-room-items-field-migration-create-001"))
|
||||
.andExpect(status().isCreated())
|
||||
.andExpect(jsonPath("$.items[0].route_code").value("R02_NEW_GROUP_BLOCK_NORMAL"))
|
||||
.andReturn();
|
||||
String taskId = com.jayway.jsonpath.JsonPath.read(
|
||||
result.getResponse().getContentAsString(),
|
||||
"$.items[0].task_id");
|
||||
|
||||
mockMvc.perform(get("/api/reservation/tasks/{taskId}", taskId))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.fields[?(@.field_path=='extracted_fields.room_items.0.room_quantity')].field_pointer")
|
||||
.value(contains("/extracted_fields/room_items/0/room_quantity")))
|
||||
.andExpect(jsonPath("$.fields[?(@.field_path=='extracted_fields.room_items.0.room_type_raw')].field_pointer")
|
||||
.value(contains("/extracted_fields/room_items/0/room_type_raw")))
|
||||
.andExpect(jsonPath("$.fields[?(@.field_path=='extracted_fields.room_items.0.pms_room_type_code')].field_pointer")
|
||||
.value(contains("/extracted_fields/room_items/0/pms_room_type_code")))
|
||||
.andExpect(jsonPath("$.fields[?(@.field_path=='extracted_fields.room_items.0.pms_room_type_code')].value")
|
||||
.value(contains("RM2")));
|
||||
|
||||
mockMvc.perform(put("/api/reservation/tasks/{taskId}/draft", taskId)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{
|
||||
"field_values": {
|
||||
"extracted_fields.room_items.0.room_quantity": 4,
|
||||
"/extracted_fields/room_items/0/pms_room_type_code": "RM3"
|
||||
}
|
||||
}
|
||||
"""))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.draft_payload.field_values['extracted_fields.room_items.0.room_quantity']")
|
||||
.value(4))
|
||||
.andExpect(jsonPath("$.draft_payload.field_values['extracted_fields.room_items.0.pms_room_type_code']")
|
||||
.value("RM3"));
|
||||
|
||||
mockMvc.perform(post("/api/reservation/tasks/{taskId}/confirm", taskId)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{
|
||||
"field_values": {
|
||||
"extracted_fields.room_items.0.room_quantity": 5,
|
||||
"extracted_fields.room_items.0.pms_room_type_code": "RM4"
|
||||
}
|
||||
}
|
||||
"""))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.task_status").value("READY"))
|
||||
.andExpect(jsonPath("$.confirmed_payload.field_values['extracted_fields.room_items.0.room_quantity']")
|
||||
.value(5))
|
||||
.andExpect(jsonPath("$.confirmed_payload.field_values['extracted_fields.room_items.0.pms_room_type_code']")
|
||||
.value("RM4"))
|
||||
.andExpect(jsonPath("$.confirmed_payload.effective_payload.extracted_fields.room_items[0].room_quantity")
|
||||
.value(5))
|
||||
.andExpect(jsonPath("$.confirmed_payload.effective_payload.extracted_fields.room_items[0].pms_room_type_code")
|
||||
.value("RM4"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailClosedWhenV3BusinessEventMissesRequiredField() throws Exception {
|
||||
ObjectNode event = fixture("row_multiple_derived.json").path("message_events").get(0).deepCopy();
|
||||
@@ -417,6 +487,13 @@ class SuperAgentTaskResultP0FixtureRegressionTest {
|
||||
result.getResponse().getContentAsString(),
|
||||
"$.items[0].order_id");
|
||||
|
||||
mockMvc.perform(get("/api/reservation/tasks/{taskId}", taskId))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.fields[?(@.field_path=='extracted_fields.room_items.0.pms_room_type_code')].field_pointer")
|
||||
.value(contains("/extracted_fields/room_items/0/pms_room_type_code")))
|
||||
.andExpect(jsonPath("$.fields[?(@.field_path=='extracted_fields.room_items.0.room_type_raw')].value")
|
||||
.value(contains("SUITE")));
|
||||
|
||||
mockMvc.perform(post("/api/reservation/tasks/{taskId}/manual-review-resolutions", taskId)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
@@ -436,12 +513,14 @@ class SuperAgentTaskResultP0FixtureRegressionTest {
|
||||
.andExpect(jsonPath("$.review_resolution.field_overrides[0].field_pointer")
|
||||
.value("/extracted_fields/room_items/0/pms_room_type_code"))
|
||||
.andExpect(jsonPath("$.review_resolution.field_overrides[0].field_path")
|
||||
.value("extracted_fields.pms_room_type_code"))
|
||||
.andExpect(jsonPath("$.confirmed_payload.field_values['extracted_fields.room_type']")
|
||||
.value("extracted_fields.room_items.0.pms_room_type_code"))
|
||||
.andExpect(jsonPath("$.confirmed_payload.field_values['extracted_fields.room_items.0.room_type_raw']")
|
||||
.value("SUITE"))
|
||||
.andExpect(jsonPath("$.confirmed_payload.field_values['extracted_fields.room_quantity']")
|
||||
.andExpect(jsonPath("$.confirmed_payload.field_values['extracted_fields.room_items.0.room_quantity']")
|
||||
.value(2))
|
||||
.andExpect(jsonPath("$.confirmed_payload.field_values['extracted_fields.pms_room_type_code']")
|
||||
.andExpect(jsonPath("$.confirmed_payload.field_values['extracted_fields.room_items.0.pms_room_type_code']")
|
||||
.value("SU1"))
|
||||
.andExpect(jsonPath("$.confirmed_payload.effective_payload.extracted_fields.room_items[0].pms_room_type_code")
|
||||
.value("SU1"));
|
||||
|
||||
Long sourceTaskCount = jdbcTemplate.queryForObject("""
|
||||
|
||||
Reference in New Issue
Block a user