优化
This commit is contained in:
+2
-6
@@ -7,7 +7,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class LoadingCompletionGateSqlContractTest {
|
||||
@Test
|
||||
void requiresEveryLinkedContainerToPassBookingAndTruckGates() throws Exception {
|
||||
void requiresEveryLinkedContainerToHaveReleasedBookingWithoutTruckPrerequisite() throws Exception {
|
||||
Select select = LoadingCompletionGateMapper.class
|
||||
.getDeclaredMethod("snapshot", long.class, long.class).getAnnotation(Select.class);
|
||||
String sql = String.join(" ", select.value()).replaceAll("\\s+", " ");
|
||||
@@ -17,12 +17,8 @@ class LoadingCompletionGateSqlContractTest {
|
||||
.contains("JOIN fl_loading_plan p ON p.id=l.loading_plan_id")
|
||||
.contains("fl_ocean_booking_container_allocation")
|
||||
.contains("b.status='RELEASED'")
|
||||
.contains("fl_truck_appointment")
|
||||
.contains("t.container_booking_id=p.container_booking_id")
|
||||
.contains("t.status='COMPLETED'")
|
||||
.doesNotContain("t.loading_plan_id=p.id")
|
||||
.doesNotContain("fl_truck_appointment", "truckReady", "t.status='COMPLETED'")
|
||||
.contains("l.tenant_id=#{tenantId} AND l.inbound_id=#{inboundId}")
|
||||
.doesNotContain("truckReady, FROM")
|
||||
.doesNotContain("fl_customs_case", "fl_customs_receipt", "customsReady")
|
||||
.doesNotContain("<", ">");
|
||||
}
|
||||
|
||||
+8
-8
@@ -98,7 +98,7 @@ class LoadingDeliveryServiceTest {
|
||||
LoadingActionResponse result = service.completeLoading(10, 20, 7, "admin", "k1", new CompleteLoadingRequest(3, null));
|
||||
assertThat(result.mainStatus()).isEqualTo("LOADED");
|
||||
verify(loadingModelBridge).requireCompleted(10, 20);
|
||||
verify(completionGate).requireBookingAndTransport(10, 20);
|
||||
verify(completionGate).requireReleasedBooking(10, 20);
|
||||
verify(histories).insert(any(InboundStatusHistoryEntity.class));
|
||||
}
|
||||
|
||||
@@ -119,22 +119,22 @@ class LoadingDeliveryServiceTest {
|
||||
service.requirePdaCompletionReady(10, 20);
|
||||
|
||||
verify(loadingModelBridge).requireStarted(10, 20);
|
||||
verify(completionGate).requireBookingAndTransport(10, 20);
|
||||
verify(completionGate).requireReleasedBooking(10, 20);
|
||||
verify(loadingModelBridge, never()).requireCompleted(anyLong(), anyLong());
|
||||
verify(repository, never()).completeLoading(anyLong(), anyLong(), anyLong(), anyInt());
|
||||
verify(repository, never()).claimOperation(anyLong(), anyLong(), anyLong(), anyString(), anyString(), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void pdaCompletionPreflightReportsTruckBlockerBeforeEvidenceOrWrites() {
|
||||
void pdaCompletionPreflightReportsBookingBlockerBeforeEvidenceOrWrites() {
|
||||
when(repository.findInbound(10, 20)).thenReturn(new Snapshot(20L, 10L, "LOADING", 3));
|
||||
doThrow(new com.baiyue.common.core.exception.BizException(
|
||||
com.baiyue.common.core.api.ResultCode.CONFLICT,
|
||||
"LOADED门禁未通过:拖车尚未完成(必须为 COMPLETED)"))
|
||||
.when(completionGate).requireBookingAndTransport(10, 20);
|
||||
"LOADED门禁未通过:订舱尚未完成(必须为 RELEASED)"))
|
||||
.when(completionGate).requireReleasedBooking(10, 20);
|
||||
|
||||
assertThatThrownBy(() -> service.requirePdaCompletionReady(10, 20))
|
||||
.hasMessageContaining("拖车尚未完成");
|
||||
.hasMessageContaining("订舱尚未完成");
|
||||
|
||||
verify(repository, never()).listEvidence(anyLong(), anyLong());
|
||||
verify(repository, never()).completeLoading(anyLong(), anyLong(), anyLong(), anyInt());
|
||||
@@ -170,7 +170,7 @@ class LoadingDeliveryServiceTest {
|
||||
|
||||
assertThat(result.mainStatus()).isEqualTo("LOADED");
|
||||
verify(loadingModelBridge).requireCompleted(10, 20);
|
||||
verify(completionGate).requireBookingAndTransport(10, 20);
|
||||
verify(completionGate).requireReleasedBooking(10, 20);
|
||||
verify(repository).completeLoadedChild(10, 20, 7, 3);
|
||||
verify(repository).aggregateRootLoaded(10, 20, 7);
|
||||
verify(histories).insert(argThat((InboundStatusHistoryEntity history) ->
|
||||
@@ -229,7 +229,7 @@ class LoadingDeliveryServiceTest {
|
||||
verify(repository).completeLoadedChild(10, 20, 7, 5);
|
||||
verify(repository).aggregateRootLoaded(10, 10, 7);
|
||||
verify(loadingModelBridge).requireCompleted(10, 20);
|
||||
verify(completionGate).requireBookingAndTransport(10, 20);
|
||||
verify(completionGate).requireReleasedBooking(10, 20);
|
||||
verify(histories).insert(any(InboundStatusHistoryEntity.class));
|
||||
verify(repository).completeOperation(10L, 20L, 7L, "COMPLETE_LOADING", "slice-replay", resultJson.get());
|
||||
}
|
||||
|
||||
+6
@@ -16,6 +16,12 @@ class TruckAppointmentMapperContractTest {
|
||||
assertThat(page).contains("fl_truck_appointment", "appointment_no", "vehicle_no", "status=#{q.status}", "customer_id=#{q.customerId}", "container_booking_id=#{q.containerBookingId}");
|
||||
String transition = TruckAppointmentMapper.class.getMethod("transition", long.class, String.class, String.class, int.class).getAnnotation(Update.class).value()[0];
|
||||
assertThat(transition).contains("status=#{toStatus}", "status=#{fromStatus}", "version=#{version}", "revision=revision+1", "version=version+1");
|
||||
String active = TruckAppointmentMapper.class.getMethod("activeForUpdate", long.class).getAnnotation(Select.class).value()[0];
|
||||
assertThat(active).contains("container_booking_id=#{containerBookingId}", "FOR UPDATE")
|
||||
.contains("'COMPLETED','CANCELLED','EXCEPTION'");
|
||||
String pdaComplete = TruckAppointmentMapper.class.getMethod("completeLoadingFromPda", long.class, String.class, int.class).getAnnotation(Update.class).value()[0];
|
||||
assertThat(pdaComplete).contains("status='COMPLETED'", "actual_loading_at=COALESCE", "status=#{fromStatus}",
|
||||
"version=#{version}", "revision=revision+1", "version=version+1");
|
||||
String reschedule = TruckAppointmentMapper.class.getMethod("reschedule", long.class, java.time.LocalDateTime.class, String.class, int.class).getAnnotation(Update.class).value()[0];
|
||||
assertThat(reschedule).contains("status='RESCHEDULED'", "version=#{version}", "revision=revision+1");
|
||||
String edit = TruckAppointmentMapper.class.getMethod("updateEdit", TruckAppointmentEntity.class, int.class).getAnnotation(Update.class).value()[0];
|
||||
|
||||
@@ -70,6 +70,8 @@ class LoadingPickServiceTest {
|
||||
@MockBean
|
||||
private LoadingDeliveryService loadingDeliveryService;
|
||||
@MockBean
|
||||
private TruckAppointmentService truckAppointmentService;
|
||||
@MockBean
|
||||
private InboundStatusHistoryMapper inboundHistories;
|
||||
@MockBean
|
||||
private PdaPickSettlementService settlementService;
|
||||
@@ -509,13 +511,14 @@ class LoadingPickServiceTest {
|
||||
verify(settlementService).settle(20L, 9L, "operator");
|
||||
verify(repository).syncInboundProjections(20L, 9L, "operator");
|
||||
verify(loadingDeliveryService).requirePdaCompletionReady(101L, 50L);
|
||||
verify(truckAppointmentService).completeLoadingFromPda(99L, 20L, 9L, "operator");
|
||||
verify(loadingDeliveryService).completeLoading(eq(101L), eq(50L), eq(9L), eq("operator"),
|
||||
eq("PDA-complete-1-50"), argThat(value -> value.expectedVersion() == 7
|
||||
&& "checked".equals(value.remark())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void truckBlockerLeavesPdaTaskPlanAndSettlementUntouched() {
|
||||
void conflictingTruckAppointmentsLeavePdaTaskPlanAndSettlementUntouched() {
|
||||
LoadingPickTaskEntity task = task();
|
||||
when(repository.taskForUpdate(20L)).thenReturn(task);
|
||||
when(repository.allTaskItemsResolved(20L)).thenReturn(true);
|
||||
@@ -525,12 +528,12 @@ class LoadingPickServiceTest {
|
||||
when(repository.inboundForUpdate(101L, 50L)).thenReturn(
|
||||
new LoadingInboundMapper.Snapshot(50L, 101L, "LOADING", 7));
|
||||
doThrow(new BizException(com.baiyue.common.core.api.ResultCode.CONFLICT,
|
||||
"LOADED门禁未通过:拖车尚未完成(必须为 COMPLETED)"))
|
||||
.when(loadingDeliveryService).requirePdaCompletionReady(101L, 50L);
|
||||
"同一柜次存在多条有效拖车预约,不能确认装柜完成"))
|
||||
.when(truckAppointmentService).completeLoadingFromPda(99L, 20L, 9L, "operator");
|
||||
|
||||
assertThatThrownBy(() -> service.complete(20L,
|
||||
new CompleteLoadingRequest(0, "complete-blocked", "checked"), 9L, "operator"))
|
||||
.isInstanceOf(BizException.class).hasMessageContaining("拖车尚未完成");
|
||||
.isInstanceOf(BizException.class).hasMessageContaining("多条有效拖车预约");
|
||||
|
||||
verify(settlementService, never()).settle(anyLong(), anyLong(), anyString());
|
||||
verify(repository, never()).approve(anyLong(), anyLong(), anyString(), any());
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.fasterxml.jackson.databind.json.JsonMapper;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
@@ -224,6 +225,56 @@ class TruckAppointmentServiceTest {
|
||||
assertThat(history.getValue().getToStatus()).isEqualTo("RESCHEDULED");
|
||||
}
|
||||
|
||||
@Test void pdaCompletionDoesNotRequireTruckAppointment() {
|
||||
when(repository.activeForUpdate(99L)).thenReturn(List.of());
|
||||
|
||||
service.completeLoadingFromPda(99L, 20L, 7L, "admin");
|
||||
|
||||
verify(repository, never()).completeLoadingFromPda(anyLong(), anyString(), anyInt());
|
||||
verify(repository, never()).history(any());
|
||||
}
|
||||
|
||||
@Test void pdaCompletionLocksAndCompletesTheOnlyActiveTruckWithHistory() {
|
||||
TruckAppointmentEntity before = entity(TruckAppointmentStatus.LOADING, 4);
|
||||
TruckAppointmentEntity after = entity(TruckAppointmentStatus.COMPLETED, 5);
|
||||
when(repository.activeForUpdate(99L)).thenReturn(List.of(before));
|
||||
when(repository.completeLoadingFromPda(1L, "LOADING", 4)).thenReturn(1);
|
||||
when(repository.find(1L)).thenReturn(after);
|
||||
|
||||
service.completeLoadingFromPda(99L, 20L, 7L, "admin");
|
||||
|
||||
verify(repository).completeLoadingFromPda(1L, "LOADING", 4);
|
||||
ArgumentCaptor<TruckAppointmentHistoryEntity> history = ArgumentCaptor.forClass(TruckAppointmentHistoryEntity.class);
|
||||
verify(repository).history(history.capture());
|
||||
assertThat(history.getValue().getFromStatus()).isEqualTo("LOADING");
|
||||
assertThat(history.getValue().getToStatus()).isEqualTo("COMPLETED");
|
||||
assertThat(history.getValue().getAction()).isEqualTo("PDA_COMPLETE_LOADING");
|
||||
assertThat(history.getValue().getOperatorId()).isEqualTo(7L);
|
||||
assertThat(history.getValue().getReason()).contains("20");
|
||||
}
|
||||
|
||||
@Test void pdaCompletionRejectsMultipleActiveTruckAppointmentsWithoutMutation() {
|
||||
when(repository.activeForUpdate(99L)).thenReturn(List.of(
|
||||
entity(TruckAppointmentStatus.LOADING, 1), entity(TruckAppointmentStatus.DEPARTED, 2)));
|
||||
|
||||
assertThatThrownBy(() -> service.completeLoadingFromPda(99L, 20L, 7L, "admin"))
|
||||
.isInstanceOf(BizException.class).hasMessageContaining("多条有效拖车预约");
|
||||
|
||||
verify(repository, never()).completeLoadingFromPda(anyLong(), anyString(), anyInt());
|
||||
verify(repository, never()).history(any());
|
||||
}
|
||||
|
||||
@Test void pdaCompletionRejectsConcurrentTruckModificationWithoutHistory() {
|
||||
TruckAppointmentEntity before = entity(TruckAppointmentStatus.RESCHEDULED, 4);
|
||||
when(repository.activeForUpdate(99L)).thenReturn(List.of(before));
|
||||
when(repository.completeLoadingFromPda(1L, "RESCHEDULED", 4)).thenReturn(0);
|
||||
|
||||
assertThatThrownBy(() -> service.completeLoadingFromPda(99L, 20L, 7L, "admin"))
|
||||
.isInstanceOf(BizException.class).hasMessageContaining("并发修改");
|
||||
|
||||
verify(repository, never()).history(any());
|
||||
}
|
||||
|
||||
private String requestHash(String action, long id, Object request) throws Exception {
|
||||
String canonical = action + '|' + id + '|' + objectMapper.writeValueAsString(request);
|
||||
return java.util.HexFormat.of().formatHex(java.security.MessageDigest.getInstance("SHA-256")
|
||||
|
||||
Reference in New Issue
Block a user