fix(loading): remove ISO container validation across PDA fulfillment
This commit is contained in:
@@ -1,49 +0,0 @@
|
||||
package com.baiyue.firstleg.domain;
|
||||
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/** ISO 6346 真实集装箱号的唯一规范化与校验入口。 */
|
||||
public final class Iso6346ContainerNumber {
|
||||
private static final int[] LETTER_VALUES = {
|
||||
10, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 23, 24,
|
||||
25, 26, 27, 28, 29, 30, 31, 32, 34, 35, 36, 37, 38
|
||||
};
|
||||
|
||||
private Iso6346ContainerNumber() {
|
||||
}
|
||||
|
||||
public static String require(String raw) {
|
||||
String value = normalizeWhitespace(raw);
|
||||
if (value == null || !value.matches("[A-Z]{3}[UJZ][0-9]{7}")) {
|
||||
throw invalid();
|
||||
}
|
||||
int sum = 0;
|
||||
for (int index = 0; index < 10; index++) {
|
||||
char current = value.charAt(index);
|
||||
int number = Character.isDigit(current)
|
||||
? current - '0' : LETTER_VALUES[current - 'A'];
|
||||
sum += number * (1 << index);
|
||||
}
|
||||
int expected = sum % 11 % 10;
|
||||
if (value.charAt(10) - '0' != expected) throw invalid();
|
||||
return value;
|
||||
}
|
||||
|
||||
private static String normalizeWhitespace(String raw) {
|
||||
if (raw == null) return null;
|
||||
StringBuilder value = new StringBuilder(raw.length());
|
||||
raw.codePoints().filter(codePoint -> !Character.isWhitespace(codePoint)
|
||||
&& !Character.isSpaceChar(codePoint))
|
||||
.forEach(value::appendCodePoint);
|
||||
String normalized = value.toString().toUpperCase(Locale.ROOT);
|
||||
return normalized.isEmpty() ? null : normalized;
|
||||
}
|
||||
|
||||
private static BizException invalid() {
|
||||
return new BizException(ResultCode.BAD_REQUEST,
|
||||
"真实集装箱号必须符合ISO 6346结构、设备类别和校验位");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.baiyue.firstleg.domain;
|
||||
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.common.event.ContainerNumbers;
|
||||
|
||||
/** 真实集装箱号录入与作业门禁,与跨服务事件使用同一规范化规则。 */
|
||||
public final class RealContainerNumber {
|
||||
private RealContainerNumber() { }
|
||||
|
||||
/** 保留柜号必填要求,不再检查 ISO 长度、设备类别和校验位。 */
|
||||
public static String require(String raw) {
|
||||
try {
|
||||
return ContainerNumbers.normalizeRequired(raw);
|
||||
} catch (IllegalArgumentException exception) {
|
||||
throw new BizException(ResultCode.BAD_REQUEST, "真实集装箱号不能为空");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import com.baiyue.firstleg.dto.ContainerDtos.FullUpdateRequest;
|
||||
import com.baiyue.firstleg.dto.ContainerDtos.SaveRequest;
|
||||
import com.baiyue.firstleg.dto.ContainerDtos.SimulationCreateRequest;
|
||||
import com.baiyue.firstleg.dto.ContainerDtos.OceanBookingLinkRequest;
|
||||
import com.baiyue.firstleg.domain.Iso6346ContainerNumber;
|
||||
import com.baiyue.firstleg.domain.RealContainerNumber;
|
||||
import com.baiyue.firstleg.entity.ContainerBookingEntity;
|
||||
import com.baiyue.firstleg.repository.ContainerRepository;
|
||||
import com.baiyue.firstleg.pricebookquery.application.model.PriceBookQueryRows.ChannelRow;
|
||||
@@ -124,6 +124,7 @@ public class ContainerService {
|
||||
return required(id);
|
||||
}
|
||||
|
||||
/** 提柜后补录规范化真实柜号,保留同订舱唯一及作业冻结门禁。 */
|
||||
@Transactional
|
||||
public ContainerBookingEntity assignRealContainerNo(long id, String requestedContainerNo, int expectedVersion,
|
||||
long operatorId, String operatorName) {
|
||||
@@ -138,7 +139,7 @@ public class ContainerService {
|
||||
|| repository.hasPickTask(id) || repository.hasEvidence(id) || repository.hasDocuments(id)) {
|
||||
throw conflict("已进入PDA、证据、单证或不可逆装柜阶段,不能补录真实柜号");
|
||||
}
|
||||
String containerNo = Iso6346ContainerNumber.require(requestedContainerNo);
|
||||
String containerNo = RealContainerNumber.require(requestedContainerNo);
|
||||
if (repository.containerNoExists(container.getOceanBookingId(), containerNo, id)) {
|
||||
throw conflict("当前提单下集装箱号已存在");
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ import com.baiyue.firstleg.loading.bridge.dto.LoadingModelBridgeDtos.Link;
|
||||
import com.baiyue.firstleg.repository.LoadingListRepository;
|
||||
import com.baiyue.firstleg.repository.LoadingPickRepository;
|
||||
import com.baiyue.firstleg.repository.ContainerRepository;
|
||||
import com.baiyue.firstleg.domain.Iso6346ContainerNumber;
|
||||
import com.baiyue.firstleg.domain.RealContainerNumber;
|
||||
import com.baiyue.firstleg.domain.PriceBookChannelCatalog;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
@@ -493,15 +493,10 @@ public class LoadingListService {
|
||||
return repository.preloadDetail(requirePlan(planId));
|
||||
}
|
||||
|
||||
/** 保留关联入口已接受的柜号,运输入口首次录入或纠错仍执行原有校验。 */
|
||||
/** 未提交柜号时保留原值,首次录入或纠错统一规范化并禁止清空。 */
|
||||
private static String targetContainerNo(AggregateRow before, String requested) {
|
||||
if (requested == null) return StringUtils.hasText(before.getContainerNo()) ? before.getContainerNo() : null;
|
||||
String normalized = requested.replaceAll("[\\p{javaWhitespace}\\p{Z}]", "")
|
||||
.toUpperCase(java.util.Locale.ROOT);
|
||||
if (StringUtils.hasText(normalized) && normalized.equals(before.getContainerNo())) {
|
||||
return before.getContainerNo();
|
||||
}
|
||||
return Iso6346ContainerNumber.require(requested);
|
||||
return RealContainerNumber.require(requested);
|
||||
}
|
||||
|
||||
private static String transportHash(long planId, int version, String containerNo,
|
||||
|
||||
@@ -23,7 +23,7 @@ import com.baiyue.firstleg.dto.LoadingPickDtos.InboundInventory;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.InventorySnapshot;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.InventoryUnit;
|
||||
import com.baiyue.firstleg.dto.LoadingPickDtos.PendingTask;
|
||||
import com.baiyue.firstleg.domain.Iso6346ContainerNumber;
|
||||
import com.baiyue.firstleg.domain.RealContainerNumber;
|
||||
import com.baiyue.firstleg.client.DomesticPdaOperatorClient;
|
||||
import com.baiyue.firstleg.entity.LoadingPickAssignmentEntity;
|
||||
import com.baiyue.firstleg.entity.LoadingPickHistoryEntity;
|
||||
@@ -140,7 +140,7 @@ public class LoadingPickService {
|
||||
smartLoadingRepository.items(plan.getId()));
|
||||
}
|
||||
|
||||
/** 确认预装前验证作业资格和真实柜号,格式问题必须在实物作业前纠正。 */
|
||||
/** 确认预装前验证作业资格和真实柜号必填。 */
|
||||
public List<Link> requirePreloadConfirmationReady(long loadingPlanId) {
|
||||
inboundHoldGuard.requirePlanNotHeld(loadingPlanId);
|
||||
List<Link> links = loadingModelBridge.requireAllocatedLinksForConfirmation(loadingPlanId);
|
||||
@@ -614,12 +614,12 @@ public class LoadingPickService {
|
||||
/** 本进程事务提交后的唤醒信号,权威命令和恢复事实仍保存在数据库。 */
|
||||
public record CompletionAccepted(long commandId, long acceptedAtNanos) {}
|
||||
|
||||
/** 在仍可纠错的预装阶段提示格式错误,避免领取或实物作业后卡在完成门禁。 */
|
||||
/** 确认预装和创建 PDA 任务前要求柜号非空,不以 ISO 格式阻断作业。 */
|
||||
private static void requireContainerNumberBeforeStarting(String containerNo) {
|
||||
try {
|
||||
Iso6346ContainerNumber.require(containerNo);
|
||||
RealContainerNumber.require(containerNo);
|
||||
} catch (BizException exception) {
|
||||
throw conflict("请先在预装运输信息中纠正真实集装箱号后再确认预装或启动PDA,需符合ISO 6346");
|
||||
throw conflict("请先在预装运输信息中录入真实集装箱号后再确认预装或启动PDA");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -630,9 +630,9 @@ public class LoadingPickService {
|
||||
throw conflict("PDA确认装柜完成前必须录入真实集装箱号");
|
||||
}
|
||||
try {
|
||||
Iso6346ContainerNumber.require(booking.getContainerNo());
|
||||
RealContainerNumber.require(booking.getContainerNo());
|
||||
} catch (BizException exception) {
|
||||
throw conflict("PDA确认装柜完成前必须录入符合ISO 6346的真实集装箱号");
|
||||
throw conflict("PDA确认装柜完成前必须录入真实集装箱号");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user