优化
This commit is contained in:
+1
-1
@@ -30,7 +30,7 @@ class CustomerTransitStatisticsContractTest {
|
||||
.findFirst().orElseThrow().getAnnotation(org.apache.ibatis.annotations.Select.class).value();
|
||||
var statement = String.join("\n", sql);
|
||||
assertThat(statement).contains("actual_sailing_at IS NOT NULL", "actual_warehouse_arrival_at IS NULL",
|
||||
"status NOT IN ('CANCELLED', 'FAILED')", "DISTINCT x.customer_id, x.container_booking_id",
|
||||
"status <> 'CANCELLED'", "DISTINCT x.customer_id, x.container_booking_id",
|
||||
"b.customer_id", "i.account_id", "i.deleted = 0", "NOT EXISTS",
|
||||
"x.customer_id = #{customerId}", "LIKE '20%'", "THEN 1.0",
|
||||
"LIKE '40%'", "THEN 2.0", "LIKE '45%'", "THEN 2.25", "ELSE NULL");
|
||||
|
||||
+9
-8
@@ -23,15 +23,10 @@ class PlatformOceanBookingControllerContractTest {
|
||||
Map.entry("channels", "platform:firstleg:ocean-booking:list"),
|
||||
Map.entry("detail", "platform:firstleg:ocean-booking:list"),
|
||||
Map.entry("create", "platform:firstleg:ocean-booking:create"),
|
||||
Map.entry("updateDraft", "platform:firstleg:ocean-booking:edit"),
|
||||
Map.entry("submit", "platform:firstleg:ocean-booking:submit"),
|
||||
Map.entry("confirm", "platform:firstleg:ocean-booking:confirm"),
|
||||
Map.entry("fail", "platform:firstleg:ocean-booking:confirm"),
|
||||
Map.entry("release", "platform:firstleg:ocean-booking:release"),
|
||||
Map.entry("cancel", "platform:firstleg:ocean-booking:cancel"),
|
||||
Map.entry("requestChange", "platform:firstleg:ocean-booking:change"),
|
||||
Map.entry("confirmChange", "platform:firstleg:ocean-booking:change"),
|
||||
Map.entry("failChange", "platform:firstleg:ocean-booking:change"));
|
||||
Map.entry("confirmChange", "platform:firstleg:ocean-booking:change"));
|
||||
permissions.forEach((name, permission) -> {
|
||||
Method method = method(name);
|
||||
assertThat(method.getAnnotation(PreAuthorize.class).value())
|
||||
@@ -56,13 +51,19 @@ class PlatformOceanBookingControllerContractTest {
|
||||
String yaml = Files.readString(Path.of("src/main/resources/openapi/ocean-bookings-v1.yaml"));
|
||||
assertThat(yaml).contains("/api/platform/v1/ocean-bookings/channels:",
|
||||
"originWarehouseId", "platform:firstleg:ocean-booking:list", "ChannelOption")
|
||||
.contains("required: [channelCode, warehouseId, warehouseName, serviceTerm, freightTerm, parties, containerRequirements]")
|
||||
.contains("required: [channelCode, warehouseId, warehouseName, billOfLadingNo, vesselName, voyageNo, serviceTerm, freightTerm, estimatedSailingAt, estimatedArrivalAt, parties, containerRequirements]")
|
||||
.contains("registration persists it directly as CONFIRMED")
|
||||
.contains("RELEASED remains a separate later action")
|
||||
.contains("request must repeat the same bill of lading, vessel, voyage, ETD and ETA values; mismatches are rejected")
|
||||
.contains("CONFIRMED, CHANGE_PENDING, RELEASED, CANCELLED")
|
||||
.contains("DRAFT, SUBMITTED and FAILED are excluded")
|
||||
.contains("DRAFT is internal to the create transaction and legacy SUBMITTED/FAILED values are invalid")
|
||||
.contains("customerId: {type: integer, format: int64, minimum: 1, nullable: true")
|
||||
.contains("customerName: {type: string, maxLength: 128, nullable: true")
|
||||
.contains("serviceTerm: {type: string, maxLength: 32, description: Required free-text")
|
||||
.contains("freightTerm: {type: string, maxLength: 32, description: Required free-text")
|
||||
.contains("billOfLadingNo: {type: string, maxLength: 128, description: Required offline-confirmed")
|
||||
.contains("estimatedArrivalAt: {type: string, format: date-time, description: Required offline-confirmed ETA")
|
||||
.doesNotContain("Must be omitted until release")
|
||||
.doesNotContain("required: [customerId, customerName, channelCode, channelName");
|
||||
}
|
||||
|
||||
|
||||
+3
-2
@@ -30,9 +30,10 @@ class OceanBookingMapperContractTest {
|
||||
.flatMap(annotation -> Arrays.stream(annotation.value()))
|
||||
.reduce("", (left, right) -> left + '\n' + right);
|
||||
assertThat(sql)
|
||||
.contains("status='DRAFT' AND version=#{expectedVersion}")
|
||||
.contains("status=#{fromStatus} AND version=#{expectedVersion}")
|
||||
.contains("status='CONFIRMED' AND version=#{expectedVersion}")
|
||||
.contains("status='CHANGE_PENDING' AND version=#{expectedVersion}");
|
||||
.contains("status='CHANGE_PENDING' AND version=#{expectedVersion}")
|
||||
.doesNotContain("SUBMITTED")
|
||||
.doesNotContain("FAILED");
|
||||
}
|
||||
}
|
||||
|
||||
+2
-5
@@ -32,9 +32,9 @@ class OceanBookingSqlContractTest {
|
||||
@Test
|
||||
void enforcesStateIdempotencyAndAuditContracts() throws Exception {
|
||||
assertThat(sql())
|
||||
.contains("OLD.status='DRAFT' AND NEW.status IN ('SUBMITTED','CANCELLED')")
|
||||
.contains("OLD.status='DRAFT' AND NEW.status='CONFIRMED'")
|
||||
.contains("OLD.status='CONFIRMED' AND NEW.status IN ('RELEASED','CHANGE_PENDING','CANCELLED')")
|
||||
.contains("OLD.status='CHANGE_PENDING' AND NEW.status IN ('CONFIRMED','FAILED','CANCELLED')")
|
||||
.contains("OLD.status='CHANGE_PENDING' AND NEW.status IN ('CONFIRMED','CANCELLED')")
|
||||
.contains("OLD.status IN ('RELEASED','CANCELLED')")
|
||||
.contains("UNIQUE KEY uk_ocean_operation (operator_id,operation,idempotency_key)")
|
||||
.contains("request_hash CHAR(64) NOT NULL")
|
||||
@@ -47,9 +47,6 @@ class OceanBookingSqlContractTest {
|
||||
assertThat(sql())
|
||||
.contains("platform:firstleg:ocean-booking:list")
|
||||
.contains("platform:firstleg:ocean-booking:create")
|
||||
.contains("platform:firstleg:ocean-booking:edit")
|
||||
.contains("platform:firstleg:ocean-booking:submit")
|
||||
.contains("platform:firstleg:ocean-booking:confirm")
|
||||
.contains("platform:firstleg:ocean-booking:release")
|
||||
.contains("platform:firstleg:ocean-booking:cancel")
|
||||
.contains("platform:firstleg:ocean-booking:change")
|
||||
|
||||
+100
-100
@@ -10,7 +10,6 @@ import com.baiyue.firstleg.oceanbooking.dto.OceanBookingDtos.DraftData;
|
||||
import com.baiyue.firstleg.oceanbooking.dto.OceanBookingDtos.Query;
|
||||
import com.baiyue.firstleg.oceanbooking.dto.OceanBookingDtos.PartyRequest;
|
||||
import com.baiyue.firstleg.oceanbooking.dto.OceanBookingDtos.ReleaseRequest;
|
||||
import com.baiyue.firstleg.oceanbooking.dto.OceanBookingDtos.UpdateDraftRequest;
|
||||
import com.baiyue.firstleg.service.LoadingPickService;
|
||||
import com.baiyue.firstleg.oceanbooking.entity.OceanBookingContainerRequirementEntity;
|
||||
import com.baiyue.firstleg.oceanbooking.entity.OceanBookingContainerAllocationEntity;
|
||||
@@ -27,6 +26,7 @@ import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.math.BigDecimal;
|
||||
@@ -55,27 +55,8 @@ class OceanBookingServiceTest {
|
||||
when(repository.histories(anyLong())).thenReturn(List.of());
|
||||
when(repository.claimOperation(any(), anyLong(), anyString(), anyString(), anyString())).thenReturn(true);
|
||||
when(repository.completeOperation(anyLong(), anyLong(), anyString(), anyString(), anyString())).thenReturn(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void advancesDraftToSubmittedWithVersionAndHistory() {
|
||||
when(repository.find(1L)).thenReturn(booking("DRAFT", 2), booking("SUBMITTED", 3));
|
||||
var shipper = new com.baiyue.firstleg.oceanbooking.entity.OceanBookingPartyEntity(); shipper.setPartyType("SHIPPER");
|
||||
var consignee = new com.baiyue.firstleg.oceanbooking.entity.OceanBookingPartyEntity(); consignee.setPartyType("CONSIGNEE");
|
||||
var notify = new com.baiyue.firstleg.oceanbooking.entity.OceanBookingPartyEntity(); notify.setPartyType("NOTIFY_PARTY");
|
||||
when(repository.parties(1L)).thenReturn(List.of(shipper, consignee, notify));
|
||||
when(repository.requirements(1L)).thenReturn(List.of(new com.baiyue.firstleg.oceanbooking.entity.OceanBookingContainerRequirementEntity()));
|
||||
when(repository.transition(1L, "DRAFT", "SUBMITTED", null, null, 2, 9L, "admin")).thenReturn(1);
|
||||
|
||||
Detail result = service.submit(1L, 9L, "admin", "submit-1", new ActionRequest(2, "提交"));
|
||||
|
||||
assertThat(result.oceanBooking().getStatus()).isEqualTo("SUBMITTED");
|
||||
ArgumentCaptor<OceanBookingStatusHistoryEntity> history =
|
||||
ArgumentCaptor.forClass(OceanBookingStatusHistoryEntity.class);
|
||||
verify(repository).insert(history.capture());
|
||||
assertThat(history.getValue().getFromStatus()).isEqualTo("DRAFT");
|
||||
assertThat(history.getValue().getToStatus()).isEqualTo("SUBMITTED");
|
||||
assertThat(history.getValue().getAction()).isEqualTo("SUBMIT");
|
||||
when(repository.transition(anyLong(), eq("DRAFT"), eq("CONFIRMED"), isNull(), eq(0),
|
||||
anyLong(), anyString())).thenReturn(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -86,12 +67,16 @@ class OceanBookingServiceTest {
|
||||
new ActionRequest(4, "错误"))).isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("不可逆");
|
||||
|
||||
verify(repository, never()).transition(anyLong(), anyString(), anyString(), any(), any(), anyInt(), anyLong(), anyString());
|
||||
verify(repository, never()).transition(anyLong(), anyString(), anyString(), any(), anyInt(), anyLong(), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void opensAllocatedContainerPdaTasksAfterRelease() {
|
||||
OceanBookingEntity confirmed = booking("CONFIRMED", 3);
|
||||
confirmed.setBillOfLadingNo("BL-001"); confirmed.setVesselName("VESSEL");
|
||||
confirmed.setVoyageNo("V001");
|
||||
confirmed.setEstimatedSailingAt(LocalDateTime.of(2026, 9, 1, 0, 0));
|
||||
confirmed.setEstimatedArrivalAt(LocalDateTime.of(2026, 9, 20, 0, 0));
|
||||
OceanBookingEntity released = booking("RELEASED", 4);
|
||||
var allocation = new OceanBookingContainerAllocationEntity();
|
||||
allocation.setContainerBookingId(12L);
|
||||
@@ -107,6 +92,25 @@ class OceanBookingServiceTest {
|
||||
verify(loadingPickService).beginIfReady(12L, 9L, "admin");
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsReleaseWhenTransportFieldsDifferFromRegisteredSnapshot() {
|
||||
OceanBookingEntity confirmed = booking("CONFIRMED", 3);
|
||||
confirmed.setBillOfLadingNo("BL-001"); confirmed.setVesselName("VESSEL");
|
||||
confirmed.setVoyageNo("V001");
|
||||
confirmed.setEstimatedSailingAt(LocalDateTime.of(2026, 9, 1, 0, 0));
|
||||
confirmed.setEstimatedArrivalAt(LocalDateTime.of(2026, 9, 20, 0, 0));
|
||||
when(repository.find(1L)).thenReturn(confirmed);
|
||||
|
||||
var request = new ReleaseRequest(3, "BL-CHANGED", "VESSEL", "V001",
|
||||
LocalDateTime.of(2026, 9, 1, 0, 0), LocalDateTime.of(2026, 9, 20, 0, 0), "放舱");
|
||||
|
||||
assertThatThrownBy(() -> service.release(1L, 9L, "admin", "release-mismatch", request))
|
||||
.isInstanceOf(BizException.class).hasMessageContaining("必须与已登记");
|
||||
|
||||
verify(repository, never()).release(anyLong(), any(), anyLong(), anyString());
|
||||
verifyNoInteractions(loadingPickService);
|
||||
}
|
||||
|
||||
@Test
|
||||
void keepsCurrentVoyagePendingUntilChangeConfirmation() {
|
||||
OceanBookingEntity current = booking("CONFIRMED", 5);
|
||||
@@ -126,27 +130,11 @@ class OceanBookingServiceTest {
|
||||
assertThat(result.oceanBooking().getPendingVesselName()).isEqualTo("NEW");
|
||||
}
|
||||
|
||||
@Test
|
||||
void replaysCompletedRequestBeforeVersionValidation() throws Exception {
|
||||
OceanBookingEntity submitted = booking("SUBMITTED", 3);
|
||||
Detail replay = new Detail(submitted, List.of(), List.of(), List.of(), List.of());
|
||||
OceanBookingOperationEntity operation = new OceanBookingOperationEntity();
|
||||
operation.setRequestHash(hashForSubmit()); operation.setStatus("SUCCESS");
|
||||
operation.setResultJson(new ObjectMapper().findAndRegisterModules().writeValueAsString(replay));
|
||||
when(repository.claimOperation(eq(1L), eq(9L), eq("SUBMIT"), eq("submit-replay"), anyString())).thenReturn(false);
|
||||
when(repository.operation(9L, "SUBMIT", "submit-replay")).thenReturn(operation);
|
||||
|
||||
Detail result = service.submit(1L, 9L, "admin", "submit-replay", new ActionRequest(2, "提交"));
|
||||
|
||||
assertThat(result.oceanBooking().getStatus()).isEqualTo("SUBMITTED");
|
||||
verify(repository, never()).find(anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsDraftAndSubmittedStatusesFromOperationalList() {
|
||||
for (String status : List.of("DRAFT", "SUBMITTED", "FAILED")) {
|
||||
assertThatThrownBy(() -> service.page(new Query(null, status, null, 1, 20)))
|
||||
.isInstanceOf(BizException.class).hasMessageContaining("仅支持");
|
||||
.isInstanceOf(BizException.class).hasMessageContaining("状态");
|
||||
}
|
||||
verify(repository, never()).page(any());
|
||||
}
|
||||
@@ -176,19 +164,33 @@ class OceanBookingServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void freezesAuthoritativeChannelNameWhenCreatingDraft() {
|
||||
void registersOfflineBookingDirectlyAsConfirmedWithCompleteTransportSnapshot() {
|
||||
ChannelRow channel = channel("SEA-US", "价格簿美西海运");
|
||||
when(priceBooks.activeVersionId()).thenReturn(51L);
|
||||
when(priceBooks.channels(51L, 701L)).thenReturn(List.of(channel));
|
||||
AtomicReference<OceanBookingEntity> inserted = new AtomicReference<>();
|
||||
doAnswer(invocation -> { inserted.set(invocation.getArgument(0)); return null; })
|
||||
.when(repository).insert(any(OceanBookingEntity.class));
|
||||
when(repository.find(anyLong())).thenAnswer(invocation -> inserted.get());
|
||||
AtomicReference<OceanBookingEntity> inserted = stubSuccessfulCreation();
|
||||
|
||||
Detail result = service.create(9L, "admin", "create-channel", new CreateRequest(0, draft()));
|
||||
|
||||
assertThat(inserted.get().getStatus()).isEqualTo("DRAFT");
|
||||
assertThat(result.oceanBooking().getStatus()).isEqualTo("CONFIRMED");
|
||||
assertThat(result.oceanBooking().getChannelCode()).isEqualTo("SEA-US");
|
||||
assertThat(result.oceanBooking().getChannelName()).isEqualTo("价格簿美西海运");
|
||||
assertThat(result.oceanBooking().getBillOfLadingNo()).isEqualTo("BL-001");
|
||||
assertThat(result.oceanBooking().getVesselName()).isEqualTo("VESSEL");
|
||||
assertThat(result.oceanBooking().getVoyageNo()).isEqualTo("V001");
|
||||
assertThat(result.oceanBooking().getEstimatedSailingAt())
|
||||
.isEqualTo(LocalDateTime.of(2026, 9, 1, 0, 0));
|
||||
assertThat(result.oceanBooking().getEstimatedArrivalAt())
|
||||
.isEqualTo(LocalDateTime.of(2026, 9, 20, 0, 0));
|
||||
ArgumentCaptor<OceanBookingStatusHistoryEntity> history =
|
||||
ArgumentCaptor.forClass(OceanBookingStatusHistoryEntity.class);
|
||||
verify(repository).insert(history.capture());
|
||||
assertThat(history.getValue().getFromStatus()).isEqualTo("DRAFT");
|
||||
assertThat(history.getValue().getToStatus()).isEqualTo("CONFIRMED");
|
||||
assertThat(history.getValue().getAction()).isEqualTo("CREATE");
|
||||
verify(repository).transition(inserted.get().getId(), "DRAFT", "CONFIRMED", null, 0, 9L,
|
||||
"admin");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -196,10 +198,7 @@ class OceanBookingServiceTest {
|
||||
ChannelRow channel = channel("SEA-US", "价格簿美西海运");
|
||||
when(priceBooks.activeVersionId()).thenReturn(51L);
|
||||
when(priceBooks.channels(51L, 701L)).thenReturn(List.of(channel));
|
||||
AtomicReference<OceanBookingEntity> inserted = new AtomicReference<>();
|
||||
doAnswer(invocation -> { inserted.set(invocation.getArgument(0)); return null; })
|
||||
.when(repository).insert(any(OceanBookingEntity.class));
|
||||
when(repository.find(anyLong())).thenAnswer(invocation -> inserted.get());
|
||||
stubSuccessfulCreation();
|
||||
|
||||
Detail result = service.create(9L, "admin", "create-without-customer",
|
||||
new CreateRequest(0, draft(null, null, "CY-CY", "NINGBO")));
|
||||
@@ -234,16 +233,48 @@ class OceanBookingServiceTest {
|
||||
verifyNoInteractions(priceBooks);
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsOfflineBookingWhenEtaIsEarlierThanEtd() {
|
||||
DraftData source = draft();
|
||||
DraftData invalid = new DraftData(source.customerId(), source.customerName(), source.channelCode(),
|
||||
source.warehouseId(), source.warehouseName(), source.billOfLadingNo(), source.shippingCompanyId(),
|
||||
source.shippingCompanyName(), source.bookingAgentId(), source.bookingAgentName(),
|
||||
source.vesselName(), source.voyageNo(), source.departurePortCode(), source.departurePortName(),
|
||||
source.destinationPortCode(), source.destinationPortName(), source.placeOfDelivery(),
|
||||
source.serviceTerm(), source.freightTerm(), source.estimatedArrivalAt(), source.estimatedSailingAt(),
|
||||
source.remark(), source.parties(), source.containerRequirements());
|
||||
|
||||
assertThatThrownBy(() -> service.create(9L, "admin", "invalid-range", new CreateRequest(0, invalid)))
|
||||
.isInstanceOf(BizException.class).hasMessageContaining("预计到港时间不能早于预计开船时间");
|
||||
|
||||
verifyNoInteractions(priceBooks);
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsOfflineBookingWithoutCompleteTransportSnapshot() {
|
||||
DraftData source = draft();
|
||||
DraftData missingBill = new DraftData(source.customerId(), source.customerName(), source.channelCode(),
|
||||
source.warehouseId(), source.warehouseName(), null, source.shippingCompanyId(),
|
||||
source.shippingCompanyName(), source.bookingAgentId(), source.bookingAgentName(),
|
||||
source.vesselName(), source.voyageNo(), source.departurePortCode(), source.departurePortName(),
|
||||
source.destinationPortCode(), source.destinationPortName(), source.placeOfDelivery(),
|
||||
source.serviceTerm(), source.freightTerm(), source.estimatedSailingAt(), source.estimatedArrivalAt(),
|
||||
source.remark(), source.parties(), source.containerRequirements());
|
||||
|
||||
assertThatThrownBy(() -> service.create(9L, "admin", "missing-transport",
|
||||
new CreateRequest(0, missingBill))).isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("提单号、船名、航次、ETD和ETA不能为空");
|
||||
|
||||
verifyNoInteractions(priceBooks);
|
||||
}
|
||||
|
||||
@Test
|
||||
void replaysCreateAfterChannelIsNoLongerActive() throws Exception {
|
||||
ChannelRow channel = channel("SEA-US", "价格簿美西海运");
|
||||
CreateRequest request = new CreateRequest(0, draft());
|
||||
when(priceBooks.activeVersionId()).thenReturn(51L, (Long) null);
|
||||
when(priceBooks.channels(51L, 701L)).thenReturn(List.of(channel));
|
||||
AtomicReference<OceanBookingEntity> inserted = new AtomicReference<>();
|
||||
doAnswer(invocation -> { inserted.set(invocation.getArgument(0)); return null; })
|
||||
.when(repository).insert(any(OceanBookingEntity.class));
|
||||
when(repository.find(anyLong())).thenAnswer(invocation -> inserted.get());
|
||||
stubSuccessfulCreation();
|
||||
|
||||
Detail created = service.create(9L, "admin", "create-replay-channel", request);
|
||||
|
||||
@@ -260,40 +291,6 @@ class OceanBookingServiceTest {
|
||||
verify(priceBooks, times(1)).activeVersionId();
|
||||
}
|
||||
|
||||
@Test
|
||||
void replaysDraftUpdateAfterChannelIsNoLongerActive() throws Exception {
|
||||
UpdateDraftRequest request = new UpdateDraftRequest(5, draft());
|
||||
Detail expected = new Detail(booking("DRAFT", 6), List.of(), List.of(), List.of(), List.of());
|
||||
OceanBookingOperationEntity operation = new OceanBookingOperationEntity();
|
||||
operation.setRequestHash(hashForUpdate(request)); operation.setStatus("SUCCESS");
|
||||
operation.setResultJson(new ObjectMapper().findAndRegisterModules().writeValueAsString(expected));
|
||||
when(repository.claimOperation(eq(1L), eq(9L), eq("UPDATE_DRAFT"), eq("update-replay-channel"), anyString()))
|
||||
.thenReturn(false);
|
||||
when(repository.operation(9L, "UPDATE_DRAFT", "update-replay-channel")).thenReturn(operation);
|
||||
|
||||
Detail replay = service.updateDraft(1L, 9L, "admin", "update-replay-channel", request);
|
||||
|
||||
assertThat(replay.oceanBooking().getVersion()).isEqualTo(6);
|
||||
verifyNoInteractions(priceBooks);
|
||||
verify(repository, never()).find(anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsConfirmationWhenFrozenChannelIsNoLongerActive() {
|
||||
OceanBookingEntity submitted = booking("SUBMITTED", 3);
|
||||
submitted.setWarehouseId(701L); submitted.setWarehouseName("深圳仓"); submitted.setChannelName("美西海运");
|
||||
OceanBookingContainerRequirementEntity requirement = new OceanBookingContainerRequirementEntity();
|
||||
requirement.setCapacityCbm(java.math.BigDecimal.ONE); requirement.setMaxWeightKg(java.math.BigDecimal.ONE);
|
||||
when(repository.find(1L)).thenReturn(submitted);
|
||||
when(repository.requirements(1L)).thenReturn(List.of(requirement));
|
||||
when(priceBooks.activeVersionId()).thenReturn(null);
|
||||
|
||||
assertThatThrownBy(() -> service.confirm(1L, 9L, "admin", "confirm-channel", new ActionRequest(3, "确认")))
|
||||
.isInstanceOf(BizException.class).hasMessageContaining("ACTIVE");
|
||||
|
||||
verify(repository, never()).transition(anyLong(), anyString(), anyString(), any(), any(), anyInt(), anyLong(), anyString());
|
||||
}
|
||||
|
||||
private static ChannelRow channel(String code, String name) {
|
||||
ChannelRow value = new ChannelRow(); value.setPriceBookVersionId(51L); value.setChannelCode(code);
|
||||
value.setChannelName(name); value.setPriceModel("FBA_WAREHOUSE_RATE"); return value;
|
||||
@@ -310,15 +307,24 @@ class OceanBookingServiceTest {
|
||||
new PartyRequest("NOTIFY_PARTY", "通知人", null, null, null));
|
||||
List<ContainerRequirementRequest> requirements = List.of(
|
||||
new ContainerRequirementRequest("40HQ", "DRY", BigDecimal.ONE, BigDecimal.TEN, 1));
|
||||
return new DraftData(customerId, customerName, "SEA-US", 701L, "深圳仓", null,
|
||||
81L, "船公司", null, null, null, null, "CNSZX", "深圳", "USLAX", "洛杉矶",
|
||||
null, serviceTerm, freightTerm, null, null, null, parties, requirements);
|
||||
return new DraftData(customerId, customerName, "SEA-US", 701L, "深圳仓", "BL-001",
|
||||
81L, "船公司", null, null, "VESSEL", "V001", "CNSZX", "深圳", "USLAX", "洛杉矶",
|
||||
null, serviceTerm, freightTerm, LocalDateTime.of(2026, 9, 1, 0, 0),
|
||||
LocalDateTime.of(2026, 9, 20, 0, 0), null, parties, requirements);
|
||||
}
|
||||
|
||||
private String hashForSubmit() throws Exception {
|
||||
var method = OceanBookingService.class.getDeclaredMethod("hash", String.class, Long.class, Object.class);
|
||||
method.setAccessible(true);
|
||||
return (String) method.invoke(service, "SUBMIT", 1L, new ActionRequest(2, "提交"));
|
||||
private AtomicReference<OceanBookingEntity> stubSuccessfulCreation() {
|
||||
AtomicReference<OceanBookingEntity> inserted = new AtomicReference<>();
|
||||
doAnswer(invocation -> { inserted.set(invocation.getArgument(0)); return null; })
|
||||
.when(repository).insert(any(OceanBookingEntity.class));
|
||||
when(repository.find(anyLong())).thenAnswer(invocation -> {
|
||||
OceanBookingEntity confirmed = new OceanBookingEntity();
|
||||
BeanUtils.copyProperties(inserted.get(), confirmed);
|
||||
confirmed.setStatus("CONFIRMED");
|
||||
confirmed.setVersion(1);
|
||||
return confirmed;
|
||||
});
|
||||
return inserted;
|
||||
}
|
||||
|
||||
private String hashForCreate(CreateRequest request) throws Exception {
|
||||
@@ -327,12 +333,6 @@ class OceanBookingServiceTest {
|
||||
return (String) method.invoke(service, "CREATE", (Long) null, request);
|
||||
}
|
||||
|
||||
private String hashForUpdate(UpdateDraftRequest request) throws Exception {
|
||||
var method = OceanBookingService.class.getDeclaredMethod("hash", String.class, Long.class, Object.class);
|
||||
method.setAccessible(true);
|
||||
return (String) method.invoke(service, "UPDATE_DRAFT", 1L, request);
|
||||
}
|
||||
|
||||
private static OceanBookingEntity booking(String status, int version) {
|
||||
OceanBookingEntity value = new OceanBookingEntity(); value.setId(1L); value.setBookingNo("OB1");
|
||||
value.setStatus(status); value.setCustomerId(100L); value.setCustomerName("客户");
|
||||
|
||||
-6
@@ -25,9 +25,6 @@ class Phase3BookingTruckPermissionSqlContractTest {
|
||||
private static final Set<String> OCEAN_CONTROLLER_PERMISSIONS = Set.of(
|
||||
"platform:firstleg:ocean-booking:list",
|
||||
"platform:firstleg:ocean-booking:create",
|
||||
"platform:firstleg:ocean-booking:edit",
|
||||
"platform:firstleg:ocean-booking:submit",
|
||||
"platform:firstleg:ocean-booking:confirm",
|
||||
"platform:firstleg:ocean-booking:release",
|
||||
"platform:firstleg:ocean-booking:cancel",
|
||||
"platform:firstleg:ocean-booking:change");
|
||||
@@ -35,9 +32,6 @@ class Phase3BookingTruckPermissionSqlContractTest {
|
||||
private static final Set<String> OCEAN_SQL_PERMISSIONS = Set.of(
|
||||
"platform:firstleg:ocean-booking:list",
|
||||
"platform:firstleg:ocean-booking:create",
|
||||
"platform:firstleg:ocean-booking:edit",
|
||||
"platform:firstleg:ocean-booking:submit",
|
||||
"platform:firstleg:ocean-booking:confirm",
|
||||
"platform:firstleg:ocean-booking:release",
|
||||
"platform:firstleg:ocean-booking:cancel",
|
||||
"platform:firstleg:ocean-booking:change",
|
||||
|
||||
Reference in New Issue
Block a user