提交
This commit is contained in:
@@ -18,8 +18,8 @@ public final class DeliveryRequests {
|
|||||||
@NotBlank @Size(max = 32) String deliveryMethod,
|
@NotBlank @Size(max = 32) String deliveryMethod,
|
||||||
@Size(max = 128) String waybillNo,
|
@Size(max = 128) String waybillNo,
|
||||||
@Size(max = 255) String carrierName,
|
@Size(max = 255) String carrierName,
|
||||||
@Size(max = 64) String pickupMethod,
|
@NotBlank @Size(max = 64) String pickupMethod,
|
||||||
@Size(max = 64) String loadingType,
|
@NotBlank @Size(max = 64) String loadingType,
|
||||||
@Size(max = 64) String vehicleType,
|
@Size(max = 64) String vehicleType,
|
||||||
@Size(max = 64) String vehicleSize,
|
@Size(max = 64) String vehicleSize,
|
||||||
@Size(max = 128) String driverName,
|
@Size(max = 128) String driverName,
|
||||||
@@ -28,12 +28,7 @@ public final class DeliveryRequests {
|
|||||||
@Size(max = 128) String appointmentAccount,
|
@Size(max = 128) String appointmentAccount,
|
||||||
@Size(max = 128) String sellerName,
|
@Size(max = 128) String sellerName,
|
||||||
@Size(max = 1000) String remark,
|
@Size(max = 1000) String remark,
|
||||||
@Valid @Size(max = 20) List<FeeItem> fees) {
|
@NotNull @Valid @Size(min = 1, max = 20) List<FeeItem> 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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public record FeeItem(@NotBlank @Size(max = 64) String feeType,
|
public record FeeItem(@NotBlank @Size(max = 64) String feeType,
|
||||||
@NotBlank @Size(max = 8) String currency,
|
@NotBlank @Size(max = 8) String currency,
|
||||||
|
|||||||
+24
@@ -100,6 +100,7 @@ public class DeliveryTransferService {
|
|||||||
throw conflict("All dispatch pallets must be received before transfer to delivery");
|
throw conflict("All dispatch pallets must be received before transfer to delivery");
|
||||||
}
|
}
|
||||||
if (!StringUtils.hasText(dispatch.getOverseasWarehouseId())) throw conflict("Dispatch warehouse is required");
|
if (!StringUtils.hasText(dispatch.getOverseasWarehouseId())) throw conflict("Dispatch warehouse is required");
|
||||||
|
validateTransferFees(request);
|
||||||
|
|
||||||
LocalDateTime now = LocalDateTime.now();
|
LocalDateTime now = LocalDateTime.now();
|
||||||
OutboundOrder outbound = new OutboundOrder();
|
OutboundOrder outbound = new OutboundOrder();
|
||||||
@@ -168,6 +169,29 @@ public class DeliveryTransferService {
|
|||||||
return result(outbound, delivery, false);
|
return result(outbound, delivery, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void validateTransferFees(DeliveryRequests.Transfer request) {
|
||||||
|
List<DeliveryRequests.FeeItem> inputs = request.fees() == null ? List.of() : request.fees();
|
||||||
|
if (inputs.isEmpty()) throw conflict("Delivery fee is required");
|
||||||
|
Set<String> allowed = Set.of("DELIVERY_FEE", "LOADING_FEE", "APPOINTMENT_FEE");
|
||||||
|
Set<String> 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
|
@Transactional
|
||||||
public DeliveryResponses.PdaTask createOneClick(String customerId, String operatorId, DeliveryRequests.OneClickOutbound request) {
|
public DeliveryResponses.PdaTask createOneClick(String customerId, String operatorId, DeliveryRequests.OneClickOutbound request) {
|
||||||
OutboundOrder existing = outboundOrders.findByIdempotency(customerId, request.idempotencyKey().trim());
|
OutboundOrder existing = outboundOrders.findByIdempotency(customerId, request.idempotencyKey().trim());
|
||||||
|
|||||||
+13
-6
@@ -31,6 +31,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
|||||||
import org.springframework.test.util.ReflectionTestUtils;
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
import org.springframework.web.server.ResponseStatusException;
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -88,7 +89,7 @@ class DeliveryTransferServiceTest {
|
|||||||
));
|
));
|
||||||
|
|
||||||
DeliveryResponses.TransferResult result = service.transfer("TENANT-A", "DSP-101", "U-1",
|
DeliveryResponses.TransferResult result = service.transfer("TENANT-A", "DSP-101", "U-1",
|
||||||
new DeliveryRequests.Transfer("transfer-101", "ready"));
|
transferRequest("transfer-101", "ready"));
|
||||||
|
|
||||||
ArgumentCaptor<OutboundOrder> outbound = ArgumentCaptor.forClass(OutboundOrder.class);
|
ArgumentCaptor<OutboundOrder> outbound = ArgumentCaptor.forClass(OutboundOrder.class);
|
||||||
ArgumentCaptor<DeliveryOrder> delivery = ArgumentCaptor.forClass(DeliveryOrder.class);
|
ArgumentCaptor<DeliveryOrder> delivery = ArgumentCaptor.forClass(DeliveryOrder.class);
|
||||||
@@ -125,7 +126,7 @@ class DeliveryTransferServiceTest {
|
|||||||
when(deliveryOrders.lockByOutbound("TENANT-A", 301L)).thenReturn(delivery);
|
when(deliveryOrders.lockByOutbound("TENANT-A", 301L)).thenReturn(delivery);
|
||||||
|
|
||||||
DeliveryResponses.TransferResult result = service.transfer("TENANT-A", "DSP-101", "U-1",
|
DeliveryResponses.TransferResult result = service.transfer("TENANT-A", "DSP-101", "U-1",
|
||||||
new DeliveryRequests.Transfer(" transfer-101 ", null));
|
transferRequest(" transfer-101 ", null));
|
||||||
|
|
||||||
assertTrue(result.replayed());
|
assertTrue(result.replayed());
|
||||||
assertEquals("OUT-301", result.outboundNo());
|
assertEquals("OUT-301", result.outboundNo());
|
||||||
@@ -144,7 +145,7 @@ class DeliveryTransferServiceTest {
|
|||||||
when(outboundOrders.findByIdempotency("TENANT-A", "shared-key")).thenReturn(other);
|
when(outboundOrders.findByIdempotency("TENANT-A", "shared-key")).thenReturn(other);
|
||||||
|
|
||||||
ResponseStatusException exception = assertThrows(ResponseStatusException.class, () ->
|
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());
|
assertEquals(409, exception.getStatusCode().value());
|
||||||
verify(outboundOrders, never()).insert((OutboundOrder) any());
|
verify(outboundOrders, never()).insert((OutboundOrder) any());
|
||||||
@@ -164,7 +165,7 @@ class DeliveryTransferServiceTest {
|
|||||||
when(deliveryOrders.lockByOutbound("TENANT-A", 301L)).thenReturn(delivery);
|
when(deliveryOrders.lockByOutbound("TENANT-A", 301L)).thenReturn(delivery);
|
||||||
|
|
||||||
DeliveryResponses.TransferResult result = service.transfer("TENANT-A", "DSP-101", "U-1",
|
DeliveryResponses.TransferResult result = service.transfer("TENANT-A", "DSP-101", "U-1",
|
||||||
new DeliveryRequests.Transfer("transfer-101", null));
|
transferRequest("transfer-101", null));
|
||||||
|
|
||||||
assertTrue(result.replayed());
|
assertTrue(result.replayed());
|
||||||
assertEquals("OUT-301", result.outboundNo());
|
assertEquals("OUT-301", result.outboundNo());
|
||||||
@@ -185,7 +186,7 @@ class DeliveryTransferServiceTest {
|
|||||||
when(dispatchItems.findByDispatchOrder("TENANT-A", 101L)).thenReturn(List.of(source));
|
when(dispatchItems.findByDispatchOrder("TENANT-A", 101L)).thenReturn(List.of(source));
|
||||||
when(tailOrders.selectList(any())).thenReturn(List.of(tailOrder));
|
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());
|
assertEquals("WAIT_OUTBOUND", tailOrder.getCurrentStatus());
|
||||||
assertNotNull(tailOrder.getOutboundOrderNo());
|
assertNotNull(tailOrder.getOutboundOrderNo());
|
||||||
@@ -204,7 +205,7 @@ class DeliveryTransferServiceTest {
|
|||||||
when(dispatchItems.findByDispatchOrder("TENANT-A", 101L)).thenReturn(List.of(planned));
|
when(dispatchItems.findByDispatchOrder("TENANT-A", 101L)).thenReturn(List.of(planned));
|
||||||
|
|
||||||
ResponseStatusException exception = assertThrows(ResponseStatusException.class, () ->
|
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());
|
assertEquals(409, exception.getStatusCode().value());
|
||||||
assertTrue(exception.getReason().contains("received"));
|
assertTrue(exception.getReason().contains("received"));
|
||||||
@@ -248,6 +249,12 @@ class DeliveryTransferServiceTest {
|
|||||||
return value;
|
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,
|
private static DispatchItem item(Long id, Long warehousePalletId, String palletNo, String destinationCode,
|
||||||
String destinationName, String inboundCode, int packages) {
|
String destinationName, String inboundCode, int packages) {
|
||||||
DispatchItem value = new DispatchItem();
|
DispatchItem value = new DispatchItem();
|
||||||
|
|||||||
Reference in New Issue
Block a user