From ed3c1dafccd70d4150a8645acca1fdb14f31fcdb Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Thu, 27 Aug 2026 14:30:33 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...lWarehouseResourceSeedSqlContractTest.java | 4 + .../service/LocationResourceServiceTest.java | 119 +++++++++++++++--- 2 files changed, 103 insertions(+), 20 deletions(-) diff --git a/warehouse/src/test/java/com/baiyue/warehouse/repository/InitialWarehouseResourceSeedSqlContractTest.java b/warehouse/src/test/java/com/baiyue/warehouse/repository/InitialWarehouseResourceSeedSqlContractTest.java index 41f33aed0..72e561605 100644 --- a/warehouse/src/test/java/com/baiyue/warehouse/repository/InitialWarehouseResourceSeedSqlContractTest.java +++ b/warehouse/src/test/java/com/baiyue/warehouse/repository/InitialWarehouseResourceSeedSqlContractTest.java @@ -50,6 +50,10 @@ class InitialWarehouseResourceSeedSqlContractTest { .contains("(" + idPrefix + "204,0," + dongguanId + ",'A','A区','A0'") .contains("shared_overflow") .contains("'AVAILABLE'") + .doesNotContain("status='AVAILABLE',\n remark=VALUES(remark)") + .doesNotContain("status = 'AVAILABLE',\n remark = VALUES(remark)") + .doesNotContain("remark=VALUES(remark),\n deleted=0") + .doesNotContain("remark = VALUES(remark),\n deleted = 0") .doesNotContain("INSERT INTO wh_location_reservation") .doesNotContain("INSERT INTO wh_location_occupancy"); } diff --git a/warehouse/src/test/java/com/baiyue/warehouse/service/LocationResourceServiceTest.java b/warehouse/src/test/java/com/baiyue/warehouse/service/LocationResourceServiceTest.java index ecfe65978..f9c1d2d69 100644 --- a/warehouse/src/test/java/com/baiyue/warehouse/service/LocationResourceServiceTest.java +++ b/warehouse/src/test/java/com/baiyue/warehouse/service/LocationResourceServiceTest.java @@ -257,9 +257,7 @@ class LocationResourceServiceTest { item.setBusinessId(901L); }); when(repository.lockOccupiedByBusiness(0L, "firstleg-service", 901L)).thenReturn(active); - when(repository.findOwned(0L, 88L, 10L)).thenReturn(occupiedLocation(10L, "A2")); - when(repository.releaseOccupancy(active.get(1), "PDA partial pick")).thenReturn(1); - when(repository.changeStatus(0L, 88L, 10L, "OCCUPIED", "AVAILABLE", 0, 7L)).thenReturn(1); + stubSuccessfulOccupiedRelease(active.get(1), occupiedLocation(10L, "A2"), "PDA partial pick", 7L); var result = service.releaseSelected(new SelectiveReleaseCommand(0L, "firstleg-service", 901L, 7L, "PDA partial pick", 10, List.of(10L), null)); @@ -280,9 +278,7 @@ class LocationResourceServiceTest { LocationOccupancyEntity second = occupancy(202L, 102L, 10L, "A2", 502L); second.setAllocatedPackageCount(7); when(repository.lockOccupiedByBusiness(10001L, "FIRSTLEG", 301L)).thenReturn(List.of(first, second)); - when(repository.findOwned(10001L, 88L, 10L)).thenReturn(occupiedLocation(10L, "A2")); - when(repository.releaseOccupancy(second, "PDA full pick")).thenReturn(1); - when(repository.changeStatus(10001L, 88L, 10L, "OCCUPIED", "AVAILABLE", 0, 7L)).thenReturn(1); + stubSuccessfulOccupiedRelease(second, occupiedLocation(10L, "A2"), "PDA full pick", 7L); var result = service.releaseSelectedExactly(new com.baiyue.warehouse.dto.LocationDtos.ExactSelectiveReleaseCommand( 10001L, "FIRSTLEG", 301L, 7L, "PDA full pick", List.of(10L))); @@ -299,9 +295,7 @@ class LocationResourceServiceTest { LocationOccupancyEntity occupancy = occupancy(201L, 101L, 9L, "A1", 501L); occupancy.setAllocatedPackageCount(0); when(repository.lockOccupiedByBusiness(10001L, "FIRSTLEG", 301L)).thenReturn(List.of(occupancy)); - when(repository.findOwned(10001L, 88L, 9L)).thenReturn(occupiedLocation(9L, "A1")); - when(repository.releaseOccupancy(occupancy, "PDA full pick")).thenReturn(1); - when(repository.changeStatus(10001L, 88L, 9L, "OCCUPIED", "AVAILABLE", 0, 7L)).thenReturn(1); + stubSuccessfulOccupiedRelease(occupancy, occupiedLocation(9L, "A1"), "PDA full pick", 7L); var result = service.releaseSelectedExactly(new com.baiyue.warehouse.dto.LocationDtos.ExactSelectiveReleaseCommand( 10001L, "FIRSTLEG", 301L, 7L, "PDA full pick", List.of(9L))); @@ -323,11 +317,8 @@ class LocationResourceServiceTest { }); when(repository.lockOccupiedByBusiness(0L, "firstleg-service", 901L)).thenReturn(active); for (LocationOccupancyEntity item : List.of(allocationOne, allocationTwo)) { - when(repository.findOwned(0L, 88L, item.getLocationId())) - .thenReturn(occupiedLocation(item.getLocationId(), item.getLocationCode())); - when(repository.releaseOccupancy(item, "PDA default pick")).thenReturn(1); - when(repository.changeStatus(0L, 88L, item.getLocationId(), "OCCUPIED", "AVAILABLE", 0, 7L)) - .thenReturn(1); + stubSuccessfulOccupiedRelease(item, occupiedLocation(item.getLocationId(), item.getLocationCode()), + "PDA default pick", 7L); } var result = service.releaseSelected(new SelectiveReleaseCommand(0L, "firstleg-service", 901L, 7L, @@ -348,11 +339,8 @@ class LocationResourceServiceTest { 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); + stubSuccessfulOccupiedRelease(item, occupiedLocation(item.getLocationId(), item.getLocationCode()), + "PDA default pick", 7L); } var result = service.releaseSelected(new SelectiveReleaseCommand(10001L, "FIRSTLEG", 301L, 7L, @@ -379,7 +367,8 @@ class LocationResourceServiceTest { 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.lockOwned(10001L, 88L, 9L)).thenReturn(occupiedLocation(9L, "A1")); + when(repository.lockReservation(10001L, 101L)).thenReturn(confirmedReservationFor(active)); when(repository.releaseOccupancy(active, "PDA pick")).thenReturn(0); assertThatThrownBy(() -> service.releaseSelected(new SelectiveReleaseCommand( @@ -389,6 +378,64 @@ class LocationResourceServiceTest { any(String.class), any(String.class), anyInt(), anyLong()); } + @Test + void shouldConvergeDriftedAvailableLocationWhenLastOccupancyIsReleasedByReservation() { + LocationOccupancyEntity active = occupancy(201L, 101L, 9L, "A1", 501L); + active.setAllocatedPackageCount(6); + LocationEntity drifted = location(9L, "A1"); + drifted.setVersion(2); + when(repository.lockOccupiedByBusiness(10001L, "FIRSTLEG", 301L)).thenReturn(List.of(active)); + stubSuccessfulOccupiedRelease(active, drifted, "PDA complete", 7L); + + var result = service.releaseSelectedReservations(new com.baiyue.warehouse.dto.LocationDtos.ReservationReleaseCommand( + 10001L, "FIRSTLEG", 301L, 7L, "PDA complete", List.of(101L))); + + assertThat(result).singleElement().satisfies(item -> { + assertThat(item.reservationId()).isEqualTo(101L); + assertThat(item.locationId()).isEqualTo(9L); + }); + verify(repository).releaseConfirmedReservation(any(LocationReservationEntity.class), org.mockito.Mockito.eq("PDA complete")); + verify(repository, never()).changeStatus(anyLong(), anyLong(), anyLong(), + any(String.class), any(String.class), anyInt(), anyLong()); + } + + @Test + void shouldRejectAvailableNonSharedLocationWhenAnotherOccupancyRemains() { + LocationOccupancyEntity active = occupancy(201L, 101L, 9L, "A1", 501L); + when(repository.lockOccupiedByBusiness(10001L, "FIRSTLEG", 301L)).thenReturn(List.of(active)); + when(repository.lockOwned(10001L, 88L, 9L)).thenReturn(location(9L, "A1")); + LocationReservationEntity reservation = confirmedReservationFor(active); + when(repository.lockReservation(10001L, 101L)).thenReturn(reservation); + when(repository.releaseOccupancy(active, "PDA complete")).thenReturn(1); + when(repository.releaseConfirmedReservation(reservation, "PDA complete")).thenReturn(1); + when(repository.countActiveOccupancies(10001L, 9L)).thenReturn(1); + + assertThatThrownBy(() -> service.releaseSelectedReservations( + new com.baiyue.warehouse.dto.LocationDtos.ReservationReleaseCommand( + 10001L, "FIRSTLEG", 301L, 7L, "PDA complete", List.of(101L)))) + .isInstanceOf(com.baiyue.common.core.exception.BizException.class); + verify(repository, never()).changeStatus(anyLong(), anyLong(), anyLong(), + any(String.class), any(String.class), anyInt(), anyLong()); + } + + @Test + void shouldReleaseOneSharedOverflowAllocationWithoutOpeningOrMergingTheOther() { + LocationOccupancyEntity active = occupancy(201L, 101L, 99L, "A0", 501L); + active.setSharedOverflow(true); + active.setAllocatedPackageCount(4); + LocationEntity shared = overflow(); + when(repository.lockOccupiedByBusiness(10001L, "FIRSTLEG", 301L)).thenReturn(List.of(active)); + stubSuccessfulOccupiedRelease(active, shared, "PDA complete", 7L); + + assertThat(service.releaseSelectedReservations( + new com.baiyue.warehouse.dto.LocationDtos.ReservationReleaseCommand( + 10001L, "FIRSTLEG", 301L, 7L, "PDA complete", List.of(101L)))) + .singleElement().extracting(item -> item.reservationId()).isEqualTo(101L); + verify(repository, never()).countActiveOccupancies(anyLong(), anyLong()); + verify(repository, never()).changeStatus(anyLong(), anyLong(), anyLong(), + any(String.class), any(String.class), anyInt(), anyLong()); + } + @Test void shouldTransferConfirmedOccupiedResourcesToChildInbound() { LocationOccupancyEntity occupancy = occupancy(201L, 101L, 9L, "A1", 501L); @@ -514,4 +561,36 @@ class LocationResourceServiceTest { reservation.setVersion(0); return reservation; } + + private void stubSuccessfulOccupiedRelease(LocationOccupancyEntity occupancy, LocationEntity location, + String reason, long operatorId) { + location.setTenantId(occupancy.getTenantId()); + LocationReservationEntity reservation = confirmedReservationFor(occupancy); + when(repository.lockOwned(occupancy.getTenantId(), occupancy.getWarehouseId(), occupancy.getLocationId())) + .thenReturn(location); + when(repository.lockReservation(occupancy.getTenantId(), occupancy.getReservationId())).thenReturn(reservation); + when(repository.releaseOccupancy(occupancy, reason)).thenReturn(1); + when(repository.releaseConfirmedReservation(reservation, reason)).thenReturn(1); + if (!Boolean.TRUE.equals(location.getSharedOverflow())) { + when(repository.countActiveOccupancies(occupancy.getTenantId(), occupancy.getLocationId())).thenReturn(0); + if ("OCCUPIED".equals(location.getStatus())) { + when(repository.changeStatus(occupancy.getTenantId(), occupancy.getWarehouseId(), occupancy.getLocationId(), + "OCCUPIED", "AVAILABLE", location.getVersion(), operatorId)).thenReturn(1); + } + } + } + + private static LocationReservationEntity confirmedReservationFor(LocationOccupancyEntity occupancy) { + LocationReservationEntity reservation = reservation("CONFIRMED"); + reservation.setId(occupancy.getReservationId()); + reservation.setTenantId(occupancy.getTenantId()); + reservation.setWarehouseId(occupancy.getWarehouseId()); + reservation.setSourceService(occupancy.getSourceService()); + reservation.setBusinessId(occupancy.getBusinessId()); + reservation.setPalletId(occupancy.getPalletId()); + reservation.setLocationId(occupancy.getLocationId()); + reservation.setLocationCode(occupancy.getLocationCode()); + reservation.setSharedOverflow(occupancy.getSharedOverflow()); + return reservation; + } }