From 5c6c0ef5ca0dcee3660003cb1765a02b7ff7be18 Mon Sep 17 00:00:00 2001 From: liuyihe <2513762445@qq.com> Date: Wed, 26 Aug 2026 15:51:46 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../delivery/dto/DeliveryRequests.java | 11 +++------ .../service/DeliveryTransferService.java | 24 +++++++++++++++++++ .../service/DeliveryTransferServiceTest.java | 19 ++++++++++----- 3 files changed, 40 insertions(+), 14 deletions(-) diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/dto/DeliveryRequests.java b/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/dto/DeliveryRequests.java index 672d64a26..d3058a005 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/dto/DeliveryRequests.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/dto/DeliveryRequests.java @@ -18,8 +18,8 @@ public final class DeliveryRequests { @NotBlank @Size(max = 32) String deliveryMethod, @Size(max = 128) String waybillNo, @Size(max = 255) String carrierName, - @Size(max = 64) String pickupMethod, - @Size(max = 64) String loadingType, + @NotBlank @Size(max = 64) String pickupMethod, + @NotBlank @Size(max = 64) String loadingType, @Size(max = 64) String vehicleType, @Size(max = 64) String vehicleSize, @Size(max = 128) String driverName, @@ -28,12 +28,7 @@ public final class DeliveryRequests { @Size(max = 128) String appointmentAccount, @Size(max = 128) String sellerName, @Size(max = 1000) String remark, - @Valid @Size(max = 20) List fees) { - public Transfer(String idempotencyKey, String remark) { - this(idempotencyKey, LocalDateTime.now(), "SELF_DELIVERY", null, null, null, null, - null, null, null, null, null, null, null, remark, List.of()); - } - } + @NotNull @Valid @Size(min = 1, max = 20) List fees) {} public record FeeItem(@NotBlank @Size(max = 64) String feeType, @NotBlank @Size(max = 8) String currency, diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/service/DeliveryTransferService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/service/DeliveryTransferService.java index 0c9d26c6b..4394ab6bd 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/service/DeliveryTransferService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/delivery/service/DeliveryTransferService.java @@ -100,6 +100,7 @@ public class DeliveryTransferService { throw conflict("All dispatch pallets must be received before transfer to delivery"); } if (!StringUtils.hasText(dispatch.getOverseasWarehouseId())) throw conflict("Dispatch warehouse is required"); + validateTransferFees(request); LocalDateTime now = LocalDateTime.now(); OutboundOrder outbound = new OutboundOrder(); @@ -168,6 +169,29 @@ public class DeliveryTransferService { return result(outbound, delivery, false); } + private void validateTransferFees(DeliveryRequests.Transfer request) { + List inputs = request.fees() == null ? List.of() : request.fees(); + if (inputs.isEmpty()) throw conflict("Delivery fee is required"); + Set allowed = Set.of("DELIVERY_FEE", "LOADING_FEE", "APPOINTMENT_FEE"); + Set seen = new HashSet<>(); + for (DeliveryRequests.FeeItem input : inputs) { + String feeType = input.feeType().trim().toUpperCase(Locale.ROOT); + if (!allowed.contains(feeType)) throw conflict("Unsupported delivery fee type: " + feeType); + if (!seen.add(feeType)) throw conflict("Duplicate delivery fee type: " + feeType); + if (!"USD".equalsIgnoreCase(input.currency().trim())) throw conflict("Delivery fees must use USD"); + if (input.amount().signum() < 0) throw conflict("Delivery fee amount cannot be negative"); + } + if (!seen.contains("DELIVERY_FEE")) throw conflict("Delivery fee is required"); + if (Set.of("FLOOR", "MIXED").contains(request.loadingType().trim().toUpperCase(Locale.ROOT)) + && !seen.contains("LOADING_FEE")) { + throw conflict("Loading fee is required for floor or mixed loading"); + } + if ("BUY_APPOINTMENT".equalsIgnoreCase(trim(request.appointmentAccount()))) { + if (!StringUtils.hasText(request.sellerName())) throw conflict("Seller name is required when buying an appointment"); + if (!seen.contains("APPOINTMENT_FEE")) throw conflict("Appointment fee is required when buying an appointment"); + } + } + @Transactional public DeliveryResponses.PdaTask createOneClick(String customerId, String operatorId, DeliveryRequests.OneClickOutbound request) { OutboundOrder existing = outboundOrders.findByIdempotency(customerId, request.idempotencyKey().trim()); diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/delivery/service/DeliveryTransferServiceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/delivery/service/DeliveryTransferServiceTest.java index 7b4d63944..c23a505a6 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/delivery/service/DeliveryTransferServiceTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/delivery/service/DeliveryTransferServiceTest.java @@ -31,6 +31,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.web.server.ResponseStatusException; +import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.List; @@ -88,7 +89,7 @@ class DeliveryTransferServiceTest { )); DeliveryResponses.TransferResult result = service.transfer("TENANT-A", "DSP-101", "U-1", - new DeliveryRequests.Transfer("transfer-101", "ready")); + transferRequest("transfer-101", "ready")); ArgumentCaptor outbound = ArgumentCaptor.forClass(OutboundOrder.class); ArgumentCaptor delivery = ArgumentCaptor.forClass(DeliveryOrder.class); @@ -125,7 +126,7 @@ class DeliveryTransferServiceTest { when(deliveryOrders.lockByOutbound("TENANT-A", 301L)).thenReturn(delivery); DeliveryResponses.TransferResult result = service.transfer("TENANT-A", "DSP-101", "U-1", - new DeliveryRequests.Transfer(" transfer-101 ", null)); + transferRequest(" transfer-101 ", null)); assertTrue(result.replayed()); assertEquals("OUT-301", result.outboundNo()); @@ -144,7 +145,7 @@ class DeliveryTransferServiceTest { when(outboundOrders.findByIdempotency("TENANT-A", "shared-key")).thenReturn(other); ResponseStatusException exception = assertThrows(ResponseStatusException.class, () -> - service.transfer("TENANT-A", "DSP-102", "U-1", new DeliveryRequests.Transfer("shared-key", null))); + service.transfer("TENANT-A", "DSP-102", "U-1", transferRequest("shared-key", null))); assertEquals(409, exception.getStatusCode().value()); verify(outboundOrders, never()).insert((OutboundOrder) any()); @@ -164,7 +165,7 @@ class DeliveryTransferServiceTest { when(deliveryOrders.lockByOutbound("TENANT-A", 301L)).thenReturn(delivery); DeliveryResponses.TransferResult result = service.transfer("TENANT-A", "DSP-101", "U-1", - new DeliveryRequests.Transfer("transfer-101", null)); + transferRequest("transfer-101", null)); assertTrue(result.replayed()); assertEquals("OUT-301", result.outboundNo()); @@ -185,7 +186,7 @@ class DeliveryTransferServiceTest { when(dispatchItems.findByDispatchOrder("TENANT-A", 101L)).thenReturn(List.of(source)); when(tailOrders.selectList(any())).thenReturn(List.of(tailOrder)); - service.transfer("TENANT-A", "DSP-101", "U-1", new DeliveryRequests.Transfer("direct-order", null)); + service.transfer("TENANT-A", "DSP-101", "U-1", transferRequest("direct-order", null)); assertEquals("WAIT_OUTBOUND", tailOrder.getCurrentStatus()); assertNotNull(tailOrder.getOutboundOrderNo()); @@ -204,7 +205,7 @@ class DeliveryTransferServiceTest { when(dispatchItems.findByDispatchOrder("TENANT-A", 101L)).thenReturn(List.of(planned)); ResponseStatusException exception = assertThrows(ResponseStatusException.class, () -> - service.transfer("TENANT-A", "DSP-101", "U-1", new DeliveryRequests.Transfer("transfer-101", null))); + service.transfer("TENANT-A", "DSP-101", "U-1", transferRequest("transfer-101", null))); assertEquals(409, exception.getStatusCode().value()); assertTrue(exception.getReason().contains("received")); @@ -248,6 +249,12 @@ class DeliveryTransferServiceTest { return value; } + private static DeliveryRequests.Transfer transferRequest(String idempotencyKey, String remark) { + return new DeliveryRequests.Transfer(idempotencyKey, LocalDateTime.now(), "SELF_DELIVERY", null, null, + "ONE_PICKUP_ONE_DROP", "PALLET", null, null, null, null, null, null, null, remark, + List.of(new DeliveryRequests.FeeItem("DELIVERY_FEE", "USD", BigDecimal.ZERO, null))); + } + private static DispatchItem item(Long id, Long warehousePalletId, String palletNo, String destinationCode, String destinationName, String inboundCode, int packages) { DispatchItem value = new DispatchItem();