装柜修改
This commit is contained in:
@@ -34,7 +34,7 @@ baiyue.security:
|
||||
internal-service-token: ${INTERNAL_SERVICE_TOKEN:change-me-to-an-internal-token-at-least-32-bytes-long}
|
||||
token-key-prefix: ${REDIS_TOKEN_KEY_PREFIX:baiyue:auth}
|
||||
issuer: ${JWT_ISSUER:auth-service}
|
||||
access-token-ttl: ${ACCESS_TOKEN_TTL:30m}
|
||||
access-token-ttl: ${ACCESS_TOKEN_TTL:24h}
|
||||
refresh-token-ttl: ${REFRESH_TOKEN_TTL:7d}
|
||||
max-login-failures: ${MAX_LOGIN_FAILURES:5}
|
||||
lock-duration: ${LOGIN_LOCK_DURATION:15m}
|
||||
|
||||
@@ -247,7 +247,7 @@ public class InboundOrderRepository {
|
||||
Function.identity(), (left, right) -> left));
|
||||
}
|
||||
|
||||
private Map<Long, String> locationCodesMap(Long tenantId, Map<Long, Long> tenantByInbound) {
|
||||
public Map<Long, String> locationCodesMap(Long tenantId, Map<Long, Long> tenantByInbound) {
|
||||
if (tenantByInbound.isEmpty()) {
|
||||
return Map.of();
|
||||
}
|
||||
|
||||
+8
-6
@@ -33,10 +33,13 @@ public class InboundPalletAllocationRepository {
|
||||
.eq(InboundPalletAllocationEntity::getInboundId, inboundId));
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
int allocationCount = result.allocations().size();
|
||||
int perAllocation = allocationCount == 0 ? 0
|
||||
: (int) Math.ceil((double) totalPackageCount / allocationCount);
|
||||
int remaining = totalPackageCount;
|
||||
for (var source : result.allocations()) {
|
||||
if (allocationCount > totalPackageCount) {
|
||||
throw new IllegalArgumentException("托盘数量不能超过入库单总箱数");
|
||||
}
|
||||
int basePackageCount = allocationCount == 0 ? 0 : totalPackageCount / allocationCount;
|
||||
int remainder = allocationCount == 0 ? 0 : totalPackageCount % allocationCount;
|
||||
for (int index = 0; index < result.allocations().size(); index++) {
|
||||
var source = result.allocations().get(index);
|
||||
InboundPalletAllocationEntity value = new InboundPalletAllocationEntity();
|
||||
value.setTenantId(tenantId);
|
||||
value.setInboundId(inboundId);
|
||||
@@ -47,10 +50,9 @@ public class InboundPalletAllocationRepository {
|
||||
value.setPalletCode(source.palletCode());
|
||||
value.setPalletCapacityCbm(capacity);
|
||||
value.setAllocatedVolumeCbm(source.allocatedVolumeCbm());
|
||||
int allocatedPackages = Math.min(perAllocation, remaining);
|
||||
int allocatedPackages = basePackageCount + (index < remainder ? 1 : 0);
|
||||
value.setAllocatedPackageCount(allocatedPackages);
|
||||
value.setPickedPackageCount(0);
|
||||
remaining -= allocatedPackages;
|
||||
value.setLocationId(source.locationId());
|
||||
value.setLocationCode(source.locationCode());
|
||||
value.setReservationId(source.reservationId());
|
||||
|
||||
+10
-9
@@ -7,6 +7,7 @@ import com.baiyue.firstleg.entity.InboundOutboxEntity;
|
||||
import com.baiyue.firstleg.repository.FinanceFeeSourceOutboxRepository;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import feign.FeignException;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@@ -32,19 +33,19 @@ public class FinanceFeeSourceOutboxRelay {
|
||||
FinanceFeeSourceOutboxService.ADJUSTED,
|
||||
FinanceFeeSourceOutboxService.VOIDED);
|
||||
|
||||
private final FinanceFeeSourceOutboxRepository repository;
|
||||
private final FinanceFeeSourceEventClient client;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final Clock clock;
|
||||
private final String workerId;
|
||||
@Resource
|
||||
private FinanceFeeSourceOutboxRepository repository;
|
||||
@Resource
|
||||
private FinanceFeeSourceEventClient client;
|
||||
@Resource
|
||||
private ObjectMapper objectMapper;
|
||||
private Clock clock = Clock.systemDefaultZone();
|
||||
private String workerId = UUID.randomUUID().toString();
|
||||
@Value("${baiyue.security.internal-service-token}")
|
||||
private String internalToken;
|
||||
|
||||
@Autowired
|
||||
public FinanceFeeSourceOutboxRelay(FinanceFeeSourceOutboxRepository repository,
|
||||
FinanceFeeSourceEventClient client,
|
||||
ObjectMapper objectMapper) {
|
||||
this(repository, client, objectMapper, Clock.systemDefaultZone(), UUID.randomUUID().toString());
|
||||
public FinanceFeeSourceOutboxRelay() {
|
||||
}
|
||||
|
||||
FinanceFeeSourceOutboxRelay(FinanceFeeSourceOutboxRepository repository,
|
||||
|
||||
+5
-1
@@ -16,6 +16,7 @@ import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
public class MeasurementLocationOccupationService {
|
||||
@@ -38,7 +39,10 @@ public class MeasurementLocationOccupationService {
|
||||
long warehouseId = requireWarehouse(order);
|
||||
long resourceTenantId = PLATFORM_WAREHOUSE_RESOURCE_TENANT_ID;
|
||||
BigDecimal volume = requireVolume(order);
|
||||
String key = "measure-" + inboundId + "-" + measurementId + "-" + measurementVersion;
|
||||
// 补偿释放后的预占记录需要保留审计,失败重试必须使用新的物理预占键;
|
||||
// 若仍存在活动预占,仓储侧会按业务单据复用,不会创建新的资源占用。
|
||||
String key = "measure-" + inboundId + "-" + measurementId + "-" + measurementVersion
|
||||
+ "-" + UUID.randomUUID();
|
||||
WarehousePalletLocationClient.BatchResult reserved = requireSuccess(call(() -> warehouse.reserve(
|
||||
internalToken, new WarehousePalletLocationClient.ReserveRequest(resourceTenantId, warehouseId,
|
||||
SOURCE_SERVICE, inboundId, order.getInboundCode(), key, operatorId, volume,
|
||||
|
||||
@@ -79,7 +79,7 @@ public class UnifiedInboundQueryService {
|
||||
}
|
||||
InboundTransportSnapshotEntity transport = includePhaseThree
|
||||
? operations.findTransport(tenantId, inbound.getId()) : null;
|
||||
return new UnifiedInboundDetail(summary(inbound, detail, preload, pick, evidence, deliveryAt, transport), timeline);
|
||||
return new UnifiedInboundDetail(summary(inbound, detail, preload, pick, evidence, deliveryAt, transport, null), timeline);
|
||||
}
|
||||
|
||||
private List<UnifiedInboundSummary> summaries(List<InboundOrderEntity> records) {
|
||||
@@ -92,17 +92,20 @@ public class UnifiedInboundQueryService {
|
||||
var evidences = operations.findEvidence(phaseThreeOwners);
|
||||
var deliveries = operations.findDeliveryHistory(phaseThreeOwners);
|
||||
var transports = operations.findTransports(phaseThreeOwners);
|
||||
Map<Long, Long> owners = records.stream().collect(Collectors.toMap(
|
||||
InboundOrderEntity::getId, InboundOrderEntity::getTenantId, (left, right) -> left));
|
||||
var locationCodes = inbounds.locationCodesMap(null, owners);
|
||||
return records.stream().map(inbound -> summary(inbound, null, preloads.get(inbound.getId()),
|
||||
picks.get(inbound.getId()), evidences.getOrDefault(inbound.getId(), List.of()),
|
||||
deliveries.get(inbound.getId()) == null ? null : deliveries.get(inbound.getId()).getOccurredAt(),
|
||||
transports.get(inbound.getId()))).toList();
|
||||
transports.get(inbound.getId()), locationCodes.get(inbound.getId()))).toList();
|
||||
}
|
||||
|
||||
private UnifiedInboundSummary summary(InboundOrderEntity inbound, InboundOrderDetail detail,
|
||||
com.baiyue.firstleg.split.entity.PreloadPlanEntity preload,
|
||||
com.baiyue.firstleg.loading.entity.InboundLoadingPickTaskEntity pick,
|
||||
List<LoadingEvidenceEntity> evidence, LocalDateTime deliveryAt,
|
||||
InboundTransportSnapshotEntity transport) {
|
||||
InboundTransportSnapshotEntity transport, String locationCodes) {
|
||||
String mainStatus = mainStatus(inbound.getProcessStatus());
|
||||
List<LoadingEvidenceEntity> activeEvidence = evidence.stream()
|
||||
.filter(item -> item.getStatus() == null || "ACTIVE".equals(item.getStatus())).toList();
|
||||
@@ -121,7 +124,7 @@ public class UnifiedInboundQueryService {
|
||||
"DELIVERED".equals(mainStatus) ? "COMPLETED" : "PENDING", deliveryAt,
|
||||
inbound.getExpressTrackingNo()) : null;
|
||||
var inboundSummary = detail == null
|
||||
? com.baiyue.firstleg.dto.InboundOrderSummary.from(inbound, transport, null) : detail.summary();
|
||||
? com.baiyue.firstleg.dto.InboundOrderSummary.from(inbound, transport, locationCodes) : detail.summary();
|
||||
return new UnifiedInboundSummary(inboundSummary, mainStatus,
|
||||
nextAction(mainStatus, inbound.getProcessStatus(), preload == null ? null : preload.getStatus()),
|
||||
preloadSummary, loadingSummary, deliverySummary);
|
||||
|
||||
+1
-1
@@ -58,7 +58,7 @@ class MeasurementLocationOccupationServiceTest {
|
||||
verify(warehouse).reserve(org.mockito.ArgumentMatchers.eq("internal-token"), reserve.capture());
|
||||
assertThat(reserve.getValue().tenantId()).isEqualTo(PLATFORM_RESOURCE_TENANT);
|
||||
assertThat(reserve.getValue().businessId()).isEqualTo(INBOUND);
|
||||
assertThat(reserve.getValue().idempotencyKey()).isEqualTo("measure-88-30-3");
|
||||
assertThat(reserve.getValue().idempotencyKey()).startsWith("measure-88-30-3-");
|
||||
|
||||
ArgumentCaptor<WarehousePalletLocationClient.ConfirmRequest> confirm =
|
||||
ArgumentCaptor.forClass(WarehousePalletLocationClient.ConfirmRequest.class);
|
||||
|
||||
@@ -46,7 +46,8 @@ public interface FirstlegUnifiedOrderClient {
|
||||
Integer version,
|
||||
Long customerId, String customerName, Long warehouseId, String warehouseName,
|
||||
Long deliveryWarehouseId, String deliveryWarehouseName, String channelName,
|
||||
Integer totalPackageCount, BigDecimal actualGrossWeightKg, BigDecimal actualVolumeCbm,
|
||||
Integer totalPackageCount, String locationCodes,
|
||||
BigDecimal actualGrossWeightKg, BigDecimal actualVolumeCbm,
|
||||
BigDecimal chargeableWeightKg, LocalDateTime measureCompletedAt,
|
||||
String loadingStatus, String containerNo, String cabinetNo, String vesselName, String voyageNo,
|
||||
LocalDateTime sailingAt, LocalDateTime portArrivalAt,
|
||||
|
||||
@@ -18,7 +18,8 @@ public final class UnifiedOrderResponse {
|
||||
Long receivingWarehouseId, String receivingWarehouseName,
|
||||
Long destinationWarehouseId, String destinationWarehouseName,
|
||||
Long inboundId, Integer inboundVersion, String inboundCode, LocalDate expectedShipDate,
|
||||
Integer totalPackageCount, BigDecimal actualGrossWeightKg, BigDecimal actualVolumeCbm,
|
||||
Integer totalPackageCount, String recommendedPalletType, String locationCodes,
|
||||
BigDecimal actualGrossWeightKg, BigDecimal actualVolumeCbm,
|
||||
MeasurementSummary measurement, PreloadSummary preload,
|
||||
LoadingSummary loading, DeliverySummary delivery, TransportSummary transport,
|
||||
String customerRemark, String latestInternalNote,
|
||||
|
||||
@@ -140,7 +140,7 @@ public class UnifiedOrderQueryService {
|
||||
order.getChannelName(), order.getReceivingWarehouseId(), order.getReceivingWarehouseName(),
|
||||
order.getDestinationWarehouseId(), order.getDestinationWarehouseName(), null, null,
|
||||
order.getInboundCode(),
|
||||
order.getExpectedShipDate(), null, null, null, null, null, null, null, null,
|
||||
order.getExpectedShipDate(), null, null, null, null, null, null, null, null, null, null,
|
||||
order.getCustomerRemark(), latestInternalNote,
|
||||
order.getCreatedAt(), order.getUpdatedAt());
|
||||
}
|
||||
@@ -149,8 +149,9 @@ public class UnifiedOrderQueryService {
|
||||
summary.mainStatus(), summary.nextAction(), order.getStatus(), order.getCustomerId(), order.getCustomerName(),
|
||||
order.getChannelName(), order.getReceivingWarehouseId(), order.getReceivingWarehouseName(),
|
||||
order.getDestinationWarehouseId(), order.getDestinationWarehouseName(), inbound.id(), inbound.version(), inbound.inboundCode(),
|
||||
order.getExpectedShipDate(), inbound.totalPackageCount(), inbound.actualGrossWeightKg(),
|
||||
inbound.actualVolumeCbm(), measurement(inbound), preload(summary.preload()), loading(summary.loading()),
|
||||
order.getExpectedShipDate(), inbound.totalPackageCount(), "STANDARD", inbound.locationCodes(),
|
||||
inbound.actualGrossWeightKg(), inbound.actualVolumeCbm(), measurement(inbound),
|
||||
preload(summary.preload()), loading(summary.loading()),
|
||||
delivery(summary.delivery()), transport(inbound), order.getCustomerRemark(), latestInternalNote,
|
||||
order.getCreatedAt(), order.getUpdatedAt());
|
||||
}
|
||||
@@ -162,7 +163,8 @@ public class UnifiedOrderQueryService {
|
||||
summary.mainStatus(), summary.nextAction(), parseOrderStatus(inbound.orderStatusSnapshot()),
|
||||
inbound.customerId(), inbound.customerName(), inbound.channelName(), inbound.warehouseId(),
|
||||
inbound.warehouseName(), inbound.deliveryWarehouseId(), inbound.deliveryWarehouseName(),
|
||||
inbound.id(), inbound.version(), inbound.inboundCode(), null, inbound.totalPackageCount(), inbound.actualGrossWeightKg(),
|
||||
inbound.id(), inbound.version(), inbound.inboundCode(), null, inbound.totalPackageCount(),
|
||||
"STANDARD", inbound.locationCodes(), inbound.actualGrossWeightKg(),
|
||||
inbound.actualVolumeCbm(), measurement(inbound), preload(summary.preload()), loading(summary.loading()),
|
||||
delivery(summary.delivery()), transport(inbound), order == null ? null : order.getCustomerRemark(), latestInternalNote,
|
||||
inbound.createdAt(), inbound.updatedAt());
|
||||
|
||||
@@ -136,6 +136,8 @@ class UnifiedOrderQueryServiceTest {
|
||||
assertThat(row.loading().loadingVersion()).isEqualTo(3);
|
||||
assertThat(row.loading().pickingVersion()).isEqualTo(3);
|
||||
assertThat(row.inboundCode()).isEqualTo("RK-77");
|
||||
assertThat(row.recommendedPalletType()).isEqualTo("STANDARD");
|
||||
assertThat(row.locationCodes()).isEqualTo("A0-01, B2-03");
|
||||
});
|
||||
verify(orders, never()).unifiedPage(any(), any(), any());
|
||||
}
|
||||
@@ -244,7 +246,7 @@ class UnifiedOrderQueryServiceTest {
|
||||
private FirstlegUnifiedOrderClient.Summary summary(String status, String action) {
|
||||
var inbound = new FirstlegUnifiedOrderClient.Inbound(
|
||||
88L, 10001L, 77L, "FL-77", "RK-77", "INBOUNDED", "APPROVED", "APPROVED", 7,
|
||||
30001L, "测试客户", 11L, "深圳仓", 22L, "ONT8", "美森", 10, null, null, null,
|
||||
30001L, "测试客户", 11L, "深圳仓", 22L, "ONT8", "美森", 10, "A0-01, B2-03", null, null, null,
|
||||
null, null, null, null, null, null, null, null, null,
|
||||
LocalDateTime.of(2026, 8, 6, 10, 0), LocalDateTime.of(2026, 8, 6, 11, 0));
|
||||
return new FirstlegUnifiedOrderClient.Summary(inbound, status, action,
|
||||
|
||||
+6
-4
@@ -108,13 +108,15 @@ public class PalletLocationAllocationService {
|
||||
if (active.size() != split.palletCount()) {
|
||||
throw new BizException(ResultCode.CONFLICT, "活动预占数量与托盘拆分数量不一致");
|
||||
}
|
||||
if (active.size() > request.packageCount()) {
|
||||
throw new BizException(ResultCode.CONFLICT, "托盘数量超过入库单总箱数,无法为每个托盘分配至少一箱");
|
||||
}
|
||||
active.sort(Comparator.comparingLong(LocationDtos.ActiveResource::reservationId));
|
||||
List<LocationDtos.OccupancyTarget> targets = new ArrayList<>(active.size());
|
||||
int perAllocation = (int) Math.ceil((double) request.packageCount() / active.size());
|
||||
int remainingPackages = request.packageCount();
|
||||
int basePackageCount = request.packageCount() / active.size();
|
||||
int remainder = request.packageCount() % active.size();
|
||||
for (int index = 0; index < active.size(); index++) {
|
||||
int allocatedPackages = Math.min(perAllocation, remainingPackages);
|
||||
remainingPackages -= allocatedPackages;
|
||||
int allocatedPackages = basePackageCount + (index < remainder ? 1 : 0);
|
||||
targets.add(new LocationDtos.OccupancyTarget(active.get(index).reservationId(),
|
||||
split.allocations().get(index).allocatedVolumeCbm(), allocatedPackages));
|
||||
}
|
||||
|
||||
+44
@@ -21,6 +21,7 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -140,6 +141,49 @@ class PalletLocationAllocationServiceTest {
|
||||
.isEqualTo(20);
|
||||
}
|
||||
|
||||
@Test
|
||||
void confirmDistributesRemainderWithoutZeroPackageAllocations() {
|
||||
List<LocationDtos.ActiveResource> resources = new ArrayList<>();
|
||||
List<LocationDtos.OccupancyResult> occupancyResults = new ArrayList<>();
|
||||
for (int index = 1; index <= 10; index++) {
|
||||
long reservationId = 100L + index;
|
||||
long palletId = 200L + index;
|
||||
long locationId = 300L + index;
|
||||
resources.add(new LocationDtos.ActiveResource(reservationId, null, 20L, palletId,
|
||||
"TP-" + index, locationId, "A-" + index, false, "RESERVED"));
|
||||
occupancyResults.add(new LocationDtos.OccupancyResult(400L + index, "OC-" + index,
|
||||
reservationId, palletId, locationId, "A-" + index, false));
|
||||
when(pallets.transitionStatus(10001L, 20L, palletId, List.of("RESERVED"), "OCCUPIED"))
|
||||
.thenReturn(1);
|
||||
}
|
||||
when(locations.findActiveResources(10001L, "FIRSTLEG", 89L)).thenReturn(resources);
|
||||
when(locations.confirmOccupied(any())).thenReturn(occupancyResults);
|
||||
|
||||
service.confirm(new PalletAllocationDtos.ConfirmRequest(10001L, 20L, "FIRSTLEG", 89L,
|
||||
9001L, new BigDecimal("12.0"), new BigDecimal("1.2"), 12));
|
||||
|
||||
org.mockito.ArgumentCaptor<LocationDtos.ConfirmCommand> command =
|
||||
org.mockito.ArgumentCaptor.forClass(LocationDtos.ConfirmCommand.class);
|
||||
verify(locations).confirmOccupied(command.capture());
|
||||
assertThat(command.getValue().targets())
|
||||
.extracting(LocationDtos.OccupancyTarget::allocatedPackageCount)
|
||||
.containsExactly(2, 2, 1, 1, 1, 1, 1, 1, 1, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void confirmRejectsMorePalletsThanPackages() {
|
||||
List<LocationDtos.ActiveResource> resources = List.of(
|
||||
new LocationDtos.ActiveResource(101L, null, 20L, 30L, "TP-001", 40L, "A-01", false, "RESERVED"),
|
||||
new LocationDtos.ActiveResource(102L, null, 20L, 31L, "TP-002", 41L, "A-02", false, "RESERVED"));
|
||||
when(locations.findActiveResources(10001L, "FIRSTLEG", 90L)).thenReturn(resources);
|
||||
|
||||
assertThatThrownBy(() -> service.confirm(new PalletAllocationDtos.ConfirmRequest(10001L, 20L,
|
||||
"FIRSTLEG", 90L, 9001L, new BigDecimal("2.0"), new BigDecimal("1.0"), 1)))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("托盘数量超过入库单总箱数");
|
||||
verify(locations, never()).confirmOccupied(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReleaseOnlyPalletsReturnedBySelectedLocationRelease() {
|
||||
var resource = new LocationDtos.ExactSelectiveReleaseResult(
|
||||
|
||||
Reference in New Issue
Block a user