提交
This commit is contained in:
+2
-1
@@ -1426,6 +1426,7 @@ public class DeliveryTransferService {
|
||||
new LambdaQueryWrapper<OutboundOrder>().eq(StringUtils.hasText(customerId), OutboundOrder::getCustomerId, customerId)
|
||||
.like(StringUtils.hasText(query.documentNo()), OutboundOrder::getOutboundNo, query.documentNo())
|
||||
.like(StringUtils.hasText(query.lxCode()), OutboundOrder::getLxCodes, query.lxCode())
|
||||
.ne(StringUtils.hasText(query.lxCode()), OutboundOrder::getOutboundType, "DISCARD_OUTBOUND")
|
||||
.eq(StringUtils.hasText(query.warehouseId()), OutboundOrder::getOverseasWarehouseId, query.warehouseId())
|
||||
.eq(StringUtils.hasText(query.status()), OutboundOrder::getStatus, query.status())
|
||||
.eq(StringUtils.hasText(query.outboundType()), OutboundOrder::getOutboundType, query.outboundType())
|
||||
@@ -1611,7 +1612,7 @@ public class DeliveryTransferService {
|
||||
d.getDeliveryNo(), d.getStatus(), d.getDestinationWarehouseId(), d.getDestinationWarehouseCode(),
|
||||
d.getDestinationWarehouseName(), value(d.getPalletCount()), value(d.getPackageCount()))).toList(), replayed);
|
||||
}
|
||||
private DeliveryResponses.OutboundRow outboundRow(OutboundOrder o) { return new DeliveryResponses.OutboundRow(o.getId(), o.getOutboundNo(), o.getDispatchNo(), o.getOverseasWarehouseId(), o.getLxCodes(), o.getStatus(), o.getPalletCount(), o.getPackageCount(), o.getGrossWeightKg(), o.getVolumeWeightKg(), o.getVolumeCbm(), o.getCreatedAt(), o.getOutboundType()); }
|
||||
private DeliveryResponses.OutboundRow outboundRow(OutboundOrder o) { return new DeliveryResponses.OutboundRow(o.getId(), o.getOutboundNo(), o.getDispatchNo(), o.getOverseasWarehouseId(), "DISCARD_OUTBOUND".equals(o.getOutboundType()) ? null : o.getLxCodes(), o.getStatus(), o.getPalletCount(), o.getPackageCount(), o.getGrossWeightKg(), o.getVolumeWeightKg(), o.getVolumeCbm(), o.getCreatedAt(), o.getOutboundType()); }
|
||||
private DeliveryResponses.OutboundPalletRow outboundPalletRow(OutboundPalletItem item) {
|
||||
return new DeliveryResponses.OutboundPalletRow(item.getId(), item.getPalletNo(), item.getCartonMarks(),
|
||||
item.getInboundCode(), item.getDestinationWarehouseCode(), item.getDestinationWarehouseName(),
|
||||
|
||||
-7
@@ -77,13 +77,6 @@ public class CargoDiscardController {
|
||||
return ApiResponse.success(service.cancel(scope(user, customerId), discardNo, operator(user), true));
|
||||
}
|
||||
|
||||
@PostMapping("/{discardNo}/complete")
|
||||
public ApiResponse<CargoDiscardResponses.Detail> complete(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam(required = false) String customerId, @PathVariable String discardNo) {
|
||||
requireApprovalPermission(user);
|
||||
return ApiResponse.success(service.complete(scope(user, customerId), discardNo, operator(user)));
|
||||
}
|
||||
|
||||
private String scope(AuthenticatedUser user, String requested) {
|
||||
if (user == null || user.tenantId() == null) throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
|
||||
if ("PLATFORM".equals(user.appCode()) && user.tenantId() == 0L) {
|
||||
|
||||
+6
@@ -46,6 +46,12 @@ public class MerchantCargoDiscardController {
|
||||
org.springframework.http.ContentDisposition.inline().filename(file.fileName(), java.nio.charset.StandardCharsets.UTF_8).build().toString())
|
||||
.contentType(org.springframework.http.MediaType.parseMediaType(file.contentType())).body(file.content());
|
||||
}
|
||||
@PostMapping("/{discardNo}/confirm-completion")
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_DISCARD_CONFIRM + "')")
|
||||
public ApiResponse<CargoDiscardResponses.Detail> confirmCompletion(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable String discardNo) {
|
||||
return ApiResponse.success(service.confirmCompletion(customer(user), discardNo, operator(user)));
|
||||
}
|
||||
@PostMapping("/{discardNo}/cancel")
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.MERCHANT_DISCARD_CANCEL + "')")
|
||||
public ApiResponse<CargoDiscardResponses.Detail> cancel(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ public final class CargoDiscardResponses {
|
||||
public record Summary(Long id, String customerId, String discardNo, String inboundCodes,
|
||||
String overseasWarehouseId, String overseasWarehouseName,
|
||||
int totalPackages, BigDecimal totalFee, String currency, String status,
|
||||
String createdBy, LocalDateTime createdAt, LocalDateTime reviewedAt, String outboundNo) {}
|
||||
String createdBy, LocalDateTime createdAt, LocalDateTime reviewedAt, String outboundNo, LocalDateTime customerConfirmedAt) {}
|
||||
|
||||
public record Item(Long id, Long orderId, String inboundCode, String skuCode,
|
||||
String productName, int availablePackagesSnapshot, int discardPackages, List<String> cartonMarks) {
|
||||
|
||||
+1
@@ -28,6 +28,7 @@ public class CargoDiscardApplication {
|
||||
private String createdBy;
|
||||
private String reviewedBy;
|
||||
private String reviewRemark;
|
||||
private LocalDateTime customerConfirmedAt;
|
||||
private LocalDateTime reviewedAt;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
+1
-1
@@ -122,7 +122,7 @@ public class PdaCargoDiscardService {
|
||||
application.setTotalPackages(actualTotal);
|
||||
application.setTotalFee(application.getFeePerPackage().multiply(java.math.BigDecimal.valueOf(actualTotal)));
|
||||
discardService.updateLinkedStatus(application.getCustomerId(), application, CargoDiscardService.APPROVED,
|
||||
CargoDiscardService.PDA_COMPLETED, identity.operatorKey(), "全部托盘 PDA 废弃作业完成,待仓库确认");
|
||||
CargoDiscardService.PDA_COMPLETED, identity.operatorKey(), "全部托盘 PDA 废弃作业完成,待客户确认");
|
||||
}
|
||||
outboundService.sync(application);
|
||||
return palletTasks(application).stream().filter(value -> value.palletNo().equals(palletNo.trim()))
|
||||
|
||||
+2
-1
@@ -50,7 +50,7 @@ public class CargoDiscardOutboundService {
|
||||
outbound.setId(IdWorker.getId()); outbound.setCustomerId(application.getCustomerId());
|
||||
outbound.setOutboundNo(orderNoGenerator.next("OUTBOUND", application.getOverseasWarehouseId(), outbound.getId()));
|
||||
outbound.setOverseasWarehouseId(application.getOverseasWarehouseId()); outbound.setOutboundType(TYPE);
|
||||
outbound.setLxCodes(application.getInboundCodes()); outbound.setRecipientInfo("废弃单 " + application.getDiscardNo());
|
||||
outbound.setLxCodes(null); outbound.setRecipientInfo("废弃单 " + application.getDiscardNo());
|
||||
outbound.setStatus("WAIT_PICK"); outbound.setPackageCount(application.getTotalPackages());
|
||||
outbound.setIdempotencyKey(key(application)); outbound.setRequestHash(key(application));
|
||||
outbound.setVolumeWeightKg(BigDecimal.ZERO); outbound.setVolumeCbm(BigDecimal.ZERO);
|
||||
@@ -116,6 +116,7 @@ public class CargoDiscardOutboundService {
|
||||
default -> lines.stream().anyMatch(i -> "COMPLETED".equals(i.getPdaStatus())) ? "PICKING" : "WAIT_PICK";
|
||||
};
|
||||
outbound.setStatus(status);
|
||||
outbound.setLxCodes(null);
|
||||
if ("OUTBOUNDED".equals(status)) outbound.setPackageCount(application.getTotalPackages());
|
||||
outbound.setUpdatedAt(LocalDateTime.now()); outbounds.updateById(outbound);
|
||||
}
|
||||
|
||||
+35
-6
@@ -76,6 +76,8 @@ public class CargoDiscardService {
|
||||
@Resource private ReturnInventoryCartonMapper returnCartons;
|
||||
@Resource private ReturnInventoryCartonItemMapper returnCartonItems;
|
||||
@Resource private ObjectMapper objectMapper;
|
||||
@Resource private com.baiyue.lastleg.client.DomesticWarehouseDirectoryClient warehouseDirectory;
|
||||
@Value("${baiyue.security.internal-service-token:}") private String internalToken;
|
||||
@Value("${baiyue.lastleg.cargo-discard.fee-per-package-usd:5.00}")
|
||||
private BigDecimal feePerPackage = new BigDecimal("5.00");
|
||||
|
||||
@@ -105,7 +107,14 @@ public class CargoDiscardService {
|
||||
carton.getSourceReturnNo(), carton.getSourceReturnNo(), carton.getOverseasWarehouseId(), null,
|
||||
null, available, skus));
|
||||
}
|
||||
return result.stream().map(candidate -> withBoxes(customerId, candidate)).toList();
|
||||
Map<String, String> names = result.stream().anyMatch(c -> !StringUtils.hasText(c.overseasWarehouseName()))
|
||||
? warehouseNames() : Map.of();
|
||||
return result.stream().map(candidate -> withBoxes(customerId, new CargoDiscardResponses.Candidate(
|
||||
candidate.orderId(), candidate.sourceType(), candidate.returnCartonId(), candidate.cartonMark(),
|
||||
candidate.inboundCode(), candidate.inboundOrderNo(), candidate.overseasWarehouseId(),
|
||||
StringUtils.hasText(candidate.overseasWarehouseName()) ? candidate.overseasWarehouseName() : names.get(candidate.overseasWarehouseId()),
|
||||
candidate.inboundCreatedAt(), candidate.availablePackages(), candidate.skus(), candidate.inventoryPositionId(),
|
||||
candidate.palletNo(), candidate.cartons(), candidate.cartonSelectionSupported(), candidate.cartonSelectionBlockReason()))).toList();
|
||||
}
|
||||
|
||||
private CargoDiscardResponses.Candidate withBoxes(String customer, CargoDiscardResponses.Candidate candidate) {
|
||||
@@ -201,6 +210,7 @@ public class CargoDiscardService {
|
||||
if (!availableSkus.equals(requestedSkus)) throw conflict("目前不支持部分废弃,必须选择退货箱内全部 SKU 库存");
|
||||
}
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
if (!StringUtils.hasText(warehouseName)) warehouseName = warehouseNames().get(warehouseId);
|
||||
CargoDiscardApplication application = new CargoDiscardApplication();
|
||||
application.setId(IdWorker.getId()); application.setCustomerId(customerId);
|
||||
application.setDiscardNo(orderNoGenerator.next("DISCARD", warehouseId, application.getId()));
|
||||
@@ -258,7 +268,9 @@ public class CargoDiscardService {
|
||||
.le(query.createdTo() != null, CargoDiscardApplication::getCreatedAt, query.createdTo())
|
||||
.orderByDesc(CargoDiscardApplication::getCreatedAt).orderByDesc(CargoDiscardApplication::getId);
|
||||
Page<CargoDiscardApplication> result = applications.selectPage(Page.of(query.page(), query.size()), wrapper);
|
||||
List<CargoDiscardResponses.Summary> records = result.getRecords().stream().map(this::summary).toList();
|
||||
Map<String, String> names = result.getRecords().stream().anyMatch(a -> !StringUtils.hasText(a.getOverseasWarehouseName()))
|
||||
? warehouseNames() : Map.of();
|
||||
List<CargoDiscardResponses.Summary> records = result.getRecords().stream().map(a -> summary(a, names)).toList();
|
||||
return new PageResult<>(records, query.page(), query.size(), result.getTotal(), result.getPages());
|
||||
}
|
||||
|
||||
@@ -325,12 +337,13 @@ public class CargoDiscardService {
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public CargoDiscardResponses.Detail complete(String customerId, String discardNo, String operatorId) {
|
||||
public CargoDiscardResponses.Detail confirmCompletion(String customerId, String discardNo, String operatorId) {
|
||||
CargoDiscardApplication application = applications.lockNo(customerId, discardNo);
|
||||
if (application == null) throw notFound("Cargo discard application not found");
|
||||
if (COMPLETED.equals(application.getStatus())) return detail(customerId, discardNo);
|
||||
if (!PDA_COMPLETED.equals(application.getStatus())) throw conflict("全部托盘 PDA 作业完成后才能确认废弃完成");
|
||||
updateLinkedStatus(customerId, application, PDA_COMPLETED, COMPLETED, operatorId, "仓库确认废弃完成");
|
||||
application.setCustomerConfirmedAt(now());
|
||||
updateLinkedStatus(customerId, application, PDA_COMPLETED, COMPLETED, operatorId, "客户确认废弃完成");
|
||||
return detail(customerId, discardNo);
|
||||
}
|
||||
|
||||
@@ -478,10 +491,26 @@ public class CargoDiscardService {
|
||||
}
|
||||
|
||||
private CargoDiscardResponses.Summary summary(CargoDiscardApplication value) {
|
||||
return summary(value, StringUtils.hasText(value.getOverseasWarehouseName()) ? Map.of() : warehouseNames());
|
||||
}
|
||||
|
||||
private Map<String, String> warehouseNames() {
|
||||
var response = warehouseDirectory.receivingWarehouses(internalToken);
|
||||
if (response == null || !response.successful() || response.data() == null)
|
||||
throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE, "海外仓库目录暂不可用");
|
||||
Map<String, String> names = new HashMap<>();
|
||||
for (var warehouse : response.data()) {
|
||||
if (warehouse.warehouseId() != null && StringUtils.hasText(warehouse.warehouseName()))
|
||||
names.put(warehouse.warehouseId().toString(), warehouse.warehouseName());
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
private CargoDiscardResponses.Summary summary(CargoDiscardApplication value, Map<String, String> names) {
|
||||
return new CargoDiscardResponses.Summary(value.getId(), value.getCustomerId(), value.getDiscardNo(), value.getInboundCodes(),
|
||||
value.getOverseasWarehouseId(), value.getOverseasWarehouseName(), value.getTotalPackages(),
|
||||
value.getOverseasWarehouseId(), StringUtils.hasText(value.getOverseasWarehouseName()) ? value.getOverseasWarehouseName() : names.get(value.getOverseasWarehouseId()), value.getTotalPackages(),
|
||||
value.getTotalFee(), value.getCurrency(), value.getStatus(), value.getCreatedBy(), value.getCreatedAt(),
|
||||
value.getReviewedAt(), outboundService.outboundNo(value));
|
||||
value.getReviewedAt(), outboundService.outboundNo(value), value.getCustomerConfirmedAt());
|
||||
}
|
||||
|
||||
private void history(String customerId, Long applicationId, String from, String to, String operatorId,
|
||||
|
||||
@@ -58,6 +58,7 @@ public final class OverseasPermissions {
|
||||
public static final String MERCHANT_TRANSFER_CONFIRM = "merchant:overseas-warehouse:transfer:confirm";
|
||||
public static final String MERCHANT_DISCARD_LIST = "merchant:overseas-warehouse:discard:list";
|
||||
public static final String MERCHANT_DISCARD_CREATE = "merchant:overseas-warehouse:discard:create";
|
||||
public static final String MERCHANT_DISCARD_CONFIRM = "merchant:overseas-warehouse:discard:confirm";
|
||||
public static final String MERCHANT_DISCARD_CANCEL = "merchant:overseas-warehouse:discard:cancel";
|
||||
public static final String MERCHANT_RETURN_ORDER_LIST = "merchant:overseas-warehouse:return-order:list";
|
||||
public static final String MERCHANT_RETURN_ORDER_CREATE = "merchant:overseas-warehouse:return-order:create";
|
||||
|
||||
+15
@@ -77,6 +77,21 @@ public class PdaWarehouseTransferController {
|
||||
.contentType(MediaType.parseMediaType(file.contentType())).body(file.content());
|
||||
}
|
||||
|
||||
@GetMapping("/{transferNo}/carton-label")
|
||||
public ApiResponse<WarehouseTransferResponses.Label> cartonLabel(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String transferNo) {
|
||||
PdaWarehouseIdentity identity = PdaWarehouseIdentity.require(user);
|
||||
return ApiResponse.success(service.pdaCartonLabel(identity.overseasWarehouseId(), transferNo));
|
||||
}
|
||||
|
||||
@PostMapping("/{transferNo}/cartons/count")
|
||||
public ApiResponse<WarehouseTransferResponses.Detail> countCartons(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String transferNo,
|
||||
@Valid @RequestBody WarehouseTransferRequests.CountCartons request) {
|
||||
PdaWarehouseIdentity identity = PdaWarehouseIdentity.require(user);
|
||||
return ApiResponse.success(service.countCartons(identity.operatorKey(), identity.overseasWarehouseId(), transferNo, request));
|
||||
}
|
||||
|
||||
@PostMapping("/{transferNo}/palletize")
|
||||
public ApiResponse<WarehouseTransferResponses.Detail> palletize(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String transferNo,
|
||||
|
||||
+26
-9
@@ -97,6 +97,24 @@ public final class WarehouseTransferRequests {
|
||||
public record Outbound(@NotBlank @Size(max = 255) String idempotencyKey,
|
||||
@Size(max = 1000) String remark) {}
|
||||
|
||||
public record CountCartons(
|
||||
@NotBlank @Size(max = 255) String idempotencyKey,
|
||||
@NotEmpty @Size(max = 5000) List<@Valid @NotNull CountCarton> boxes,
|
||||
@Size(max = 1000) String discrepancyReason
|
||||
) {}
|
||||
|
||||
public record CountCarton(
|
||||
@Size(max = 255) String cartonMark,
|
||||
Long transferItemId,
|
||||
@NotNull Long transferPalletId,
|
||||
@NotNull @DecimalMin("0.01") BigDecimal length,
|
||||
@NotNull @DecimalMin("0.01") BigDecimal width,
|
||||
@NotNull @DecimalMin("0.01") BigDecimal height,
|
||||
@NotNull @DecimalMin("0.01") BigDecimal weight,
|
||||
@NotBlank @Size(max = 255) String skuCode,
|
||||
@Positive @Max(2147483647) long quantity
|
||||
) {}
|
||||
|
||||
public record Palletize(
|
||||
@NotBlank @Size(max = 255) String idempotencyKey,
|
||||
@NotEmpty @Size(max = 20) List<@NotNull Long> photoFileIds
|
||||
@@ -104,13 +122,7 @@ public final class WarehouseTransferRequests {
|
||||
|
||||
public record PalletDraft(
|
||||
Long palletId,
|
||||
@NotBlank @Size(max = 255) String idempotencyKey,
|
||||
@Positive int cartonCount,
|
||||
@NotNull @DecimalMin("0.01") BigDecimal lengthCm,
|
||||
@NotNull @DecimalMin("0.01") BigDecimal widthCm,
|
||||
@NotNull @DecimalMin("0.01") BigDecimal heightCm,
|
||||
@NotNull @DecimalMin("0.000001") BigDecimal volumeCbm,
|
||||
@NotNull @DecimalMin("0.01") BigDecimal grossWeightKg
|
||||
@NotBlank @Size(max = 255) String idempotencyKey
|
||||
) {}
|
||||
|
||||
public record PalletAction(@NotBlank @Size(max = 255) String idempotencyKey) {}
|
||||
@@ -151,8 +163,13 @@ public final class WarehouseTransferRequests {
|
||||
@Size(max = 128) String skuCode,
|
||||
@Size(max = 255) String productNameZh,
|
||||
@Positive int packageCount,
|
||||
@Size(max = 255) String shippingMark
|
||||
) {}
|
||||
@Size(max = 255) String shippingMark,
|
||||
@Positive Long quantity
|
||||
) {
|
||||
public Item(String skuCode, String productNameZh, int packageCount, String shippingMark) {
|
||||
this(skuCode, productNameZh, packageCount, shippingMark, null);
|
||||
}
|
||||
}
|
||||
|
||||
public record RelabelItem(
|
||||
@NotBlank @Size(max = 32) String operationType,
|
||||
|
||||
+4
-2
@@ -6,8 +6,10 @@ import java.util.List;
|
||||
|
||||
public final class WarehouseTransferResponses {
|
||||
private WarehouseTransferResponses() {}
|
||||
public record Item(Long id, String skuCode, String productNameZh, int packageCount, String shippingMark) {}
|
||||
public record Carton(String cartonMark, String customerCartonMark, int cartonIndex, int totalCartons, String status) {}
|
||||
public record Item(Long id, String skuCode, String productNameZh, int packageCount, String shippingMark, Long quantity) {}
|
||||
public record Carton(String cartonMark, String customerCartonMark, int cartonIndex, int totalCartons, String status,
|
||||
Long transferItemId, Long transferPalletId, BigDecimal length, BigDecimal width, BigDecimal height,
|
||||
BigDecimal weight, Long quantity, String discrepancyReason, String countedBy, LocalDateTime countedAt) {}
|
||||
public record Label(String transferNo, List<Carton> cartons, String printableHtml) {}
|
||||
public record DestinationResult(String customerId, String transferNo) {}
|
||||
public record PalletItem(Long transferItemId, String skuCode, int packageCount) {}
|
||||
|
||||
+11
@@ -13,12 +13,23 @@ public class WarehouseTransferCarton {
|
||||
@TableId(type = IdType.INPUT) private Long id;
|
||||
private Long transferOrderId;
|
||||
private Long transferItemId;
|
||||
private Long transferPalletId;
|
||||
private String customerId;
|
||||
private String cartonMark;
|
||||
private String customerCartonMark;
|
||||
private Integer cartonIndex;
|
||||
private Integer totalCartons;
|
||||
private String status;
|
||||
private java.math.BigDecimal lengthCm;
|
||||
private java.math.BigDecimal widthCm;
|
||||
private java.math.BigDecimal heightCm;
|
||||
private java.math.BigDecimal grossWeightKg;
|
||||
private Long countedQuantity;
|
||||
private String countIdempotencyKey;
|
||||
private String countRequestHash;
|
||||
private String discrepancyReason;
|
||||
private String countedBy;
|
||||
private LocalDateTime countedAt;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
|
||||
+1
@@ -15,6 +15,7 @@ public class WarehouseTransferItem {
|
||||
private String skuCode;
|
||||
private String productNameZh;
|
||||
private Integer packageCount;
|
||||
private Long declaredQuantity;
|
||||
private String shippingMark;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
|
||||
+133
-42
@@ -525,7 +525,7 @@ public class WarehouseTransferService {
|
||||
new LambdaQueryWrapper<WarehouseTransferItem>().eq(WarehouseTransferItem::getCustomerId, customerId)
|
||||
.eq(WarehouseTransferItem::getTransferOrderId, transfer.getId()).orderByAsc(WarehouseTransferItem::getId))
|
||||
.stream().map(value -> new WarehouseTransferResponses.Item(value.getId(), value.getSkuCode(), value.getProductNameZh(),
|
||||
value.getPackageCount(), value.getShippingMark())).toList();
|
||||
value.getPackageCount(), value.getShippingMark(), value.getDeclaredQuantity())).toList();
|
||||
Map<Long, WarehouseTransferResponses.Item> itemById = items.stream()
|
||||
.collect(Collectors.toMap(WarehouseTransferResponses.Item::id, Function.identity()));
|
||||
List<WarehouseTransferResponses.SourceOrder> sources = sourceOrders.selectList(
|
||||
@@ -535,7 +535,7 @@ public class WarehouseTransferService {
|
||||
value.getSourceLastlegOrderNo(), value.getInboundOrderId(), value.getInboundCode())).toList();
|
||||
List<WarehouseTransferResponses.Carton> cartons = transferCartons.listOrder(customerId, transfer.getId())
|
||||
.stream().map(value -> new WarehouseTransferResponses.Carton(value.getCartonMark(), value.getCustomerCartonMark(),
|
||||
value.getCartonIndex(), value.getTotalCartons(), value.getStatus())).toList();
|
||||
value.getCartonIndex(), value.getTotalCartons(), value.getStatus(), value.getTransferItemId(), value.getTransferPalletId(), value.getLengthCm(), value.getWidthCm(), value.getHeightCm(), value.getGrossWeightKg(), value.getCountedQuantity(), value.getDiscrepancyReason(), value.getCountedBy(), value.getCountedAt())).toList();
|
||||
List<WarehouseTransferPalletItem> palletItemRows = transferPalletItems == null
|
||||
? List.of() : transferPalletItems.listOrder(customerId, transfer.getId());
|
||||
Map<Long, List<WarehouseTransferPalletItem>> palletItemsByPallet = palletItemRows.stream()
|
||||
@@ -607,13 +607,21 @@ public class WarehouseTransferService {
|
||||
transfer.getCustomerConfirmedAt(), transfer.getOutboundAt(), transfer.getCreatedAt());
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public WarehouseTransferResponses.Label pdaCartonLabel(long warehouseId, String transferNo) {
|
||||
var transfer = transfers.findNoByWarehouse(transferNo, String.valueOf(warehouseId));
|
||||
if (transfer == null) throw notFound("当前仓没有该转运单");
|
||||
return cartonLabel(transfer.getCustomerId(), transferNo);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public WarehouseTransferResponses.Label cartonLabel(String customerId, String transferNo) {
|
||||
WarehouseTransferOrder transfer = transfers.findNo(customerId, transferNo);
|
||||
if (transfer == null) throw notFound("Warehouse transfer order not found");
|
||||
if (DRAFT.equals(transfer.getStatus())) throw conflict("Submit the warehouse transfer order before printing carton labels");
|
||||
List<WarehouseTransferResponses.Carton> cartons = transferCartons.listOrder(customerId, transfer.getId()).stream()
|
||||
.map(value -> new WarehouseTransferResponses.Carton(value.getCartonMark(), value.getCustomerCartonMark(), value.getCartonIndex(), value.getTotalCartons(), value.getStatus())).toList();
|
||||
.filter(value -> !"NOT_RECEIVED".equals(value.getStatus()))
|
||||
.map(value -> new WarehouseTransferResponses.Carton(value.getCartonMark(), value.getCustomerCartonMark(), value.getCartonIndex(), value.getTotalCartons(), value.getStatus(), value.getTransferItemId(), value.getTransferPalletId(), value.getLengthCm(), value.getWidthCm(), value.getHeightCm(), value.getGrossWeightKg(), value.getCountedQuantity(), value.getDiscrepancyReason(), value.getCountedBy(), value.getCountedAt())).toList();
|
||||
StringBuilder labels = new StringBuilder();
|
||||
for (WarehouseTransferResponses.Carton carton : cartons) {
|
||||
labels.append("<section class=label><div class=title>仓储转运箱唛</div><div class=mark>")
|
||||
@@ -689,9 +697,10 @@ public class WarehouseTransferService {
|
||||
WarehouseTransferRequests.PalletDraft request) {
|
||||
WarehouseTransferOrder transfer = requirePdaMeasuringTransfer(warehouseId, transferNo);
|
||||
String customerId = transfer.getCustomerId();
|
||||
validatePalletMeasurement(request.lengthCm(), request.widthCm(), request.heightCm(), request.volumeCbm());
|
||||
String requestHash = hash(request);
|
||||
List<WarehouseTransferPallet> existing = transferPallets.listOrder(customerId, transfer.getId());
|
||||
if (transferCartons.listOrder(customerId, transfer.getId()).stream().anyMatch(box -> box.getCountedAt() != null))
|
||||
throw conflict("箱子清点已确认,不能再新增或修改托盘");
|
||||
WarehouseTransferPallet pallet;
|
||||
if (request.palletId() == null) {
|
||||
pallet = existing.stream().filter(value -> request.idempotencyKey().equals(value.getDraftIdempotencyKey()))
|
||||
@@ -710,9 +719,9 @@ public class WarehouseTransferService {
|
||||
if (pallet == null) throw notFound("Warehouse transfer pallet draft not found");
|
||||
if (!"DRAFT".equals(pallet.getStatus())) throw conflict("Generated pallet measurements are locked");
|
||||
}
|
||||
pallet.setCartonCount(request.cartonCount()); pallet.setLengthCm(request.lengthCm());
|
||||
pallet.setWidthCm(request.widthCm()); pallet.setHeightCm(request.heightCm());
|
||||
pallet.setVolumeCbm(request.volumeCbm()); pallet.setGrossWeightKg(request.grossWeightKg());
|
||||
pallet.setCartonCount(0); pallet.setLengthCm(BigDecimal.ZERO);
|
||||
pallet.setWidthCm(BigDecimal.ZERO); pallet.setHeightCm(BigDecimal.ZERO);
|
||||
pallet.setVolumeCbm(BigDecimal.ZERO); pallet.setGrossWeightKg(BigDecimal.ZERO);
|
||||
pallet.setDraftIdempotencyKey(request.idempotencyKey()); pallet.setDraftRequestHash(requestHash);
|
||||
pallet.setUpdatedAt(LocalDateTime.now()); transferPallets.insertOrUpdate(pallet);
|
||||
fleetPool.receiving(customerId, transferNo, pdaOperatorId());
|
||||
@@ -782,6 +791,94 @@ public class WarehouseTransferService {
|
||||
return detail(transfer.getCustomerId(), transferNo);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public WarehouseTransferResponses.Detail countCartons(String operatorId, long warehouseId, String transferNo,
|
||||
WarehouseTransferRequests.CountCartons request) {
|
||||
WarehouseTransferOrder transfer = transfers.lockForPda(transferNo, String.valueOf(warehouseId));
|
||||
if (transfer == null) throw notFound("当前仓没有该转运单");
|
||||
String customerId = transfer.getCustomerId();
|
||||
List<WarehouseTransferCarton> cartons = transferCartons.listOrder(customerId, transfer.getId());
|
||||
String requestHash = hash(request);
|
||||
boolean sameKey = cartons.stream().anyMatch(box -> request.idempotencyKey().equals(box.getCountIdempotencyKey()));
|
||||
if (sameKey) {
|
||||
if (cartons.stream().anyMatch(box -> !request.idempotencyKey().equals(box.getCountIdempotencyKey())
|
||||
|| !requestHash.equals(box.getCountRequestHash()))) throw conflict("幂等键已用于其他清点内容");
|
||||
return detail(customerId, transferNo);
|
||||
}
|
||||
if (!WAIT_PDA_RECEIVING.equals(transfer.getStatus())) throw conflict("转运单已结束入库测量");
|
||||
if (cartons.stream().anyMatch(box -> box.getCountedAt() != null)) throw conflict("本单已完成清点,请刷新查看结果");
|
||||
if (cartons.isEmpty()) throw conflict("转运单缺少箱号预报");
|
||||
if (request.boxes().size() != transfer.getPackageCount() && !StringUtils.hasText(request.discrepancyReason()))
|
||||
throw badRequest("实收箱数与预报不一致时必须填写差异原因");
|
||||
Map<String, WarehouseTransferCarton> byMark = cartons.stream().collect(Collectors.toMap(WarehouseTransferCarton::getCartonMark, Function.identity()));
|
||||
Map<Long, WarehouseTransferItem> byItem = transferItems.selectList(new LambdaQueryWrapper<WarehouseTransferItem>()
|
||||
.eq(WarehouseTransferItem::getCustomerId, customerId).eq(WarehouseTransferItem::getTransferOrderId, transfer.getId()))
|
||||
.stream().collect(Collectors.toMap(WarehouseTransferItem::getId, Function.identity()));
|
||||
List<WarehouseTransferPallet> countedPallets = transferPallets.listOrder(customerId, transfer.getId());
|
||||
if (countedPallets.stream().anyMatch(pallet -> request.boxes().stream().noneMatch(box -> pallet.getId().equals(box.transferPalletId()))))
|
||||
throw badRequest("每个托盘必须至少分配一个实收箱,请核对箱子所属托盘");
|
||||
Map<Long, WarehouseTransferPallet> byPallet = countedPallets.stream().collect(Collectors.toMap(WarehouseTransferPallet::getId, Function.identity()));
|
||||
Set<String> seen = new java.util.HashSet<>();
|
||||
for (var input : request.boxes()) {
|
||||
var targetPallet = byPallet.get(input.transferPalletId());
|
||||
if (targetPallet == null || targetPallet.getWarehousePalletId() == null
|
||||
|| !Set.of("GENERATED", "BOUND").contains(targetPallet.getStatus()))
|
||||
throw badRequest("请先创建本单托盘号,再选择箱子所属托盘");
|
||||
var carton = byMark.get(input.cartonMark());
|
||||
if (StringUtils.hasText(input.cartonMark()) && (carton == null || !seen.add(input.cartonMark())))
|
||||
throw badRequest("箱号不属于本单或重复填写");
|
||||
if (carton == null && input.transferItemId() == null) throw badRequest("新增实收箱必须选择本单申报明细");
|
||||
var item = byItem.get(carton == null ? input.transferItemId() : carton.getTransferItemId());
|
||||
if (item == null || !item.getSkuCode().equals(input.skuCode())) throw badRequest("箱内 SKU 必须与该箱申报明细一致");
|
||||
if (input.quantity() <= 0 || input.quantity() > Integer.MAX_VALUE || input.length() == null || input.width() == null
|
||||
|| input.height() == null || input.weight() == null || input.length().signum() <= 0 || input.width().signum() <= 0
|
||||
|| input.height().signum() <= 0 || input.weight().signum() <= 0) throw badRequest("每箱必须填写有效尺寸、重量和实点件数");
|
||||
}
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
for (var carton : cartons) {
|
||||
if (seen.contains(carton.getCartonMark())) continue;
|
||||
carton.setStatus("NOT_RECEIVED"); carton.setCountedAt(now); carton.setCountedBy(operatorId);
|
||||
carton.setCountIdempotencyKey(request.idempotencyKey()); carton.setCountRequestHash(requestHash);
|
||||
carton.setDiscrepancyReason(trim(request.discrepancyReason())); carton.setUpdatedAt(now); transferCartons.updateById(carton);
|
||||
}
|
||||
int nextIndex = cartons.stream().mapToInt(WarehouseTransferCarton::getCartonIndex).max().orElse(0);
|
||||
for (var input : request.boxes()) {
|
||||
var carton = byMark.get(input.cartonMark());
|
||||
boolean added = carton == null;
|
||||
if (added) {
|
||||
carton = new WarehouseTransferCarton(); carton.setId(IdWorker.getId());
|
||||
carton.setTransferOrderId(transfer.getId()); carton.setTransferItemId(input.transferItemId()); carton.setCustomerId(customerId);
|
||||
carton.setCartonIndex(++nextIndex); carton.setTotalCartons(transfer.getPackageCount());
|
||||
carton.setCartonMark(transferNo + "#EX-" + nextIndex); carton.setCreatedAt(now);
|
||||
}
|
||||
carton.setTransferPalletId(input.transferPalletId());
|
||||
carton.setLengthCm(input.length()); carton.setWidthCm(input.width()); carton.setHeightCm(input.height());
|
||||
carton.setGrossWeightKg(input.weight()); carton.setCountedQuantity(input.quantity());
|
||||
carton.setCountedBy(operatorId); carton.setCountedAt(now); carton.setStatus("COUNTED");
|
||||
carton.setCountIdempotencyKey(request.idempotencyKey()); carton.setCountRequestHash(requestHash); carton.setUpdatedAt(now);
|
||||
carton.setDiscrepancyReason(trim(request.discrepancyReason()));
|
||||
if (added) transferCartons.insert(carton); else transferCartons.updateById(carton);
|
||||
}
|
||||
// Aggregate only the submitted, physically assigned boxes; never infer SKU placement from declaration order.
|
||||
for (var pallet : countedPallets) {
|
||||
var assigned = request.boxes().stream().filter(box -> pallet.getId().equals(box.transferPalletId())).toList();
|
||||
pallet.setCartonCount(assigned.size());
|
||||
pallet.setVolumeCbm(assigned.stream().map(box -> box.length().multiply(box.width()).multiply(box.height())
|
||||
.movePointLeft(6)).reduce(BigDecimal.ZERO, BigDecimal::add).setScale(6, java.math.RoundingMode.HALF_UP));
|
||||
pallet.setGrossWeightKg(assigned.stream().map(WarehouseTransferRequests.CountCarton::weight).reduce(BigDecimal.ZERO, BigDecimal::add));
|
||||
pallet.setUpdatedAt(now); transferPallets.updateById(pallet);
|
||||
}
|
||||
return detail(customerId, transferNo);
|
||||
}
|
||||
|
||||
private List<WarehouseTransferCarton> requireCountedCartons(WarehouseTransferOrder transfer) {
|
||||
var cartons = transferCartons.listOrder(transfer.getCustomerId(), transfer.getId());
|
||||
if (cartons.isEmpty() || cartons.stream().anyMatch(box -> box.getCountedAt() == null)
|
||||
|| cartons.stream().noneMatch(box -> "COUNTED".equals(box.getStatus())))
|
||||
throw conflict("请先完成全部箱子的尺寸测量和箱内清点");
|
||||
return cartons.stream().filter(box -> "COUNTED".equals(box.getStatus())).toList();
|
||||
}
|
||||
|
||||
private WarehouseTransferOrder requirePdaMeasuringTransfer(long warehouseId, String transferNo) {
|
||||
WarehouseTransferOrder transfer = transfers.lockForPda(transferNo, String.valueOf(warehouseId));
|
||||
if (transfer == null) throw notFound("Warehouse transfer order not found");
|
||||
@@ -818,6 +915,7 @@ public class WarehouseTransferService {
|
||||
if (!WAIT_PDA_RECEIVING.equals(transfer.getStatus())) {
|
||||
throw conflict("Warehouse transfer is not awaiting PDA receiving");
|
||||
}
|
||||
List<WarehouseTransferCarton> countedCartons = requireCountedCartons(transfer);
|
||||
List<WarehouseTransferItem> items = transferItems.selectList(new LambdaQueryWrapper<WarehouseTransferItem>()
|
||||
.eq(WarehouseTransferItem::getCustomerId, customerId)
|
||||
.eq(WarehouseTransferItem::getTransferOrderId, transfer.getId()).orderByAsc(WarehouseTransferItem::getId));
|
||||
@@ -838,8 +936,10 @@ public class WarehouseTransferService {
|
||||
if (persistedPallets.isEmpty() || persistedPallets.stream().anyMatch(value -> !"BOUND".equals(value.getStatus()))) {
|
||||
throw conflict("Every pallet must be generated and scan-bound before completing palletization");
|
||||
}
|
||||
if (persistedPallets.stream().anyMatch(pallet -> pallet.getCartonCount() == null || pallet.getCartonCount() <= 0)) throw conflict("每个托盘必须至少分配一个实收箱");
|
||||
int palletCartons = persistedPallets.stream().mapToInt(WarehouseTransferPallet::getCartonCount).sum();
|
||||
List<DerivedPalletAllocation> derivedAllocations = derivePersistedPalletAllocations(items, persistedPallets);
|
||||
if (palletCartons != countedCartons.size()) throw conflict("打托箱数必须与已清点实收箱数一致");
|
||||
List<DerivedPalletAllocation> derivedAllocations = deriveCountedPalletAllocations(countedCartons, persistedPallets);
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
List<WarehouseTransferPalletItem> completedPalletItems = new ArrayList<>();
|
||||
for (int index = 0; index < persistedPallets.size(); index++) {
|
||||
@@ -1452,29 +1552,20 @@ public class WarehouseTransferService {
|
||||
return result;
|
||||
}
|
||||
|
||||
static List<DerivedPalletAllocation> derivePersistedPalletAllocations(List<WarehouseTransferItem> items,
|
||||
List<WarehouseTransferPallet> pallets) {
|
||||
if (items == null || items.isEmpty()) throw conflict("Warehouse transfer declared cargo is incomplete");
|
||||
static List<DerivedPalletAllocation> deriveCountedPalletAllocations(List<WarehouseTransferCarton> cartons,
|
||||
List<WarehouseTransferPallet> pallets) {
|
||||
List<DerivedPalletAllocation> result = new ArrayList<>();
|
||||
int itemIndex = 0;
|
||||
int itemRemaining = items.get(0).getPackageCount();
|
||||
for (int palletIndex = 0; palletIndex < pallets.size(); palletIndex++) {
|
||||
int palletRemaining = pallets.get(palletIndex).getCartonCount();
|
||||
while (palletRemaining > 0) {
|
||||
WarehouseTransferItem item = items.get(itemIndex);
|
||||
if (itemIndex + 1 == items.size()) {
|
||||
result.add(new DerivedPalletAllocation(palletIndex, item.getId(), palletRemaining));
|
||||
break;
|
||||
}
|
||||
int allocated = Math.min(palletRemaining, itemRemaining);
|
||||
result.add(new DerivedPalletAllocation(palletIndex, item.getId(), allocated));
|
||||
palletRemaining -= allocated;
|
||||
itemRemaining -= allocated;
|
||||
if (itemRemaining == 0 && itemIndex + 1 < items.size()) {
|
||||
itemIndex++;
|
||||
itemRemaining = items.get(itemIndex).getPackageCount();
|
||||
}
|
||||
}
|
||||
Set<Long> palletIds = pallets.stream().map(WarehouseTransferPallet::getId).collect(Collectors.toSet());
|
||||
if (cartons.stream().anyMatch(box -> box.getTransferPalletId() == null || !palletIds.contains(box.getTransferPalletId())))
|
||||
throw conflict("每个实收箱必须分配到本单托盘");
|
||||
for (int index = 0; index < pallets.size(); index++) {
|
||||
var pallet = pallets.get(index);
|
||||
Map<Long, Integer> quantities = new java.util.LinkedHashMap<>();
|
||||
cartons.stream().filter(box -> pallet.getId().equals(box.getTransferPalletId()))
|
||||
.forEach(box -> quantities.merge(box.getTransferItemId(), 1, Integer::sum));
|
||||
if (quantities.values().stream().mapToInt(Integer::intValue).sum() != pallet.getCartonCount())
|
||||
throw conflict("托盘箱数与分配的实收箱不一致");
|
||||
for (var entry : quantities.entrySet()) result.add(new DerivedPalletAllocation(index, entry.getKey(), entry.getValue()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -1796,18 +1887,17 @@ public class WarehouseTransferService {
|
||||
pallet.setWarehouseResourceLockId(source.getWarehouseResourceLockId()); pallet.setAssignmentStatus("ASSIGNED");
|
||||
pallet.setLockedAt(source.getCompletedAt()); pallet.setActualVolumeCbm(source.getVolumeCbm());
|
||||
pallet.setActualPackageCount(source.getCartonCount()); inboundPlanPallets.insert(pallet);
|
||||
for (WarehouseTransferPalletItem sourceItem : palletItems.stream()
|
||||
.filter(row -> row.getTransferPalletId().equals(source.getId())).toList()) {
|
||||
InboundPlanPalletAllocation allocation = new InboundPlanPalletAllocation(); allocation.setId(IdWorker.getId());
|
||||
allocation.setCustomerId(customerId); allocation.setPlanId(plan.getId()); allocation.setLineId(line.getId());
|
||||
allocation.setPalletId(pallet.getId()); allocation.setAllocatedVolumeCbm(BigDecimal.ZERO);
|
||||
allocation.setPlannedPackageCount(sourceItem.getPackageCount()); allocation.setCountedPackageCount(sourceItem.getPackageCount());
|
||||
// PDA 已完成测量、打托并确认了本托箱数;入仓计划列表的“实际拣货数”
|
||||
// 直接汇总该字段,不能再保留初始化时的 0。
|
||||
allocation.setPickedPackageCount(sourceItem.getPackageCount());
|
||||
allocation.setActualVolumeCbm(source.getVolumeCbm());
|
||||
allocation.setStatus("COMPLETED"); allocation.setVersion(0); inboundPlanAllocations.insert(allocation);
|
||||
}
|
||||
// Plan allocations are unique by inbound line + pallet. SKU breakdown
|
||||
// belongs to transfer pallet items and must not duplicate that allocation.
|
||||
int receivedPackages = palletItems.stream().filter(row -> row.getTransferPalletId().equals(source.getId()))
|
||||
.mapToInt(WarehouseTransferPalletItem::getPackageCount).sum();
|
||||
if (receivedPackages != source.getCartonCount()) throw conflict("托盘明细箱数与实收不一致");
|
||||
InboundPlanPalletAllocation allocation = new InboundPlanPalletAllocation(); allocation.setId(IdWorker.getId());
|
||||
allocation.setCustomerId(customerId); allocation.setPlanId(plan.getId()); allocation.setLineId(line.getId());
|
||||
allocation.setPalletId(pallet.getId()); allocation.setAllocatedVolumeCbm(BigDecimal.ZERO);
|
||||
allocation.setPlannedPackageCount(receivedPackages); allocation.setCountedPackageCount(receivedPackages);
|
||||
allocation.setPickedPackageCount(receivedPackages); allocation.setActualVolumeCbm(source.getVolumeCbm());
|
||||
allocation.setStatus("COMPLETED"); allocation.setVersion(0); inboundPlanAllocations.insert(allocation);
|
||||
}
|
||||
plan.setAssignedFlag(true); plan.setStatus("COMPLETED"); plan.setPalletCount(pallets.size());
|
||||
plan.setTotalVolumeCbm(pallets.stream().map(WarehouseTransferPallet::getVolumeCbm)
|
||||
@@ -1879,6 +1969,7 @@ public class WarehouseTransferService {
|
||||
item.setId(IdWorker.getId()); item.setTransferOrderId(transferId); item.setCustomerId(customerId);
|
||||
item.setSkuCode(Objects.toString(trim(source.skuCode()), "UNSPECIFIED"));
|
||||
item.setProductNameZh(Objects.toString(trim(source.productNameZh()), "仓储转运货物"));
|
||||
item.setDeclaredQuantity(source.quantity());
|
||||
item.setPackageCount(source.packageCount()); item.setShippingMark(trim(source.shippingMark())); item.setCreatedAt(now);
|
||||
transferItems.insert(item);
|
||||
return item.getId();
|
||||
|
||||
Reference in New Issue
Block a user