fix(firstleg): allow audited carrier node time corrections
This commit is contained in:
+2
@@ -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,
|
||||
|
||||
+30
-1
@@ -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) {
|
||||
|
||||
@@ -82,7 +82,9 @@ paths:
|
||||
schema: {$ref: '#/components/schemas/ManualNodeRequest'}
|
||||
responses:
|
||||
'200':
|
||||
description: Idempotent fact append; only actualAt advances and actual EMPTY_RETURNED stops
|
||||
description: Idempotent fact append; estimated facts never advance actual progress; the current latestActualNode may append an audited correction fact and replace latestActualAt; a later actual node advances progress and actual EMPTY_RETURNED stops
|
||||
'409':
|
||||
description: A0409 when expectedVersion is stale or a manual actual node is earlier than latestActualNode
|
||||
content: {application/json: {schema: {$ref: '#/components/schemas/DetailEnvelope'}}}
|
||||
components:
|
||||
parameters:
|
||||
@@ -122,7 +124,7 @@ components:
|
||||
ManualNodeRequest:
|
||||
type: object
|
||||
required: [expectedVersion, standardNode, observedAt, reason]
|
||||
description: estimatedAt and actualAt cannot both be null; estimatedAt never advances the latest actual node.
|
||||
description: estimatedAt and actualAt cannot both be null; estimatedAt never advances the latest actual node; when actualAt is present, standardNode may equal latestActualNode to correct latestActualAt through a new immutable audit fact, but cannot be earlier.
|
||||
properties:
|
||||
expectedVersion: {type: integer, minimum: 0}
|
||||
standardNode: {$ref: '#/components/schemas/StandardNode'}
|
||||
|
||||
Reference in New Issue
Block a user