feat: support overseas warehouse locations and A0 overflow
This commit is contained in:
+1
-1
@@ -3,4 +3,4 @@ package com.baiyue.warehouse.dto;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public record WarehouseLocationResolveResponse(long locationId, String locationCode, String locationType,
|
||||
BigDecimal maxLoadKg, String status) { }
|
||||
boolean sharedOverflow, BigDecimal maxLoadKg, String status) { }
|
||||
|
||||
+2
@@ -20,6 +20,8 @@ public class WarehouseLocationEntity extends BaseEntity {
|
||||
private String zoneCode;
|
||||
private String locationCode;
|
||||
private String locationType;
|
||||
/** A0 is a system-managed shared overflow location; normal locations are exclusive. */
|
||||
private Boolean sharedOverflow;
|
||||
private BigDecimal maxLoadKg;
|
||||
private String status;
|
||||
private String remark;
|
||||
|
||||
+8
@@ -47,6 +47,14 @@ public class WarehouseResourceRepository {
|
||||
.last("LIMIT 1"));
|
||||
}
|
||||
|
||||
public WarehouseLocationEntity findSharedOverflowLocation(long warehouseId) {
|
||||
return locations.selectOne(new LambdaQueryWrapper<WarehouseLocationEntity>()
|
||||
.eq(WarehouseLocationEntity::getCustomerId, 0L)
|
||||
.eq(WarehouseLocationEntity::getWarehouseId, warehouseId)
|
||||
.eq(WarehouseLocationEntity::getLocationCode, "A0")
|
||||
.last("LIMIT 1"));
|
||||
}
|
||||
|
||||
public WarehouseLocationEntity findLocationByCode(long customerId, long warehouseId, String locationCode) {
|
||||
return locations.selectOne(new LambdaQueryWrapper<WarehouseLocationEntity>()
|
||||
.eq(WarehouseLocationEntity::getCustomerId, customerId)
|
||||
|
||||
+15
-11
@@ -34,8 +34,8 @@ public class WarehouseResourceLockService {
|
||||
if (concurrent != null) return idempotentResult(concurrent, request);
|
||||
throw new BizException(ResultCode.NOT_FOUND, "Pallet or location does not belong to the tenant warehouse");
|
||||
}
|
||||
boolean virtualLocation = isVirtual(location);
|
||||
boolean locationAvailable = virtualLocation ? "ACTIVE".equals(location.getStatus()) : "IDLE".equals(location.getStatus());
|
||||
boolean nonExclusiveLocation = isNonExclusive(location);
|
||||
boolean locationAvailable = nonExclusiveLocation ? "ACTIVE".equals(location.getStatus()) : "IDLE".equals(location.getStatus());
|
||||
if (!"IDLE".equals(pallet.getStatus()) || !locationAvailable) {
|
||||
WarehouseResourceLockEntity concurrent =
|
||||
repository.findByReservationKey(request.customerId(), request.reservationKey());
|
||||
@@ -45,7 +45,7 @@ public class WarehouseResourceLockService {
|
||||
if (repository.occupyPallet(request.customerId(), request.warehouseId(), request.palletId(), request.locationId()) != 1) {
|
||||
return concurrentIdempotentOrConflict(request);
|
||||
}
|
||||
if (!virtualLocation && repository.occupyLocation(request.customerId(), request.warehouseId(), request.locationId()) != 1) {
|
||||
if (!nonExclusiveLocation && repository.occupyLocation(request.customerId(), request.warehouseId(), request.locationId()) != 1) {
|
||||
throw new BizException(ResultCode.CONFLICT, "Location is no longer IDLE");
|
||||
}
|
||||
|
||||
@@ -68,9 +68,9 @@ public class WarehouseResourceLockService {
|
||||
WarehouseResourceLockEntity lock = requireLock(customerId, lockId);
|
||||
if ("RELEASED".equals(lock.getStatus())) return WarehouseResourceLockResponse.from(lock);
|
||||
requireActive(lock);
|
||||
boolean virtualLocation = isVirtualLockLocation(lock);
|
||||
int palletResult = virtualLocation ? repository.releasePalletAtVirtualLocation(lock) : repository.releasePallet(lock);
|
||||
if (palletResult != 1 || (!virtualLocation && repository.releaseLocation(lock) != 1)
|
||||
boolean nonExclusiveLocation = isNonExclusiveLockLocation(lock);
|
||||
int palletResult = nonExclusiveLocation ? repository.releasePalletAtVirtualLocation(lock) : repository.releasePallet(lock);
|
||||
if (palletResult != 1 || (!nonExclusiveLocation && repository.releaseLocation(lock) != 1)
|
||||
|| repository.close(lock, "RELEASED") != 1) {
|
||||
throw new BizException(ResultCode.CONFLICT, "Resource lock state changed concurrently");
|
||||
}
|
||||
@@ -84,9 +84,9 @@ public class WarehouseResourceLockService {
|
||||
WarehouseResourceLockEntity lock = requireLock(customerId, lockId);
|
||||
if ("SCRAPPED".equals(lock.getStatus())) return WarehouseResourceLockResponse.from(lock);
|
||||
requireActive(lock);
|
||||
boolean virtualLocation = isVirtualLockLocation(lock);
|
||||
int palletResult = virtualLocation ? repository.scrapPalletAtVirtualLocation(lock) : repository.scrapPallet(lock);
|
||||
if (palletResult != 1 || (!virtualLocation && repository.releaseLocation(lock) != 1)
|
||||
boolean nonExclusiveLocation = isNonExclusiveLockLocation(lock);
|
||||
int palletResult = nonExclusiveLocation ? repository.scrapPalletAtVirtualLocation(lock) : repository.scrapPallet(lock);
|
||||
if (palletResult != 1 || (!nonExclusiveLocation && repository.releaseLocation(lock) != 1)
|
||||
|| repository.close(lock, "SCRAPPED") != 1) {
|
||||
throw new BizException(ResultCode.CONFLICT, "Resource lock state changed concurrently");
|
||||
}
|
||||
@@ -150,13 +150,17 @@ public class WarehouseResourceLockService {
|
||||
}
|
||||
|
||||
|
||||
private boolean isVirtualLockLocation(WarehouseResourceLockEntity lock) {
|
||||
private boolean isNonExclusiveLockLocation(WarehouseResourceLockEntity lock) {
|
||||
WarehouseLocationEntity location = repository.findLocation(lock.getCustomerId(), lock.getWarehouseId(), lock.getLocationId());
|
||||
if (location == null) throw new BizException(ResultCode.NOT_FOUND, "Resource lock location not found");
|
||||
return isVirtual(location);
|
||||
return isNonExclusive(location);
|
||||
}
|
||||
|
||||
private static boolean isVirtual(WarehouseLocationEntity location) {
|
||||
return "VIRTUAL".equals(location.getLocationType());
|
||||
}
|
||||
|
||||
private static boolean isNonExclusive(WarehouseLocationEntity location) {
|
||||
return isVirtual(location) || WarehouseResourceService.isSharedOverflow(location);
|
||||
}
|
||||
}
|
||||
|
||||
+56
-1
@@ -25,6 +25,7 @@ import org.springframework.util.StringUtils;
|
||||
@Service
|
||||
public class WarehouseResourceService {
|
||||
public static final long PUBLIC_RESOURCE_SCOPE = 0L;
|
||||
public static final String SHARED_OVERFLOW_LOCATION_CODE = "A0";
|
||||
private static final Set<String> LOCATION_STATUSES = Set.of("ACTIVE", "IDLE", "OCCUPIED", "INACTIVE");
|
||||
private static final Set<String> PALLET_STATUSES = Set.of("IDLE", "OCCUPIED", "SCRAPPED");
|
||||
|
||||
@@ -43,6 +44,9 @@ public class WarehouseResourceService {
|
||||
@Transactional
|
||||
public void updateLocation(long customerId, long warehouseId, long id, LocationRequests.SaveRequest request) {
|
||||
WarehouseLocationEntity existing = requireLocation(customerId, warehouseId, id);
|
||||
if (isSharedOverflow(existing)) {
|
||||
throw new BizException(ResultCode.CONFLICT, "系统共享溢出库位 A0 不允许编辑");
|
||||
}
|
||||
if ("VIRTUAL".equals(existing.getLocationType())
|
||||
&& (!"VIRTUAL".equals(request.locationType()) || !"ACTIVE".equals(request.status())
|
||||
|| !existing.getLocationCode().equals(request.locationCode()))) {
|
||||
@@ -62,12 +66,16 @@ public class WarehouseResourceService {
|
||||
public PageResult<WarehouseLocationEntity> locations(long customerId, long warehouseId,
|
||||
LocationRequests.QueryRequest query) {
|
||||
requireWarehouse(customerId, warehouseId);
|
||||
if (customerId == PUBLIC_RESOURCE_SCOPE) ensureSharedOverflowLocation(warehouseId);
|
||||
return repository.pageLocations(customerId, warehouseId, query);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteLocation(long customerId, long warehouseId, long id) {
|
||||
WarehouseLocationEntity location = requireLocation(customerId, warehouseId, id);
|
||||
if (isSharedOverflow(location)) {
|
||||
throw new BizException(ResultCode.CONFLICT, "系统共享溢出库位 A0 不允许删除");
|
||||
}
|
||||
if ("VIRTUAL".equals(location.getLocationType())) {
|
||||
throw new BizException(ResultCode.CONFLICT, "Default virtual location cannot be deleted");
|
||||
}
|
||||
@@ -192,6 +200,40 @@ public class WarehouseResourceService {
|
||||
return resolved;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public WarehouseLocationEntity ensureSharedOverflowLocation(long warehouseId) {
|
||||
SharedWarehouseReference warehouse = requireWarehouseReference(warehouseId);
|
||||
WarehouseLocationEntity existing = repository.findSharedOverflowLocation(warehouseId);
|
||||
if (existing != null) {
|
||||
if (!isSharedOverflow(existing)) {
|
||||
throw new BizException(ResultCode.CONFLICT, "A0库位编码已被非共享溢出库位占用");
|
||||
}
|
||||
return existing;
|
||||
}
|
||||
WarehouseLocationEntity location = new WarehouseLocationEntity();
|
||||
location.setId(IdWorker.getId());
|
||||
location.setCustomerId(PUBLIC_RESOURCE_SCOPE);
|
||||
location.setWarehouseId(warehouseId);
|
||||
location.setZoneCode("OVERFLOW");
|
||||
location.setLocationCode(SHARED_OVERFLOW_LOCATION_CODE);
|
||||
location.setLocationType("SHARED_OVERFLOW");
|
||||
location.setSharedOverflow(true);
|
||||
location.setMaxLoadKg(new java.math.BigDecimal("999999999.000"));
|
||||
location.setStatus("ACTIVE");
|
||||
location.setRemark("系统共享溢出库位,不得停用或删除");
|
||||
try {
|
||||
repository.insertLocation(location);
|
||||
} catch (org.springframework.dao.DuplicateKeyException ignored) {
|
||||
// Concurrent first use: read the row created by the winner.
|
||||
}
|
||||
WarehouseLocationEntity resolved = repository.findLocationByCode(PUBLIC_RESOURCE_SCOPE, warehouseId,
|
||||
SHARED_OVERFLOW_LOCATION_CODE);
|
||||
if (resolved == null || !isSharedOverflow(resolved)) {
|
||||
throw new BizException(ResultCode.CONFLICT, "共享溢出库位 A0 创建失败");
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void updatePallet(long customerId, long warehouseId, long id, PalletRequests.SaveRequest request) {
|
||||
WarehousePalletEntity existing = requirePallet(customerId, warehouseId, id);
|
||||
@@ -244,6 +286,9 @@ public class WarehouseResourceService {
|
||||
|
||||
public WarehouseLocationResolveResponse resolvePublicLocation(long warehouseId, String locationCode) {
|
||||
if (!StringUtils.hasText(locationCode)) throw new BizException(ResultCode.BAD_REQUEST, "库位码不能为空");
|
||||
if (SHARED_OVERFLOW_LOCATION_CODE.equalsIgnoreCase(locationCode.trim())) {
|
||||
return locationReference(ensureSharedOverflowLocation(warehouseId));
|
||||
}
|
||||
WarehouseLocationEntity location = repository.findLocationByCode(PUBLIC_RESOURCE_SCOPE, warehouseId,
|
||||
locationCode.trim());
|
||||
if (location == null) throw new BizException(ResultCode.NOT_FOUND, "库位码不存在或不属于当前仓库");
|
||||
@@ -258,7 +303,7 @@ public class WarehouseResourceService {
|
||||
|
||||
private static WarehouseLocationResolveResponse locationReference(WarehouseLocationEntity location) {
|
||||
return new WarehouseLocationResolveResponse(location.getId(), location.getLocationCode(),
|
||||
location.getLocationType(), location.getMaxLoadKg(), location.getStatus());
|
||||
location.getLocationType(), isSharedOverflow(location), location.getMaxLoadKg(), location.getStatus());
|
||||
}
|
||||
|
||||
private WarehousePalletResolveResponse palletReference(WarehousePalletEntity pallet) {
|
||||
@@ -345,6 +390,10 @@ public class WarehouseResourceService {
|
||||
|
||||
private void validateLocation(LocationRequests.SaveRequest request, long customerId,
|
||||
long warehouseId, Long excludedId) {
|
||||
if (SHARED_OVERFLOW_LOCATION_CODE.equalsIgnoreCase(request.locationCode())
|
||||
|| "SHARED_OVERFLOW".equals(request.locationType())) {
|
||||
throw new BizException(ResultCode.CONFLICT, "A0 是系统共享溢出库位,只能由系统按仓库懒创建");
|
||||
}
|
||||
if (!LOCATION_STATUSES.contains(request.status())) {
|
||||
throw new BizException("库位状态仅支持 ACTIVE、IDLE、OCCUPIED、INACTIVE");
|
||||
}
|
||||
@@ -412,4 +461,10 @@ public class WarehouseResourceService {
|
||||
private static String defaultLocationCode(String warehouseCode) {
|
||||
return "LOC-" + warehouseCode + "-DEFAULT";
|
||||
}
|
||||
|
||||
public static boolean isSharedOverflow(WarehouseLocationEntity location) {
|
||||
return location != null && Boolean.TRUE.equals(location.getSharedOverflow())
|
||||
&& "SHARED_OVERFLOW".equals(location.getLocationType())
|
||||
&& SHARED_OVERFLOW_LOCATION_CODE.equalsIgnoreCase(location.getLocationCode());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user