Merge remote-tracking branch 'origin/test' into dev
This commit is contained in:
@@ -38,6 +38,10 @@ public interface WarehousePalletLocationClient {
|
||||
ApiResponse<BlockResult> confirmBlock(@RequestHeader("X-Internal-Service-Token") String token,
|
||||
@RequestBody BlockConfirmRequest request);
|
||||
|
||||
@PostMapping("/pallet-locations/blocks/expand")
|
||||
ApiResponse<BlockResult> expandBlock(@RequestHeader("X-Internal-Service-Token") String token,
|
||||
@RequestBody BlockExpandRequest request);
|
||||
|
||||
@PostMapping("/pallet-locations/blocks/transfer")
|
||||
ApiResponse<BlockResult> transferBlock(@RequestHeader("X-Internal-Service-Token") String token,
|
||||
@RequestBody BlockTransferRequest request);
|
||||
@@ -51,6 +55,8 @@ public interface WarehousePalletLocationClient {
|
||||
long operatorId, LocalDateTime expiresAt) {}
|
||||
record BlockConfirmRequest(long blockId, long businessId, String idempotencyKey,
|
||||
long operatorId) {}
|
||||
record BlockExpandRequest(long blockId, long businessId, int requiredSlotCount,
|
||||
String idempotencyKey, long operatorId) {}
|
||||
record BlockTransferRequest(long blockId, long sourceBusinessId, long targetBusinessId,
|
||||
String targetInboundCode, String idempotencyKey, long operatorId) {}
|
||||
record BlockReleaseRequest(long blockId, long businessId, String idempotencyKey,
|
||||
|
||||
@@ -67,6 +67,8 @@ public class InboundMeasurementService {
|
||||
private WarehousePalletLocationClient warehousePallets;
|
||||
@Resource
|
||||
private InboundPalletAllocationRepository palletAllocations;
|
||||
@Resource
|
||||
private MeasurementLocationOccupationService locationOccupation;
|
||||
|
||||
public InboundMeasurementResponses.Detail detail(long tenantId, long inboundId) {
|
||||
InboundOrderEntity inbound = requireInbound(tenantId, inboundId);
|
||||
@@ -296,6 +298,8 @@ public class InboundMeasurementService {
|
||||
request.adjustedWeightValue(), request.adjustedWeightUnit(),
|
||||
request.adjustmentReason(), operatorId, operatorName);
|
||||
} else if (WEIGHT_CONFIRMED.equals(weight.getRecognitionStatus())) {
|
||||
locationOccupation.ensureForConfirmedWeight(
|
||||
tenantId, inboundId, operatorId, weight.getId(), weight.getVersion());
|
||||
return toWeight(weight);
|
||||
}
|
||||
BigDecimal value = effectiveValue(weight);
|
||||
@@ -324,10 +328,9 @@ public class InboundMeasurementService {
|
||||
weight.setVersion(confirmedVersion);
|
||||
insertRevision(weight, confirmedVersion, "CONFIRM", value, unit, value, unit,
|
||||
grossWeightKg, netWeightKg, null, operatorId, operatorName);
|
||||
if (!palletAllocations.list(tenantId, inboundId).isEmpty()) {
|
||||
palletAllocations.bindNextEmpty(tenantId, inboundId, weight);
|
||||
}
|
||||
recalculateConfirmedWeight(inbound, operatorId);
|
||||
locationOccupation.ensureForConfirmedWeight(
|
||||
tenantId, inboundId, operatorId, weight.getId(), confirmedVersion);
|
||||
return toWeight(weight);
|
||||
}
|
||||
|
||||
|
||||
+95
-11
@@ -23,7 +23,6 @@ import java.util.UUID;
|
||||
@Service
|
||||
public class MeasurementLocationOccupationService {
|
||||
private static final String SOURCE_SERVICE = "firstleg-service";
|
||||
private static final long PLATFORM_WAREHOUSE_RESOURCE_TENANT_ID = 0L;
|
||||
@Resource
|
||||
private WarehousePalletLocationClient warehouse;
|
||||
@Resource
|
||||
@@ -37,26 +36,43 @@ public class MeasurementLocationOccupationService {
|
||||
@Value("${baiyue.security.internal-service-token}")
|
||||
private String internalToken;
|
||||
|
||||
public void occupy(long tenantId, long inboundId, long operatorId, long measurementId,
|
||||
int measurementVersion) {
|
||||
public void ensureForConfirmedWeight(long tenantId, long inboundId, long operatorId,
|
||||
long weightResultId, int weightVersion) {
|
||||
InboundOrderEntity order = requireOrder(tenantId, inboundId);
|
||||
long warehouseId = requireWarehouse(order);
|
||||
BigDecimal volume = requireVolume(order);
|
||||
BigDecimal volume = requireVolume(tenantId, inboundId, order);
|
||||
var confirmedWeights = confirmedWeights(tenantId, inboundId);
|
||||
int requiredSlotCount = requiredSlotCount(volume, confirmedWeights.size());
|
||||
int packageCount = requirePackageCount(order);
|
||||
var current = allocations.list(tenantId, inboundId);
|
||||
if (!current.isEmpty()) {
|
||||
long blockId = requireActiveBlock(current, warehouseId);
|
||||
if (current.size() < requiredSlotCount) {
|
||||
String expandKey = "measure-block-expand-" + inboundId + "-" + weightResultId + "-" + weightVersion;
|
||||
WarehousePalletLocationClient.BlockResult expanded = requireSuccess(call(() -> warehouse.expandBlock(
|
||||
internalToken, new WarehousePalletLocationClient.BlockExpandRequest(blockId, inboundId,
|
||||
requiredSlotCount, expandKey, operatorId))), "称重确认后整票库位块扩容失败");
|
||||
requireBlockResult(expanded, requiredSlotCount, "OCCUPIED");
|
||||
if (expanded.blockId() != blockId) {
|
||||
throw new BizException(ResultCode.CONFLICT, "Warehouse扩容返回了不同整票库位块");
|
||||
}
|
||||
requirePreservedSlots(current, expanded);
|
||||
allocations.replaceBlock(tenantId, inboundId, warehouseId, packageCount, volume, expanded);
|
||||
}
|
||||
allocations.bindConfirmedWeights(tenantId, inboundId, confirmedWeights);
|
||||
return;
|
||||
}
|
||||
|
||||
// Warehouse retains released blocks for audit. A compensated attempt therefore needs a new
|
||||
// reservation key, otherwise a retry would replay the released block instead of reserving again.
|
||||
String key = "measure-block-" + inboundId + "-" + measurementId + "-" + measurementVersion
|
||||
String key = "measure-block-" + inboundId + "-" + weightResultId + "-" + weightVersion
|
||||
+ "-" + UUID.randomUUID();
|
||||
WarehousePalletLocationClient.BlockResult reserved = requireSuccess(call(() -> warehouse.reserveBlock(
|
||||
internalToken, new WarehousePalletLocationClient.BlockReserveRequest(warehouseId,
|
||||
SOURCE_SERVICE, inboundId, order.getInboundCode(), requiredSlotCount, key, operatorId,
|
||||
LocalDateTime.now().plus(properties.getReservationTtl())))),
|
||||
"测量完成后库位预占失败");
|
||||
int packageCount = requirePackageCount(order);
|
||||
requireBlockResult(reserved, requiredSlotCount, "RESERVED");
|
||||
allocations.replaceBlock(tenantId, inboundId, warehouseId, packageCount, volume, reserved);
|
||||
allocations.bindConfirmedWeights(tenantId, inboundId, confirmedWeights);
|
||||
try {
|
||||
WarehousePalletLocationClient.BlockResult occupied = requireSuccess(call(() -> warehouse.confirmBlock(
|
||||
internalToken, new WarehousePalletLocationClient.BlockConfirmRequest(reserved.blockId(), inboundId,
|
||||
@@ -75,6 +91,68 @@ public class MeasurementLocationOccupationService {
|
||||
}
|
||||
}
|
||||
|
||||
public void requireOccupied(long tenantId, long inboundId) {
|
||||
InboundOrderEntity order = requireOrder(tenantId, inboundId);
|
||||
long warehouseId = requireWarehouse(order);
|
||||
var confirmedWeights = confirmedWeights(tenantId, inboundId);
|
||||
int requiredSlotCount = requiredSlotCount(requireVolume(tenantId, inboundId, order), confirmedWeights.size());
|
||||
var current = allocations.list(tenantId, inboundId);
|
||||
requireActiveBlock(current, warehouseId);
|
||||
if (current.size() < requiredSlotCount) {
|
||||
throw new BizException(ResultCode.CONFLICT, "整票库位块容量不足,请重新确认称重图片");
|
||||
}
|
||||
requireAllWeightsBound(current, confirmedWeights);
|
||||
}
|
||||
|
||||
private long requireActiveBlock(java.util.List<com.baiyue.firstleg.entity.InboundPalletAllocationEntity> current,
|
||||
long warehouseId) {
|
||||
if (current.isEmpty() || current.get(0).getBlockId() == null || current.get(0).getBlockId() <= 0) {
|
||||
throw new BizException(ResultCode.CONFLICT, "称重图片尚未建立整票库位块");
|
||||
}
|
||||
long blockId = current.get(0).getBlockId();
|
||||
if (current.stream().anyMatch(slot -> !Long.valueOf(blockId).equals(slot.getBlockId())
|
||||
|| !Long.valueOf(warehouseId).equals(slot.getWarehouseId())
|
||||
|| !"CONFIRMED".equals(slot.getAllocationStatus())
|
||||
|| slot.getSlotNo() == null || slot.getSlotNo() <= 0
|
||||
|| slot.getReservationId() == null || slot.getReservationId() <= 0
|
||||
|| slot.getLocationId() == null || slot.getLocationId() <= 0)) {
|
||||
throw new BizException(ResultCode.CONFLICT, "整票库位块快照不完整或状态异常");
|
||||
}
|
||||
java.util.List<Integer> slotNos = current.stream().map(item -> item.getSlotNo()).sorted().toList();
|
||||
if (!slotNos.equals(java.util.stream.IntStream.rangeClosed(1, current.size()).boxed().toList())) {
|
||||
throw new BizException(ResultCode.CONFLICT, "整票库位块槽序不连续");
|
||||
}
|
||||
return blockId;
|
||||
}
|
||||
|
||||
private static void requireAllWeightsBound(
|
||||
java.util.List<com.baiyue.firstleg.entity.InboundPalletAllocationEntity> current,
|
||||
java.util.List<InboundWeightResultEntity> confirmedWeights) {
|
||||
java.util.Set<Long> bound = current.stream()
|
||||
.filter(item -> "BOUND".equals(item.getSlotStatus()))
|
||||
.map(item -> item.getWeightResultId()).filter(java.util.Objects::nonNull)
|
||||
.collect(java.util.stream.Collectors.toSet());
|
||||
if (confirmedWeights.stream().map(InboundWeightResultEntity::getId).anyMatch(id -> !bound.contains(id))) {
|
||||
throw new BizException(ResultCode.CONFLICT, "已确认称重图片未完整绑定整票库位槽");
|
||||
}
|
||||
}
|
||||
|
||||
private static void requirePreservedSlots(
|
||||
java.util.List<com.baiyue.firstleg.entity.InboundPalletAllocationEntity> current,
|
||||
WarehousePalletLocationClient.BlockResult expanded) {
|
||||
java.util.Map<Integer, WarehousePalletLocationClient.BlockResource> bySlot = expanded.resources().stream()
|
||||
.collect(java.util.stream.Collectors.toMap(WarehousePalletLocationClient.BlockResource::slotNo,
|
||||
java.util.function.Function.identity()));
|
||||
if (current.stream().anyMatch(slot -> {
|
||||
WarehousePalletLocationClient.BlockResource resource = bySlot.get(slot.getSlotNo());
|
||||
return resource == null || resource.reservationId() != slot.getReservationId()
|
||||
|| resource.locationId() != slot.getLocationId()
|
||||
|| resource.sharedOverflow() != Boolean.TRUE.equals(slot.getSharedOverflow());
|
||||
})) {
|
||||
throw new BizException(ResultCode.CONFLICT, "Warehouse扩容改变了既有库位槽");
|
||||
}
|
||||
}
|
||||
|
||||
private InboundOrderEntity requireOrder(long tenantId, long inboundId) {
|
||||
InboundOrderEntity order = workflow.findOwned(tenantId, inboundId);
|
||||
if (order == null) throw new BizException(ResultCode.NOT_FOUND, "入库单不存在");
|
||||
@@ -88,9 +166,14 @@ public class MeasurementLocationOccupationService {
|
||||
return order.getWarehouseId();
|
||||
}
|
||||
|
||||
private static BigDecimal requireVolume(InboundOrderEntity order) {
|
||||
private BigDecimal requireVolume(long tenantId, long inboundId, InboundOrderEntity order) {
|
||||
var measurement = measurements.findLatestMeasurement(tenantId, inboundId);
|
||||
if (measurement != null && measurement.getOriginalVolumeCbm() != null
|
||||
&& measurement.getOriginalVolumeCbm().signum() > 0) {
|
||||
return measurement.getOriginalVolumeCbm();
|
||||
}
|
||||
if (order.getActualVolumeCbm() == null || order.getActualVolumeCbm().signum() <= 0) {
|
||||
throw new BizException(ResultCode.CONFLICT, "入库单缺少有效实测体积");
|
||||
throw new BizException(ResultCode.CONFLICT, "入库单缺少有效客户申报体积");
|
||||
}
|
||||
return order.getActualVolumeCbm();
|
||||
}
|
||||
@@ -154,7 +237,8 @@ public class MeasurementLocationOccupationService {
|
||||
int requiredSlotCount, String expectedStatus) {
|
||||
if (result.blockId() <= 0 || !expectedStatus.equals(result.blockStatus())
|
||||
|| result.resources() == null || result.resources().size() != requiredSlotCount
|
||||
|| result.resources().stream().anyMatch(resource -> resource.blockId() != result.blockId())) {
|
||||
|| result.resources().stream().anyMatch(resource -> resource.blockId() != result.blockId()
|
||||
|| !expectedStatus.equals(resource.resourceStatus()))) {
|
||||
throw new BizException(ResultCode.CONFLICT, "Warehouse返回的整票库位块不完整");
|
||||
}
|
||||
java.util.List<Integer> slotNos = result.resources().stream()
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ public class MeasurementSubmissionService {
|
||||
long tenantId, long inboundId, long operatorId,
|
||||
InboundMeasurementRequests.SubmitRequest request) {
|
||||
var result = measurements.submit(tenantId, inboundId, operatorId, request);
|
||||
locations.occupy(tenantId, inboundId, operatorId, result.measurementId(), result.version());
|
||||
locations.requireOccupied(tenantId, inboundId);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user