优化
This commit is contained in:
+9
@@ -27,6 +27,15 @@ public class OutboundPdaController {
|
||||
identity.operatorKey(), taskNo, request));
|
||||
}
|
||||
|
||||
@PostMapping("/{taskNo}/pallets/{palletNo}/pick")
|
||||
public ApiResponse<DeliveryResponses.PdaTask> pickPallet(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable String taskNo, @PathVariable String palletNo,
|
||||
@Valid @RequestBody DeliveryRequests.PdaPalletPick request) {
|
||||
PdaWarehouseIdentity identity = PdaWarehouseIdentity.require(user);
|
||||
return ApiResponse.success(service.pdaPickPallet(String.valueOf(identity.overseasWarehouseId()),
|
||||
identity.operatorKey(), taskNo, palletNo, request));
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ApiResponse<java.util.List<DeliveryResponses.PdaTask>> list(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam(required=false) String status) {
|
||||
|
||||
@@ -89,6 +89,7 @@ public final class DeliveryRequests {
|
||||
this(outboundPalletItemId, inventoryPositionId, packages, idempotencyKey, List.of());
|
||||
}
|
||||
}
|
||||
public record PdaPalletPick(@NotBlank @Size(max=255) String idempotencyKey) {}
|
||||
public record PdaComplete(@NotBlank @Size(max=255) String idempotencyKey) {}
|
||||
public record Cancel(@NotBlank @Size(max=1000) String reason) {}
|
||||
}
|
||||
|
||||
+83
-9
@@ -78,6 +78,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class DeliveryTransferService {
|
||||
@@ -476,7 +477,7 @@ public class DeliveryTransferService {
|
||||
}
|
||||
OutboundPdaPick pick = new OutboundPdaPick(); pick.setId(IdWorker.getId()); pick.setCustomerId(customerId); pick.setTaskId(task.getId()); pick.setOutboundPalletItemId(item.getId()); pick.setInventoryPositionId(null); pick.setPackages(request.packages()); pick.setIdempotencyKey(request.idempotencyKey().trim()); pick.setPickedAt(LocalDateTime.now()); pdaPicks.insert(pick);
|
||||
item.setPickedPackageCount(request.packages().intValue()); item.setStatus("PICKED"); outboundPallets.updateById(item);
|
||||
outbound.setStatus("PICKED"); outbound.setUpdatedAt(LocalDateTime.now()); outboundOrders.updateById(outbound);
|
||||
updateOutboundPickingStatus(customerId, outbound);
|
||||
task.setStatus("IN_PROGRESS"); task.setUpdatedAt(LocalDateTime.now()); pdaTasks.updateById(task);
|
||||
return taskResult(outbound, task);
|
||||
}
|
||||
@@ -529,10 +530,7 @@ public class DeliveryTransferService {
|
||||
pick.setTaskId(task.getId()); pick.setOutboundPalletItemId(item.getId()); pick.setInventoryPositionId(request.inventoryPositionId());
|
||||
pick.setPackages(request.packages()); pick.setIdempotencyKey(request.idempotencyKey().trim()); pick.setPickedAt(LocalDateTime.now()); pdaPicks.insert(pick);
|
||||
item.setPickedPackageCount(request.packages().intValue()); item.setStatus("PICKED"); outboundPallets.updateById(item);
|
||||
List<OutboundPalletItem> pickedItems = outboundPallets.findByOutbound(customerId, outbound.getId());
|
||||
if (!pickedItems.isEmpty() && pickedItems.stream().allMatch(value -> "PICKED".equals(value.getStatus()))) {
|
||||
outbound.setStatus("PICKED"); outbound.setUpdatedAt(LocalDateTime.now()); outboundOrders.updateById(outbound);
|
||||
}
|
||||
updateOutboundPickingStatus(customerId, outbound);
|
||||
task.setStatus("IN_PROGRESS"); task.setUpdatedAt(LocalDateTime.now()); pdaTasks.updateById(task);
|
||||
return taskResult(outbound, task);
|
||||
}
|
||||
@@ -578,14 +576,69 @@ public class DeliveryTransferService {
|
||||
for (DropshipCarton carton : cartons) { carton.setStatus("SHIPPED"); carton.setOutboundOrderNo(outbound.getOutboundNo()); carton.setVersion((carton.getVersion() == null ? 0 : carton.getVersion()) + 1); carton.setUpdatedAt(LocalDateTime.now()); dropshipCartons.updateById(carton); }
|
||||
OutboundPdaPick pick = new OutboundPdaPick(); pick.setId(IdWorker.getId()); pick.setCustomerId(customerId); pick.setTaskId(task.getId()); pick.setOutboundPalletItemId(item.getId()); pick.setInventoryPositionId(request.inventoryPositionId()); pick.setPackages(request.packages()); pick.setIdempotencyKey(request.idempotencyKey().trim()); pick.setPickedAt(LocalDateTime.now()); pdaPicks.insert(pick);
|
||||
item.setPickedPackageCount(value(item.getPickedPackageCount()) + request.packages().intValue()); item.setStatus(item.getPickedPackageCount() >= item.getPackageCount() ? "PICKED" : "PARTIAL_PICKED"); outboundPallets.updateById(item);
|
||||
List<OutboundPalletItem> pickedItems = outboundPallets.findByOutbound(customerId, outbound.getId());
|
||||
if (!pickedItems.isEmpty() && pickedItems.stream().allMatch(i -> "PICKED".equals(i.getStatus()))) {
|
||||
outbound.setStatus("PICKED"); outbound.setUpdatedAt(LocalDateTime.now()); outboundOrders.updateById(outbound);
|
||||
}
|
||||
updateOutboundPickingStatus(customerId, outbound);
|
||||
task.setStatus("IN_PROGRESS"); task.setUpdatedAt(LocalDateTime.now()); pdaTasks.updateById(task);
|
||||
return taskResult(outbound, task);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public DeliveryResponses.PdaTask pdaPickPallet(String warehouseId, String operatorId, String taskNo,
|
||||
String palletNo, DeliveryRequests.PdaPalletPick request) {
|
||||
String boundWarehouseId = required(warehouseId, "PDA warehouse binding is required");
|
||||
String code = required(taskNo, "PDA task number is required");
|
||||
String palletCode = required(palletNo, "Pallet number is required");
|
||||
OutboundPdaTask task = pdaTasks.lockByNoAndWarehouse(code, boundWarehouseId);
|
||||
if (task == null) throw notFound("PDA task not found in the bound warehouse");
|
||||
OutboundOrder outbound = outboundOrders.lockByNo(task.getCustomerId(), requireOutbound(task).getOutboundNo());
|
||||
if (outbound == null || !boundWarehouseId.equals(outbound.getOverseasWarehouseId())) {
|
||||
throw notFound("Outbound order not found in the bound warehouse");
|
||||
}
|
||||
if (!"ONE_CLICK_DROPSHIP".equals(outbound.getOutboundType())) {
|
||||
throw conflict("整托扫码仅适用于一件代发出库");
|
||||
}
|
||||
if (!Set.of("PENDING", "IN_PROGRESS").contains(task.getStatus())) {
|
||||
throw conflict("PDA task is not available for picking");
|
||||
}
|
||||
List<OutboundPalletItem> palletItems = outboundPallets.findByOutbound(task.getCustomerId(), outbound.getId())
|
||||
.stream().filter(item -> palletCode.equals(item.getPalletNo())).toList();
|
||||
if (palletItems.isEmpty()) throw notFound("当前出库任务没有该托盘");
|
||||
if (palletItems.stream().allMatch(item -> value(item.getPickedPackageCount()) >= value(item.getPackageCount()))) {
|
||||
return taskResult(outbound, task);
|
||||
}
|
||||
|
||||
List<PalletPickPlan> plans = new ArrayList<>();
|
||||
int sequence = 0;
|
||||
for (OutboundPalletItem item : palletItems) {
|
||||
long remaining = value(item.getPackageCount()) - value(item.getPickedPackageCount());
|
||||
if (remaining <= 0) continue;
|
||||
Map<Long, List<DeliveryResponses.PdaPickPosition>> positionsById = pdaPickPositions(
|
||||
task.getCustomerId(), boundWarehouseId, item).stream().collect(Collectors.groupingBy(
|
||||
DeliveryResponses.PdaPickPosition::inventoryPositionId, LinkedHashMap::new, Collectors.toList()));
|
||||
for (Map.Entry<Long, List<DeliveryResponses.PdaPickPosition>> entry : positionsById.entrySet()) {
|
||||
if (remaining <= 0) break;
|
||||
List<String> marks = entry.getValue().stream().map(DeliveryResponses.PdaPickPosition::cartonMark)
|
||||
.filter(StringUtils::hasText).distinct().toList();
|
||||
long available = marks.isEmpty()
|
||||
? entry.getValue().stream().mapToLong(DeliveryResponses.PdaPickPosition::availablePackages).sum()
|
||||
: marks.size();
|
||||
long packages = Math.min(remaining, available);
|
||||
if (packages <= 0) continue;
|
||||
List<String> selectedMarks = marks.isEmpty() ? List.of() : marks.stream().limit(packages).toList();
|
||||
plans.add(new PalletPickPlan(item.getId(), entry.getKey(), packages, selectedMarks, sequence++));
|
||||
remaining -= packages;
|
||||
}
|
||||
if (remaining > 0) throw conflict("托盘可用库存不足,整托拣货未执行:" + palletCode);
|
||||
}
|
||||
|
||||
DeliveryResponses.PdaTask result = taskResult(outbound, task);
|
||||
for (PalletPickPlan plan : plans) {
|
||||
String operationId = palletPickOperationId(request.idempotencyKey(), code, palletCode, plan);
|
||||
result = pdaPick(boundWarehouseId, operatorId, code, new DeliveryRequests.PdaPick(
|
||||
plan.itemId(), plan.positionId(), plan.packages(), operationId, plan.cartonMarks()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public DeliveryResponses.PdaTask completePda(String warehouseId, String taskNo,
|
||||
DeliveryRequests.PdaComplete request) {
|
||||
@@ -909,6 +962,27 @@ public class DeliveryTransferService {
|
||||
.stream().map(LastlegOrder::getLxCode).filter(StringUtils::hasText)
|
||||
.map(String::trim).distinct().collect(java.util.stream.Collectors.joining(","));
|
||||
}
|
||||
private void updateOutboundPickingStatus(String customerId, OutboundOrder outbound) {
|
||||
List<OutboundPalletItem> items = outboundPallets.findByOutbound(customerId, outbound.getId());
|
||||
String nextStatus = !items.isEmpty() && items.stream().allMatch(item -> "PICKED".equals(item.getStatus()))
|
||||
? "PICKED" : "PICKING";
|
||||
outbound.setStatus(nextStatus);
|
||||
outbound.setUpdatedAt(LocalDateTime.now());
|
||||
outboundOrders.updateById(outbound);
|
||||
}
|
||||
private static String palletPickOperationId(String idempotencyKey, String taskNo, String palletNo,
|
||||
PalletPickPlan plan) {
|
||||
String source = required(idempotencyKey, "Idempotency key is required") + '\u0000' + taskNo + '\u0000'
|
||||
+ palletNo + '\u0000' + plan.itemId() + '\u0000' + plan.positionId() + '\u0000' + plan.sequence();
|
||||
try {
|
||||
return "PALLET_PICK:" + HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256")
|
||||
.digest(source.getBytes(StandardCharsets.UTF_8)));
|
||||
} catch (NoSuchAlgorithmException error) {
|
||||
throw new IllegalStateException("无法生成整托拣货幂等号", error);
|
||||
}
|
||||
}
|
||||
private record PalletPickPlan(Long itemId, Long positionId, long packages,
|
||||
List<String> cartonMarks, int sequence) {}
|
||||
private BigDecimal totalWeight(String customerId, List<DispatchItem> items) {
|
||||
List<Long> palletIds = items.stream().map(DispatchItem::getWarehousePalletId)
|
||||
.filter(Objects::nonNull).distinct().toList();
|
||||
|
||||
+26
-13
@@ -13,6 +13,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.client.RestClientException;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
@Component
|
||||
@@ -32,24 +33,36 @@ public class HttpDomesticLoadingEvidenceGateway implements DomesticLoadingEviden
|
||||
|
||||
@Override
|
||||
public DownloadedFile download(long containerBookingId, long fileId) {
|
||||
ResponseEntity<byte[]> response = client.get()
|
||||
.uri("/internal/file/v1/loading-evidence/{containerBookingId}/{fileId}/content",
|
||||
containerBookingId, fileId)
|
||||
.header("X-Internal-Service-Token", internalToken).retrieve()
|
||||
.onStatus(HttpStatusCode::is4xxClientError, (request, result) -> {
|
||||
throw new ResponseStatusException(result.getStatusCode().value() == 404
|
||||
? HttpStatus.NOT_FOUND : HttpStatus.BAD_REQUEST, "国内装柜照片不可用");
|
||||
})
|
||||
.onStatus(HttpStatusCode::is5xxServerError, (request, result) -> {
|
||||
throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE, "国内装柜照片暂时无法加载");
|
||||
}).toEntity(byte[].class);
|
||||
ResponseEntity<byte[]> response;
|
||||
try {
|
||||
response = client.get()
|
||||
.uri("/internal/file/v1/loading-evidence/{containerBookingId}/{fileId}/content",
|
||||
containerBookingId, fileId)
|
||||
.header("X-Internal-Service-Token", internalToken).retrieve()
|
||||
.onStatus(HttpStatusCode::is4xxClientError, (request, result) -> {
|
||||
throw new ResponseStatusException(result.getStatusCode().value() == 404
|
||||
? HttpStatus.NOT_FOUND : HttpStatus.BAD_REQUEST, "国内装柜照片不可用");
|
||||
})
|
||||
.onStatus(HttpStatusCode::is5xxServerError, (request, result) -> {
|
||||
throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE, "国内装柜照片暂时无法加载");
|
||||
}).toEntity(byte[].class);
|
||||
} catch (ResponseStatusException exception) {
|
||||
throw exception;
|
||||
} catch (RestClientException exception) {
|
||||
throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE,
|
||||
"国内装柜照片暂时无法加载", exception);
|
||||
}
|
||||
byte[] content = response.getBody() == null ? new byte[0] : response.getBody();
|
||||
MediaType mediaType = response.getHeaders().getContentType();
|
||||
if (content.length == 0 || mediaType == null || !"image".equalsIgnoreCase(mediaType.getType())) {
|
||||
throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE,
|
||||
"国内装柜照片返回内容无效");
|
||||
}
|
||||
String disposition = response.getHeaders().getFirst(HttpHeaders.CONTENT_DISPOSITION);
|
||||
String fileName = null;
|
||||
try { fileName = disposition == null ? null : ContentDisposition.parse(disposition).getFilename(); }
|
||||
catch (IllegalArgumentException ignored) { }
|
||||
String contentType = response.getHeaders().getContentType() == null
|
||||
? MediaType.APPLICATION_OCTET_STREAM_VALUE : response.getHeaders().getContentType().toString();
|
||||
String contentType = mediaType.toString();
|
||||
return new DownloadedFile(content, fileName == null ? fileId + ".bin" : fileName, contentType);
|
||||
}
|
||||
}
|
||||
|
||||
+12
-5
@@ -134,8 +134,10 @@ public class InboundPlanService {
|
||||
requiredReceivingWarehouse(containerWarehouseId);
|
||||
validateOrders(snapshots);
|
||||
String orderType = trimToNull(orderMapper.findOrderType(customerId, request.orderId()));
|
||||
List<DropshipPalletSnapshot> dropshipPalletSnapshots = "ONE_CLICK_DROPSHIP".equals(orderType)
|
||||
? dropshipOrders.palletSnapshots(customerId, request.orderId()) : List.of();
|
||||
int sourcePalletCount = "ONE_CLICK_DROPSHIP".equals(orderType)
|
||||
? dropshipOrders.palletSnapshots(customerId, request.orderId()).size()
|
||||
? dropshipPalletSnapshots.size()
|
||||
: Objects.requireNonNullElse(
|
||||
sourcePalletMapper.palletNos(customerId, request.orderId()), List.<String>of()).size();
|
||||
if ("ONE_CLICK_DROPSHIP".equals(orderType)) {
|
||||
@@ -154,7 +156,9 @@ public class InboundPlanService {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST,
|
||||
"人工计划托数不能少于柜级预览建议托数 " + minimumPalletCount);
|
||||
}
|
||||
InboundPlanResponse response = createPlan(customerId, request.orderId(), snapshots, List.of(), null,
|
||||
List<WholePalletInput> wholePallets = "ONE_CLICK_DROPSHIP".equals(orderType)
|
||||
? dropshipPalletSnapshots.stream().map(this::wholePallet).toList() : null;
|
||||
InboundPlanResponse response = createPlan(customerId, request.orderId(), snapshots, List.of(), wholePallets,
|
||||
request, receivingWarehouse, plannedArrivalDate);
|
||||
LastlegOrderCommandRecord command = new LastlegOrderCommandRecord();
|
||||
command.setId(IdWorker.getId()); command.setCustomerId(customerId); command.setOrderId(request.orderId());
|
||||
@@ -500,7 +504,7 @@ public class InboundPlanService {
|
||||
|
||||
lineByOrder.values().forEach(lineMapper::insert);
|
||||
Map<Long, Integer> palletCounts;
|
||||
if (manual != null) {
|
||||
if (manual != null && wholePallets == null) {
|
||||
createManualPallets(plan, orders.get(0), lineByOrder.get(orders.get(0).orderId()), manual);
|
||||
palletCounts = Map.of(orders.get(0).orderId(), manual.palletCount());
|
||||
} else if (wholePallets == null) {
|
||||
@@ -512,7 +516,8 @@ public class InboundPlanService {
|
||||
item -> item.pallet().sequenceNo(), Collectors.toSet()), java.util.Set::size)));
|
||||
} else {
|
||||
SourceOrderSnapshot order = orders.get(0);
|
||||
createWholePallets(plan, order, lineByOrder.get(order.orderId()), wholePallets, reservedPallets);
|
||||
createWholePallets(plan, order, lineByOrder.get(order.orderId()), wholePallets, reservedPallets,
|
||||
manual == null ? null : manual.palletType());
|
||||
palletCounts = Map.of(order.orderId(), wholePallets.size());
|
||||
}
|
||||
for (SourceOrderSnapshot order : orders) {
|
||||
@@ -804,7 +809,8 @@ public class InboundPlanService {
|
||||
|
||||
private void createWholePallets(InboundPlan plan, SourceOrderSnapshot order, InboundPlanLine line,
|
||||
List<WholePalletInput> sourcePallets,
|
||||
Map<Integer, WarehouseResourceGateway.PalletBatchItem> reservedPallets) {
|
||||
Map<Integer, WarehouseResourceGateway.PalletBatchItem> reservedPallets,
|
||||
String palletType) {
|
||||
BigDecimal remainingVolume = volume(order);
|
||||
int remainingPackages = order.packageCount();
|
||||
for (int index = 0; index < sourcePallets.size(); index++) {
|
||||
@@ -816,6 +822,7 @@ public class InboundPlanService {
|
||||
InboundPlanPallet pallet = new InboundPlanPallet();
|
||||
pallet.setId(IdWorker.getId()); pallet.setCustomerId(plan.getCustomerId()); pallet.setPlanId(plan.getId());
|
||||
pallet.setPalletNo(source.palletNo().trim()); pallet.setSourcePalletNo(source.palletNo().trim());
|
||||
pallet.setPalletType(trimToNull(palletType) == null ? null : palletType.trim().toUpperCase());
|
||||
pallet.setDestinationWarehouseId(order.destinationWarehouseId());
|
||||
pallet.setDestinationWarehouseCode(order.destinationWarehouseCode());
|
||||
pallet.setDestinationWarehouseName(order.destinationWarehouseName());
|
||||
|
||||
@@ -78,7 +78,7 @@ baiyue:
|
||||
file:
|
||||
url: ${FILE_SERVICE_URL:http://127.0.0.1:9205}
|
||||
domestic-file:
|
||||
url: ${DOMESTIC_FILE_SERVICE_URL:http://127.0.0.1:9205}
|
||||
url: ${FILE_SERVICE_URL:http://127.0.0.1:9205}
|
||||
event-bridge:
|
||||
url: ${CN_EVENT_BRIDGE_URL:${FIRSTLEG_EVENT_BRIDGE_URL:}}
|
||||
firstleg:
|
||||
|
||||
@@ -69,6 +69,15 @@ paths:
|
||||
parameters: [{ name: taskNo, in: path, required: true, schema: { type: string } }]
|
||||
requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/OutboundPick' } } } }
|
||||
responses: { '200': { description: 更新后的统一出库任务 }, '409': { description: 库存、数量或幂等冲突 } }
|
||||
/api/pda/v1/last-leg/outbound-tasks/{taskNo}/pallets/{palletNo}/pick:
|
||||
post:
|
||||
summary: 扫描一件代发托盘并原子拣取整托剩余箱件
|
||||
description: 仅适用于一件代发出库;按托盘汇总本任务内全部 SKU 和未拣箱唛,任一库存校验失败则整托回滚。
|
||||
parameters:
|
||||
- { name: taskNo, in: path, required: true, schema: { type: string } }
|
||||
- { name: palletNo, in: path, required: true, schema: { type: string } }
|
||||
requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/IdempotentAction' } } } }
|
||||
responses: { '200': { description: 整托拣货后的任务 }, '404': { description: 当前任务没有该托盘 }, '409': { description: 非一件代发、库存不足或幂等冲突 } }
|
||||
/api/pda/v1/last-leg/outbound-tasks/{taskNo}/complete:
|
||||
post:
|
||||
summary: 完成一件代发出库任务
|
||||
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package com.baiyue.lastleg.inboundplan.pda.gateway;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.test.web.client.MockRestServiceServer;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.springframework.test.web.client.match.MockRestRequestMatchers.header;
|
||||
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
|
||||
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
|
||||
|
||||
class HttpDomesticLoadingEvidenceGatewayTest {
|
||||
private HttpDomesticLoadingEvidenceGateway gateway;
|
||||
private MockRestServiceServer server;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
RestClient.Builder builder = RestClient.builder();
|
||||
server = MockRestServiceServer.bindTo(builder).build();
|
||||
gateway = new HttpDomesticLoadingEvidenceGateway();
|
||||
ReflectionTestUtils.setField(gateway, "client", builder.baseUrl("http://domestic-file").build());
|
||||
ReflectionTestUtils.setField(gateway, "internalToken", "internal-token");
|
||||
}
|
||||
|
||||
@Test
|
||||
void downloadsImageFromDomesticFileService() {
|
||||
server.expect(requestTo("http://domestic-file/internal/file/v1/loading-evidence/8/9/content"))
|
||||
.andExpect(header("X-Internal-Service-Token", "internal-token"))
|
||||
.andRespond(withSuccess("photo-bytes".getBytes(), MediaType.IMAGE_JPEG)
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=seal.jpg"));
|
||||
|
||||
DomesticLoadingEvidenceGateway.DownloadedFile result = gateway.download(8L, 9L);
|
||||
|
||||
assertThat(result.content()).containsExactly("photo-bytes".getBytes());
|
||||
assertThat(result.fileName()).isEqualTo("seal.jpg");
|
||||
assertThat(result.contentType()).isEqualTo(MediaType.IMAGE_JPEG_VALUE);
|
||||
server.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsBusinessErrorEnvelopeReturnedWithHttp200() {
|
||||
server.expect(requestTo("http://domestic-file/internal/file/v1/loading-evidence/8/9/content"))
|
||||
.andRespond(withSuccess("{\"code\":\"S0500\",\"message\":\"Internal server error\"}",
|
||||
MediaType.APPLICATION_JSON));
|
||||
|
||||
assertThatThrownBy(() -> gateway.download(8L, 9L))
|
||||
.isInstanceOfSatisfying(ResponseStatusException.class, error -> {
|
||||
assertThat(error.getStatusCode()).isEqualTo(HttpStatus.SERVICE_UNAVAILABLE);
|
||||
assertThat(error.getReason()).isEqualTo("国内装柜照片返回内容无效");
|
||||
});
|
||||
server.verify();
|
||||
}
|
||||
}
|
||||
+36
-2
@@ -215,7 +215,7 @@ class InboundPlanServiceTest {
|
||||
when(orderMapper.lockOrder("100", 2L)).thenReturn(List.of(source));
|
||||
when(orderMapper.findOrderType("100", 2L)).thenReturn("ONE_CLICK_DROPSHIP");
|
||||
when(dropshipOrders.palletSnapshots("100", 2L)).thenReturn(List.of(
|
||||
new DropshipPalletSnapshot("DP-260914-0001", 10L, List.of())));
|
||||
new DropshipPalletSnapshot("DP-260914-0001", 33L, List.of())));
|
||||
|
||||
ResponseStatusException error = assertThrows(ResponseStatusException.class, () -> service.convertManual(
|
||||
new ManualInboundPlanRequest("100", 2L, 0,
|
||||
@@ -233,7 +233,7 @@ class InboundPlanServiceTest {
|
||||
when(orderMapper.lockOrder("100", 2L)).thenReturn(List.of(source));
|
||||
when(orderMapper.findOrderType("100", 2L)).thenReturn("ONE_CLICK_DROPSHIP");
|
||||
when(dropshipOrders.palletSnapshots("100", 2L)).thenReturn(List.of(
|
||||
new DropshipPalletSnapshot("DP-260914-0001", 10L, List.of())));
|
||||
new DropshipPalletSnapshot("DP-260914-0001", 33L, List.of())));
|
||||
when(orderMapper.markPlannedManual(eq("100"), eq(2L), any(), eq(1), eq(false), eq(0), any()))
|
||||
.thenReturn(1);
|
||||
|
||||
@@ -246,6 +246,40 @@ class InboundPlanServiceTest {
|
||||
verify(palletMapper).insert(any(InboundPlanPallet.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void oneClickDropshipManualPlanPreservesUnevenFrozenPalletPackageCounts() {
|
||||
SourceOrderSnapshot source = new SourceOrderSnapshot(2L, "100", "MSCU6639870", "RK-2",
|
||||
"PENDING_ASSIGNMENT", null, new BigDecimal("3.3651"), 20, 3,
|
||||
"193000000000200003", "US-LAX-01", "洛杉矶仓", null,
|
||||
"193000000000200003", null, "洛杉矶仓", 1, 0, false, 0);
|
||||
when(orderMapper.lockOrder("100", 2L)).thenReturn(List.of(source));
|
||||
when(orderMapper.findOrderType("100", 2L)).thenReturn("ONE_CLICK_DROPSHIP");
|
||||
when(dropshipOrders.palletSnapshots("100", 2L)).thenReturn(List.of(
|
||||
new DropshipPalletSnapshot("DP-260915-0092", 10L, List.of()),
|
||||
new DropshipPalletSnapshot("DP-260915-0093", 3L, List.of()),
|
||||
new DropshipPalletSnapshot("DP-260915-0094", 7L, List.of())));
|
||||
when(orderMapper.markPlannedManual(eq("100"), eq(2L), any(), eq(3), eq(false), eq(0), any()))
|
||||
.thenReturn(1);
|
||||
|
||||
var result = service.convertManual(new ManualInboundPlanRequest("100", 2L, 0,
|
||||
3, "WOOD", false, false, false, null), "dropship-uneven-pallet-counts", "900");
|
||||
|
||||
ArgumentCaptor<InboundPlanPallet> pallets = ArgumentCaptor.forClass(InboundPlanPallet.class);
|
||||
ArgumentCaptor<InboundPlanPalletAllocation> allocations =
|
||||
ArgumentCaptor.forClass(InboundPlanPalletAllocation.class);
|
||||
verify(palletMapper, times(3)).insert(pallets.capture());
|
||||
verify(allocationMapper, times(3)).insert(allocations.capture());
|
||||
assertEquals(List.of("DP-260915-0092", "DP-260915-0093", "DP-260915-0094"),
|
||||
pallets.getAllValues().stream().map(InboundPlanPallet::getSourcePalletNo).toList());
|
||||
assertEquals(List.of("WOOD", "WOOD", "WOOD"),
|
||||
pallets.getAllValues().stream().map(InboundPlanPallet::getPalletType).toList());
|
||||
assertEquals(List.of(10, 3, 7), allocations.getAllValues().stream()
|
||||
.map(InboundPlanPalletAllocation::getPlannedPackageCount).toList());
|
||||
assertEquals(new BigDecimal("3.3651"), allocations.getAllValues().stream()
|
||||
.map(InboundPlanPalletAllocation::getAllocatedVolumeCbm).reduce(BigDecimal.ZERO, BigDecimal::add));
|
||||
assertEquals(3, result.palletCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
void containerConversionBindsRealWarehousePalletsBackToSignedManualPlanBeforeReceiving() {
|
||||
InboundPlan plan = existingPlan("100", 2L, "IP-MANUAL");
|
||||
|
||||
+3
-1
@@ -38,6 +38,7 @@ class PdaOperationsOpenApiContractTest {
|
||||
assertNoServerOwnedFields(DispatchPdaRequests.CompletePicking.class);
|
||||
assertNoServerOwnedFields(DispatchPdaRequests.Depart.class);
|
||||
assertNoServerOwnedFields(DeliveryRequests.PdaPick.class);
|
||||
assertNoServerOwnedFields(DeliveryRequests.PdaPalletPick.class);
|
||||
assertNoServerOwnedFields(DeliveryRequests.PdaComplete.class);
|
||||
}
|
||||
|
||||
@@ -77,9 +78,10 @@ class PdaOperationsOpenApiContractTest {
|
||||
"/api/pda/v1/last-leg/outbound-tasks/resolve/{businessNo}:",
|
||||
"/api/pda/v1/last-leg/outbound-tasks/{taskNo}:",
|
||||
"/api/pda/v1/last-leg/outbound-tasks/{taskNo}/pick:",
|
||||
"/api/pda/v1/last-leg/outbound-tasks/{taskNo}/pallets/{palletNo}/pick:",
|
||||
"/api/pda/v1/last-leg/outbound-tasks/{taskNo}/complete:");
|
||||
for (String path : paths) assertTrue(yaml.contains(path), path);
|
||||
assertEquals(33, paths.size());
|
||||
assertEquals(34, paths.size());
|
||||
assertTrue(yaml.contains("enum: [LOCATION, PALLET]"));
|
||||
assertTrue(yaml.contains("enum: [APPROVE, REWORK]"));
|
||||
assertTrue(yaml.contains("Warehouse 权威实物托盘码"));
|
||||
|
||||
+3
@@ -49,6 +49,8 @@ class PdaOutboundDispatchContractTest {
|
||||
.noneMatch(component -> "warehouseId".equals(component.getName()) || "customerId".equals(component.getName())));
|
||||
assertTrue(Arrays.stream(DeliveryRequests.PdaPick.class.getRecordComponents())
|
||||
.noneMatch(component -> "warehouseId".equals(component.getName()) || "customerId".equals(component.getName())));
|
||||
assertTrue(Arrays.stream(DeliveryRequests.PdaPalletPick.class.getRecordComponents())
|
||||
.noneMatch(component -> "warehouseId".equals(component.getName()) || "customerId".equals(component.getName())));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -86,6 +88,7 @@ class PdaOutboundDispatchContractTest {
|
||||
assertTrue(yaml.contains("/api/pda/v1/last-leg/dispatches:"));
|
||||
assertTrue(yaml.contains("/api/pda/v1/last-leg/dispatches/{dispatchNo}/depart:"));
|
||||
assertTrue(yaml.contains("/api/pda/v1/last-leg/outbound-tasks/{taskNo}/complete:"));
|
||||
assertTrue(yaml.contains("/api/pda/v1/last-leg/outbound-tasks/{taskNo}/pallets/{palletNo}/pick:"));
|
||||
assertFalse(yaml.contains("name: customerId"));
|
||||
assertFalse(yaml.contains("name: warehouseId"));
|
||||
assertFalse(yaml.contains("/api/pda/v1/last-leg/delivery"));
|
||||
|
||||
Reference in New Issue
Block a user