修复M002 V3字段矩阵迁移问题

This commit is contained in:
andy
2026-07-12 01:27:42 +08:00
parent ab303eea39
commit db95aece20
10 changed files with 330 additions and 19 deletions

View File

@@ -3,14 +3,17 @@ package cn.nianxx.thhotel.workflows.reservation.common.request;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* 同卡人工复核字段修正请求。field_pointer 使用 RFC 6901 JSON Pointer。
* 同卡人工复核字段修正请求。field_pointer 使用 RFC 6901 JSON Pointerfield_path 用于兼容矩阵主路径或旧扁平路径
*
* @param fieldPointer 指向任务卡可编辑字段的 JSON Pointer
* @param fieldPath 指向任务卡可编辑字段的矩阵路径,支持 P0 主路径和旧扁平路径
* @param value 人工修正后的字段值
*/
public record ManualReviewResolutionFieldOverrideRequest(
@JsonProperty("field_pointer")
String fieldPointer,
@JsonProperty("field_path")
String fieldPath,
Object value
) {
}

View File

@@ -616,6 +616,7 @@ public class ReservationAiTaskIntakeServiceImpl implements ReservationAiTaskInta
item.put("task_type", route.taskType());
item.put("task_subtype", route.taskSubtype());
copyIfPresent(event, item, "current_or_history");
copyIfPresent(event, item, "relevant_message_excerpt");
copyIfPresent(event, item, "case_keys");
copyIfPresent(event, item, "extracted_fields");
copyIfPresent(event, item, "manual_review");

View File

@@ -85,6 +85,7 @@ public class ReservationTaskWorkflowServiceImpl implements ReservationTaskWorkfl
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";
private static final String V3_CATALOG_CODE = "M002V3";
private static final String OPERA_OPERATION_CODE_PRECHECK = "SIMULATE_PRECHECK";
private static final String OPERA_OPERATION_CODE_WRITE = "SIMULATE_WRITE";
private static final int MAX_QUEUE_ORDER_RETRY = 5;
@@ -825,12 +826,15 @@ public class ReservationTaskWorkflowServiceImpl implements ReservationTaskWorkfl
Set<String> seenFieldPaths = new LinkedHashSet<>();
for (ManualReviewResolutionFieldOverrideRequest request : requests) {
String fieldPointer = trimToNull(request == null ? null : request.fieldPointer());
ReservationTaskCardFieldDefinition definition = findEditableDefinitionByPointer(definitionByPath, fieldPointer);
if (!seenPointers.add(fieldPointer) || !seenFieldPaths.add(definition.fieldPath())) {
throw reviewPointerDuplicateError(fieldPointer, definition.fieldPath());
String fieldPath = trimToNull(request == null ? null : request.fieldPath());
ReservationTaskCardFieldDefinition definition =
findEditableDefinitionByOverride(definitionByPath, fieldPointer, fieldPath);
String resultFieldPointer = fieldPointer != null ? fieldPointer : fieldPointerFor(definition.fieldPath());
if (!seenPointers.add(resultFieldPointer) || !seenFieldPaths.add(definition.fieldPath())) {
throw reviewPointerDuplicateError(resultFieldPointer, definition.fieldPath());
}
results.add(new ManualReviewResolutionFieldOverrideResult(
fieldPointer,
resultFieldPointer,
definition.fieldPath(),
legacyFieldPathFor(definition.fieldPath()),
request.value()));
@@ -883,6 +887,48 @@ public class ReservationTaskWorkflowServiceImpl implements ReservationTaskWorkfl
throw reviewPointerError(fieldPointer);
}
/**
* 按复核请求定位可编辑字段,支持 JSON Pointer、P0 主 field_path 和旧扁平 field_path。
*/
private ReservationTaskCardFieldDefinition findEditableDefinitionByOverride(
Map<String, ReservationTaskCardFieldDefinition> definitionByPath,
String fieldPointer,
String fieldPath) {
ReservationTaskCardFieldDefinition pointerDefinition = null;
if (fieldPointer != null) {
pointerDefinition = findEditableDefinitionByPointer(definitionByPath, fieldPointer);
}
ReservationTaskCardFieldDefinition pathDefinition = null;
if (fieldPath != null) {
pathDefinition = findEditableDefinitionByFieldPath(definitionByPath, fieldPath);
}
if (pointerDefinition != null && pathDefinition != null
&& !pointerDefinition.fieldPath().equals(pathDefinition.fieldPath())) {
throw reviewPointerError(fieldPointer + " / " + fieldPath);
}
if (pointerDefinition != null) {
return pointerDefinition;
}
if (pathDefinition != null) {
return pathDefinition;
}
throw reviewPointerError(null);
}
/**
* 按矩阵字段路径查找可编辑字段,先将旧扁平路径归一化到 P0 主路径。
*/
private ReservationTaskCardFieldDefinition findEditableDefinitionByFieldPath(
Map<String, ReservationTaskCardFieldDefinition> definitionByPath,
String fieldPath) {
String canonicalFieldPath = canonicalSubmittedFieldPath(definitionByPath, fieldPath);
ReservationTaskCardFieldDefinition definition = definitionByPath.get(canonicalFieldPath);
if (definition == null || !isYes(definition.editable())) {
throw reviewPointerError(fieldPath);
}
return definition;
}
/**
* 将 RFC 6901 JSON Pointer 转换为可能的矩阵 field_path兼容数组下标到 [] 与旧扁平路径。
*/
@@ -1223,7 +1269,7 @@ public class ReservationTaskWorkflowServiceImpl implements ReservationTaskWorkfl
}
/**
* 按字段矩阵执行校验。复核解阻场景允许只读证据字段缺失,因为用户无法在该接口补齐
* 按字段矩阵执行校验。P0 V3 业务事件缺省旧 visible_reason 时做窄口兼容,其他只读必填仍要校验
*/
private List<String> validateDefinitionValues(
ReservationTaskSnapshot task,
@@ -1237,7 +1283,7 @@ public class ReservationTaskWorkflowServiceImpl implements ReservationTaskWorkfl
for (ReservationTaskCardFieldDefinition definition : activeDefinitions) {
Object value = valueForField(aiPayload, editedValues, definition.fieldPath());
if (validateRequired && isMissingRequired(task, definition, aiPayload, editedValues, value)) {
if (allowMissingReadonlyRequired && !isYes(definition.editable())) {
if (allowMissingReadonlyRequired && isReadonlyRequiredMissingAllowed(definition, aiPayload)) {
continue;
}
errors.add(definition.fieldPath() + ": 必填字段缺失。");
@@ -1252,6 +1298,18 @@ public class ReservationTaskWorkflowServiceImpl implements ReservationTaskWorkfl
return errors;
}
/**
* 兼容 0711 P0 业务事件V3 message_event 没有旧矩阵根 visible_reason 字段,但保留 relevant_message_excerpt 作为证据。
*/
private boolean isReadonlyRequiredMissingAllowed(
ReservationTaskCardFieldDefinition definition,
JsonNode aiPayload) {
return !isYes(definition.editable())
&& "visible_reason".equals(definition.fieldPath())
&& V3_CATALOG_CODE.equals(aiPayload.path("catalog_code").asText())
&& aiPayload.path("v3_message_event").isObject();
}
/**
* 生成字段定义路径集合,用于最终确认时校验全部展示字段。
*/