This commit is contained in:
2026-08-06 15:48:21 +08:00
parent 4df8b8c355
commit c3a978b497
71 changed files with 6268 additions and 13 deletions
@@ -4,6 +4,7 @@ import com.baiyue.warehouse.dto.LocationDtos.ConfirmCommand;
import com.baiyue.warehouse.dto.LocationDtos.OccupancyTarget;
import com.baiyue.warehouse.dto.LocationDtos.ReservationTarget;
import com.baiyue.warehouse.dto.LocationDtos.ReserveCommand;
import com.baiyue.warehouse.dto.LocationDtos.SelectiveReleaseCommand;
import com.baiyue.warehouse.entity.LocationEntity;
import com.baiyue.warehouse.entity.LocationOccupancyEntity;
import com.baiyue.warehouse.entity.LocationReservationEntity;
@@ -20,6 +21,7 @@ import java.time.LocalDateTime;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
@@ -29,7 +31,7 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@SpringJUnitConfig(LocationResourceService.class)
@SpringJUnitConfig(classes = {LocationResourceService.class, PickingPackageAllocator.class})
class LocationResourceServiceTest {
@MockBean private LocationRepository repository;
@Resource private LocationResourceService service;
@@ -138,6 +140,79 @@ class LocationResourceServiceTest {
verify(repository).insertOccupancy(any(LocationOccupancyEntity.class));
}
@Test
void shouldReleaseOnlyExplicitlySelectedLocations() {
List<LocationOccupancyEntity> active = List.of(
occupancy(201L, 101L, 9L, "A1", 501L),
occupancy(202L, 102L, 10L, "A2", 502L),
occupancy(203L, 103L, 11L, "A3", 503L));
when(repository.lockOccupiedByBusiness(10001L, "FIRSTLEG", 301L)).thenReturn(active);
when(repository.findOwned(10001L, 88L, 10L)).thenReturn(occupiedLocation(10L, "A2"));
when(repository.releaseOccupancy(active.get(1), "PDA partial pick")).thenReturn(1);
when(repository.changeStatus(10001L, 88L, 10L, "OCCUPIED", "AVAILABLE", 0, 7L)).thenReturn(1);
var result = service.releaseSelected(new SelectiveReleaseCommand(10001L, "FIRSTLEG", 301L, 7L,
"PDA partial pick", 10, List.of(10L), null));
assertThat(result).singleElement().satisfies(item -> {
assertThat(item.allocationNo()).isEqualTo(2);
assertThat(item.locationId()).isEqualTo(10L);
assertThat(item.releasedPackageCount()).isEqualTo(4);
});
verify(repository, never()).releaseOccupancy(active.get(0), "PDA partial pick");
verify(repository, never()).releaseOccupancy(active.get(2), "PDA partial pick");
}
@Test
void shouldReleaseFirstNAllocationsWhenLocationsAreNotSpecified() {
List<LocationOccupancyEntity> active = List.of(
occupancy(201L, 101L, 9L, "A1", 501L),
occupancy(202L, 102L, 10L, "A2", 502L),
occupancy(203L, 103L, 11L, "A3", 503L));
when(repository.lockOccupiedByBusiness(10001L, "FIRSTLEG", 301L)).thenReturn(active);
for (int index = 0; index < 2; index++) {
LocationOccupancyEntity item = active.get(index);
when(repository.findOwned(10001L, 88L, item.getLocationId()))
.thenReturn(occupiedLocation(item.getLocationId(), item.getLocationCode()));
when(repository.releaseOccupancy(item, "PDA default pick")).thenReturn(1);
when(repository.changeStatus(10001L, 88L, item.getLocationId(), "OCCUPIED", "AVAILABLE", 0, 7L))
.thenReturn(1);
}
var result = service.releaseSelected(new SelectiveReleaseCommand(10001L, "FIRSTLEG", 301L, 7L,
"PDA default pick", 10, List.of(), 2));
assertThat(result).extracting(item -> item.allocationNo()).containsExactly(1, 2);
assertThat(result).extracting(item -> item.locationCode()).containsExactly("A1", "A2");
assertThat(result).extracting(item -> item.releasedPackageCount()).containsExactly(4, 4);
verify(repository, never()).releaseOccupancy(active.get(2), "PDA default pick");
}
@Test
void shouldRejectLocationOwnedByAnotherInboundOrder() {
when(repository.lockOccupiedByBusiness(10001L, "FIRSTLEG", 301L)).thenReturn(List.of(
occupancy(201L, 101L, 9L, "A1", 501L)));
assertThatThrownBy(() -> service.releaseSelected(new SelectiveReleaseCommand(
10001L, "FIRSTLEG", 301L, 7L, "PDA partial pick", 10, List.of(99L), null)))
.isInstanceOf(com.baiyue.common.core.exception.BizException.class);
verify(repository, never()).releaseOccupancy(any(), any(String.class));
}
@Test
void shouldFailOnConcurrentOccupancyUpdate() {
LocationOccupancyEntity active = occupancy(201L, 101L, 9L, "A1", 501L);
when(repository.lockOccupiedByBusiness(10001L, "FIRSTLEG", 301L)).thenReturn(List.of(active));
when(repository.findOwned(10001L, 88L, 9L)).thenReturn(occupiedLocation(9L, "A1"));
when(repository.releaseOccupancy(active, "PDA pick")).thenReturn(0);
assertThatThrownBy(() -> service.releaseSelected(new SelectiveReleaseCommand(
10001L, "FIRSTLEG", 301L, 7L, "PDA pick", 10, List.of(), 1)))
.isInstanceOf(com.baiyue.common.core.exception.BizException.class);
verify(repository, never()).changeStatus(anyLong(), anyLong(), anyLong(),
any(String.class), any(String.class), anyInt(), anyLong());
}
private static ReserveCommand reserveCommand() {
return new ReserveCommand(10001L, 88L, "FIRSTLEG", 301L, "RK001", 7L,
LocalDateTime.now().plusMinutes(30), List.of(
@@ -176,6 +251,30 @@ class LocationResourceServiceTest {
return location;
}
private static LocationEntity occupiedLocation(long id, String code) {
LocationEntity location = location(id, code);
location.setStatus("OCCUPIED");
return location;
}
private static LocationOccupancyEntity occupancy(long id, long reservationId, long locationId,
String locationCode, long palletId) {
LocationOccupancyEntity occupancy = new LocationOccupancyEntity();
occupancy.setId(id);
occupancy.setTenantId(10001L);
occupancy.setReservationId(reservationId);
occupancy.setSourceService("FIRSTLEG");
occupancy.setBusinessId(301L);
occupancy.setWarehouseId(88L);
occupancy.setPalletId(palletId);
occupancy.setLocationId(locationId);
occupancy.setLocationCode(locationCode);
occupancy.setSharedOverflow(false);
occupancy.setOccupancyStatus("OCCUPIED");
occupancy.setVersion(0);
return occupancy;
}
private static LocationReservationEntity reservation(String status) {
LocationReservationEntity reservation = new LocationReservationEntity();
reservation.setId(101L);
@@ -104,6 +104,25 @@ class PalletLocationAllocationServiceTest {
verify(pallets, never()).findAvailable(any(Long.class), any(Long.class), any(), any(), any(Integer.class));
}
@Test
void shouldReleaseOnlyPalletsReturnedBySelectedLocationRelease() {
var resource = new LocationDtos.SelectiveReleaseResult(
102L, 202L, 2, 20L, 30L, 40L, "A-02", 4);
when(locations.releaseSelected(any())).thenReturn(List.of(resource));
when(pallets.transitionStatus(10001L, 20L, 30L, List.of("OCCUPIED"), "AVAILABLE"))
.thenReturn(1);
var result = service.releaseSelected(new PalletAllocationDtos.SelectiveReleaseRequest(
10001L, "FIRSTLEG", 88L, 9001L, "PDA partial pick", 10, List.of(40L), null));
assertThat(result).singleElement().satisfies(item -> {
assertThat(item.allocationNo()).isEqualTo(2);
assertThat(item.locationId()).isEqualTo(40L);
assertThat(item.releasedPackageCount()).isEqualTo(4);
});
verify(pallets).transitionStatus(10001L, 20L, 30L, List.of("OCCUPIED"), "AVAILABLE");
}
private static PalletAllocationDtos.ReserveRequest reserve(long businessId) {
return new PalletAllocationDtos.ReserveRequest(10001, 20, "FIRSTLEG", businessId, "RK-001", "measure-001",
9001, new BigDecimal("2"), new BigDecimal("2"), "木托盘", LocalDateTime.now().plusMinutes(30), true);
@@ -0,0 +1,24 @@
package com.baiyue.warehouse.service;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class PickingPackageAllocatorTest {
private final PickingPackageAllocator allocator = new PickingPackageAllocator();
@Test
void shouldUseCeilingShareAndCapFinalLocation() {
assertThat(allocator.allocate(10, 3)).containsExactly(4, 4, 2);
}
@Test
void shouldHandleExactDivision() {
assertThat(allocator.allocate(12, 3)).containsExactly(4, 4, 4);
}
@Test
void shouldNeverAllocateMoreThanRemainingPackages() {
assertThat(allocator.allocate(2, 3)).containsExactly(1, 1, 0);
}
}