fix(firstleg): allow audited carrier node time corrections

This commit is contained in:
2026-08-26 15:41:03 +08:00
parent 4ae236fe11
commit 2ebea9e789
5 changed files with 85 additions and 3 deletions
@@ -97,6 +97,8 @@ public class PlatformCarrierScheduleQueryController {
@PostMapping("/{queryId}/manual-nodes")
@PreAuthorize("hasAuthority('platform:firstleg:port-schedule-query:manual')")
@Operation(summary = "人工补录预计或实际标准船节点",
description = "预计节点不推进实际进度;同级当前节点可追加审计事实并修正实际时间,更早节点返回 A0409。")
@BusinessAudit(event = "CARRIER_SCHEDULE_QUERY_MANUAL_NODE", resourceType = "CARRIER_SCHEDULE_QUERY",
resourceId = "#queryId", description = "人工补录船节点事实")
public ApiResponse<Detail> manualNode(@AuthenticationPrincipal AuthenticatedUser user,
@@ -205,6 +205,7 @@ public class CarrierScheduleQueryService {
}
CarrierScheduleQueryEntity value = requiredLockedVersion(id, request.expectedVersion());
CarrierScheduleNode node = CarrierScheduleNode.require(request.standardNode());
requireManualActualNotEarlier(value, node, request.actualAt());
CarrierScheduleQueryEntity before = copy(value);
String providerEventId = sha256("MANUAL|" + id + '|' + key);
CarrierScheduleNodeFactEntity fact = addNode(id, "MANUAL", providerEventId, node.name(), node,
@@ -214,7 +215,7 @@ public class CarrierScheduleQueryService {
if (fact == null) {
return complete(id, operatorId, key, detail(value));
}
boolean advanced = advanceActualNode(value, node, request.actualAt(), request.observedAt(),
boolean advanced = advanceManualActualNode(value, node, request.actualAt(), request.observedAt(),
operatorId, operatorName);
if (!advanced && repository.updateState(value, request.expectedVersion(), operatorId, operatorName) != 1) {
throw concurrent();
@@ -226,6 +227,34 @@ public class CarrierScheduleQueryService {
return complete(id, operatorId, key, detail(after));
}
private void requireManualActualNotEarlier(CarrierScheduleQueryEntity value, CarrierScheduleNode node,
LocalDateTime actualAt) {
if (actualAt == null || value.getLatestStandardNode() == null) return;
CarrierScheduleNode current = CarrierScheduleNode.require(value.getLatestStandardNode());
if (node.sequence() < current.sequence()) {
throw conflict("人工实际节点不得早于当前最新实际节点:" + current.name());
}
}
private boolean advanceManualActualNode(CarrierScheduleQueryEntity value, CarrierScheduleNode node,
LocalDateTime actualAt, LocalDateTime observedAt,
long operatorId, String operatorName) {
if (actualAt == null) return false;
CarrierScheduleNode current = value.getLatestStandardNode() == null ? null
: CarrierScheduleNode.require(value.getLatestStandardNode());
if (current == node) {
value.setLatestNodeObservedAt(actualAt); value.setLastSuccessAt(now());
value.setSourceUpdatedAt(observedAt); value.setLastAttemptStatus("SUCCESS");
value.setLastErrorCode(null); value.setLastErrorMessage(null);
if (!"STOPPED".equals(value.getStatus())) {
value.setStatus("READY"); value.setNextAllowedAt(CarrierScheduleQuerySlots.nextSlot(now()));
}
if (repository.updateState(value, value.getVersion(), operatorId, operatorName) != 1) throw concurrent();
return true;
}
return advanceActualNode(value, node, actualAt, observedAt, operatorId, operatorName);
}
private boolean advanceActualNode(CarrierScheduleQueryEntity value, CarrierScheduleNode node,
LocalDateTime actualAt, LocalDateTime observedAt,
long operatorId, String operatorName) {