This commit is contained in:
wujinliang
2026-08-27 21:49:00 +08:00
87 changed files with 3347 additions and 252 deletions
@@ -109,6 +109,16 @@ public interface LocationMapper extends BaseMapper<LocationEntity> {
@Param("warehouseId") long warehouseId,
@Param("limit") int limit);
@Select("""
SELECT * FROM wh_location
WHERE id = #{id} AND tenant_id = #{tenantId} AND warehouse_id = #{warehouseId}
AND deleted = 0
FOR UPDATE
""")
LocationEntity lockOwned(@Param("tenantId") long tenantId,
@Param("warehouseId") long warehouseId,
@Param("id") long id);
@Insert("""
INSERT INTO wh_location (
id, tenant_id, warehouse_id, zone_code, zone_name, location_code, location_name,
@@ -61,6 +61,16 @@ public interface LocationReservationMapper extends BaseMapper<LocationReservatio
int release(@Param("tenantId") long tenantId, @Param("id") long id,
@Param("version") int version, @Param("reason") String reason);
@Update("""
UPDATE wh_location_reservation
SET reservation_status = 'RELEASED', released_at = CURRENT_TIMESTAMP(3),
release_reason = #{reason}, updated_at = CURRENT_TIMESTAMP(3), version = version + 1
WHERE id = #{id} AND tenant_id = #{tenantId}
AND reservation_status = 'CONFIRMED' AND version = #{version}
""")
int releaseConfirmed(@Param("tenantId") long tenantId, @Param("id") long id,
@Param("version") int version, @Param("reason") String reason);
@Update("""
UPDATE wh_location_reservation
SET business_id = #{targetBusinessId}, inbound_code = #{targetInboundCode},
@@ -44,6 +44,10 @@ public class LocationRepository {
.eq(LocationEntity::getId, id));
}
public LocationEntity lockOwned(long tenantId, long warehouseId, long id) {
return locations.lockOwned(tenantId, warehouseId, id);
}
public PageResult<LocationEntity> page(Long tenantId, PageQuery query) {
Page<LocationEntity> result = locations.selectLocationPage(new Page<>(query.page(), query.size()),
tenantId, query.warehouseId(), query.zoneCode(), query.locationCode(), query.locationType(), query.status());
@@ -94,6 +98,10 @@ public class LocationRepository {
|| occupancies.countActiveByLocation(tenantId, locationId) > 0;
}
public int countActiveOccupancies(long tenantId, long locationId) {
return occupancies.countActiveByLocation(tenantId, locationId);
}
public List<LocationReservationEntity> findReservationsByKeys(long tenantId, String sourceService,
Collection<String> keys) {
if (keys.isEmpty()) {
@@ -129,6 +137,10 @@ public class LocationRepository {
return reservations.release(entity.getTenantId(), entity.getId(), entity.getVersion(), reason);
}
public int releaseConfirmedReservation(LocationReservationEntity entity, String reason) {
return reservations.releaseConfirmed(entity.getTenantId(), entity.getId(), entity.getVersion(), reason);
}
public int transferConfirmedReservation(LocationReservationEntity entity, String sourceService,
long sourceBusinessId, long targetBusinessId,
String targetInboundCode) {
@@ -24,6 +24,8 @@ import com.baiyue.warehouse.entity.LocationReservationEntity;
import com.baiyue.warehouse.entity.WarehouseEntity;
import com.baiyue.warehouse.repository.LocationRepository;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -42,6 +44,7 @@ import java.util.LinkedHashMap;
import java.util.stream.Collectors;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
@@ -51,6 +54,8 @@ import java.util.Set;
@Service
@Validated
public class LocationResourceService {
private static final Logger log = LoggerFactory.getLogger(LocationResourceService.class);
@Resource
private LocationRepository repository;
@Resource
@@ -165,13 +170,8 @@ public class LocationResourceService {
if (!"OCCUPIED".equals(occupancy.getOccupancyStatus())) {
continue;
}
LocationEntity location = repository.findOwned(command.tenantId(), occupancy.getWarehouseId(), occupancy.getLocationId());
if (location == null || repository.releaseOccupancy(occupancy, command.reason()) != 1
|| (!Boolean.TRUE.equals(occupancy.getSharedOverflow())
&& repository.changeStatus(command.tenantId(), occupancy.getWarehouseId(), occupancy.getLocationId(),
"OCCUPIED", "AVAILABLE", location.getVersion(), command.operatorId()) != 1)) {
throw new BizException(ResultCode.CONFLICT, "释放占用时库位状态已被并发修改");
}
releaseOccupiedResource(occupancy, command.reason(), command.operatorId(),
"释放占用时库位状态已被并发修改");
released.add(active(occupancy, "RELEASED", null));
}
for (LocationReservationEntity reservation : repository.findReservationsByBusiness(
@@ -237,15 +237,8 @@ public class LocationResourceService {
List<SelectiveReleaseResult> released = new ArrayList<>(selectedIndexes.size());
for (int index : selectedIndexes) {
LocationOccupancyEntity occupancy = active.get(index);
LocationEntity location = repository.findOwned(command.tenantId(), occupancy.getWarehouseId(),
occupancy.getLocationId());
if (location == null || repository.releaseOccupancy(occupancy, command.reason()) != 1
|| (!Boolean.TRUE.equals(occupancy.getSharedOverflow())
&& repository.changeStatus(command.tenantId(), occupancy.getWarehouseId(), occupancy.getLocationId(),
"OCCUPIED", "AVAILABLE", location.getVersion(), command.operatorId()) != 1)) {
throw new BizException(ResultCode.CONFLICT,
"Location occupancy changed concurrently while releasing selected locations");
}
releaseOccupiedResource(occupancy, command.reason(), command.operatorId(),
"Location occupancy changed concurrently while releasing selected locations");
released.add(new SelectiveReleaseResult(occupancy.getReservationId(), occupancy.getId(), index + 1,
occupancy.getWarehouseId(), occupancy.getPalletId(), occupancy.getLocationId(),
occupancy.getLocationCode(), packages.get(index)));
@@ -289,15 +282,8 @@ public class LocationResourceService {
throw new BizException(ResultCode.CONFLICT,
"Occupied location has no valid allocated package count and cannot be released exactly");
}
LocationEntity location = repository.findOwned(command.tenantId(), occupancy.getWarehouseId(),
occupancy.getLocationId());
if (location == null || repository.releaseOccupancy(occupancy, command.reason()) != 1
|| (!Boolean.TRUE.equals(occupancy.getSharedOverflow())
&& repository.changeStatus(command.tenantId(), occupancy.getWarehouseId(), occupancy.getLocationId(),
"OCCUPIED", "AVAILABLE", location.getVersion(), command.operatorId()) != 1)) {
throw new BizException(ResultCode.CONFLICT,
"Location occupancy changed concurrently while releasing selected locations");
}
releaseOccupiedResource(occupancy, command.reason(), command.operatorId(),
"Location occupancy changed concurrently while releasing selected locations");
released.add(new ExactSelectiveReleaseResult(occupancy.getReservationId(), occupancy.getId(), index + 1,
occupancy.getWarehouseId(), occupancy.getPalletId(), occupancy.getLocationId(),
occupancy.getLocationCode(), allocatedPackageCount));
@@ -322,19 +308,94 @@ public class LocationResourceService {
LocationOccupancyEntity occupancy = active.get(index);
if (!requested.contains(occupancy.getReservationId())) continue;
Integer allocated = occupancy.getAllocatedPackageCount();
LocationEntity location = repository.findOwned(command.tenantId(), occupancy.getWarehouseId(), occupancy.getLocationId());
if (allocated == null || allocated < 0 || location == null || repository.releaseOccupancy(occupancy, command.reason()) != 1
|| (!Boolean.TRUE.equals(occupancy.getSharedOverflow()) && repository.changeStatus(command.tenantId(),
occupancy.getWarehouseId(), occupancy.getLocationId(), "OCCUPIED", "AVAILABLE", location.getVersion(),
command.operatorId()) != 1)) {
if (allocated == null || allocated < 0) {
throw new BizException(ResultCode.CONFLICT, "释放库位资源时发生并发变更");
}
releaseOccupiedResource(occupancy, command.reason(), command.operatorId(), "释放库位资源时发生并发变更");
result.add(new ExactSelectiveReleaseResult(occupancy.getReservationId(), occupancy.getId(), index + 1,
occupancy.getWarehouseId(), occupancy.getPalletId(), occupancy.getLocationId(), occupancy.getLocationCode(), allocated));
}
return List.copyOf(result);
}
/**
* Occupancy, confirmed reservation and the materialized location status form one resource fact.
* Lock the location before changing either fact so the final availability decision is serialized.
*/
private void releaseOccupiedResource(LocationOccupancyEntity occupancy, String reason, long operatorId,
String conflictMessage) {
LocationEntity location = repository.lockOwned(occupancy.getTenantId(), occupancy.getWarehouseId(),
occupancy.getLocationId());
LocationReservationEntity reservation = repository.lockReservation(
occupancy.getTenantId(), occupancy.getReservationId());
if (!matchesConfirmedResource(occupancy, reservation) || location == null) {
warnReleaseConflict(occupancy, location, reservation, 0, 0, -1, "RESOURCE_FACT_MISMATCH");
throw new BizException(ResultCode.CONFLICT, conflictMessage);
}
int occupancyAffected = repository.releaseOccupancy(occupancy, reason);
if (occupancyAffected != 1) {
warnReleaseConflict(occupancy, location, reservation, occupancyAffected, 0, -1,
"OCCUPANCY_UPDATE_MISSED");
throw new BizException(ResultCode.CONFLICT, conflictMessage);
}
int reservationAffected = repository.releaseConfirmedReservation(reservation, reason);
if (reservationAffected != 1) {
warnReleaseConflict(occupancy, location, reservation, occupancyAffected, reservationAffected, -1,
"RESERVATION_UPDATE_MISSED");
throw new BizException(ResultCode.CONFLICT, conflictMessage);
}
if (Boolean.TRUE.equals(location.getSharedOverflow())) {
return;
}
int remainingActive = repository.countActiveOccupancies(occupancy.getTenantId(), occupancy.getLocationId());
if (remainingActive > 0) {
warnReleaseConflict(occupancy, location, reservation, occupancyAffected, reservationAffected,
remainingActive, "NON_SHARED_LOCATION_STILL_OCCUPIED");
throw new BizException(ResultCode.CONFLICT, conflictMessage);
}
if ("AVAILABLE".equals(location.getStatus())) {
log.warn("warehouse_location_release_converged reservationId={} occupancyId={} locationId={} "
+ "expectedLocationStatus=OCCUPIED actualLocationStatus={} occupancyAffected={} "
+ "reservationAffected={} remainingActiveOccupancies={}",
occupancy.getReservationId(), occupancy.getId(), occupancy.getLocationId(), location.getStatus(),
occupancyAffected, reservationAffected, remainingActive);
return;
}
if (!"OCCUPIED".equals(location.getStatus())
|| repository.changeStatus(occupancy.getTenantId(), occupancy.getWarehouseId(), occupancy.getLocationId(),
"OCCUPIED", "AVAILABLE", location.getVersion(), operatorId) != 1) {
warnReleaseConflict(occupancy, location, reservation, occupancyAffected, reservationAffected,
remainingActive, "LOCATION_STATUS_UPDATE_MISSED");
throw new BizException(ResultCode.CONFLICT, conflictMessage);
}
}
private static boolean matchesConfirmedResource(LocationOccupancyEntity occupancy,
LocationReservationEntity reservation) {
return reservation != null
&& "CONFIRMED".equals(reservation.getReservationStatus())
&& Objects.equals(reservation.getWarehouseId(), occupancy.getWarehouseId())
&& Objects.equals(reservation.getBusinessId(), occupancy.getBusinessId())
&& Objects.equals(reservation.getLocationId(), occupancy.getLocationId())
&& Objects.equals(reservation.getPalletId(), occupancy.getPalletId())
&& Objects.equals(reservation.getSourceService(), occupancy.getSourceService());
}
private static void warnReleaseConflict(LocationOccupancyEntity occupancy, LocationEntity location,
LocationReservationEntity reservation, int occupancyAffected,
int reservationAffected, int remainingActive, String cause) {
log.warn("warehouse_location_release_conflict cause={} reservationId={} occupancyId={} locationId={} "
+ "expectedReservationStatus=CONFIRMED actualReservationStatus={} "
+ "expectedOccupancyStatus=OCCUPIED actualOccupancyStatus={} "
+ "expectedLocationStatus=OCCUPIED actualLocationStatus={} occupancyAffected={} "
+ "reservationAffected={} remainingActiveOccupancies={}",
cause, occupancy.getReservationId(), occupancy.getId(), occupancy.getLocationId(),
reservation == null ? null : reservation.getReservationStatus(), occupancy.getOccupancyStatus(),
location == null ? null : location.getStatus(), occupancyAffected, reservationAffected, remainingActive);
}
/**
* 将未拣货的已占用库位从主入库单转给二级子入库单。
* 转移只改变业务归属和剩余箱数,不释放库位;托盘仅作为关联快照。
@@ -85,9 +85,7 @@ ON DUPLICATE KEY UPDATE
location_type=VALUES(location_type),
max_weight_kg=VALUES(max_weight_kg),
shared_overflow=VALUES(shared_overflow),
status='AVAILABLE',
remark=VALUES(remark),
deleted=0;
remark=VALUES(remark);
INSERT INTO wh_location(
id,tenant_id,warehouse_id,zone_code,zone_name,location_code,location_name,
@@ -106,9 +104,7 @@ ON DUPLICATE KEY UPDATE
location_type=VALUES(location_type),
max_weight_kg=VALUES(max_weight_kg),
shared_overflow=VALUES(shared_overflow),
status='AVAILABLE',
remark=VALUES(remark),
deleted=0;
remark=VALUES(remark);
DROP PROCEDURE IF EXISTS assert_yiwu_location_seed;
DELIMITER //
@@ -27,6 +27,9 @@ class InitialWarehouseResourceSeedSqlContractTest {
}
private static void assertSeed(String sql, String yiwuId, String dongguanId, String idPrefix) {
int locationSeedStart = sql.indexOf("WHEN zones.zone_index = 0 AND slots.slot_number = 1 THEN 201");
int locationSeedEnd = sql.indexOf("DROP PROCEDURE IF EXISTS assert_yiwu_location_seed", locationSeedStart);
String locationSeed = sql.substring(locationSeedStart, locationSeedEnd);
assertThat(sql)
.contains("0," + yiwuId + ",'TP-001','木托盘'")
.contains("0," + dongguanId + ",'TP-002','塑料托盘'")
@@ -52,5 +55,10 @@ class InitialWarehouseResourceSeedSqlContractTest {
.contains("'AVAILABLE'")
.doesNotContain("INSERT INTO wh_location_reservation")
.doesNotContain("INSERT INTO wh_location_occupancy");
assertThat(locationSeed)
.doesNotContain("status='AVAILABLE'")
.doesNotContain("status = 'AVAILABLE'")
.doesNotContain("deleted=0")
.doesNotContain("deleted = 0");
}
}
@@ -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;
}
}