feat: support overseas warehouse locations and A0 overflow

This commit is contained in:
2026-09-22 09:38:49 +08:00
parent d3ce268ca3
commit 1ebc128c59
15 changed files with 203 additions and 15 deletions
@@ -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());
}
}