修改提交

This commit is contained in:
2026-08-22 10:16:45 +08:00
parent af8724dd70
commit 93feb7da67
16 changed files with 150 additions and 44 deletions
@@ -6,7 +6,7 @@ import org.apache.ibatis.annotations.Select;
import java.util.List;
/** 读取国内已打印的稳定箱唛池;普通装柜不在国内逐箱确认归属。 */
/** 读取国内已打印的稳定箱唛池,供装柜完成事件按实装箱数抽取冻结。 */
@Mapper
public interface LoadingCartonManifestMapper {
@Select("""
@@ -24,6 +24,7 @@ import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.ArrayList;
@@ -102,9 +103,7 @@ public class LoadingCompletedOutboxService {
sum(sorted, Row::getVolumeCbm), items, string(first.getAccountId()), orderType, pallets);
}
/**
* 国内只确认本柜实装箱数,不扫每张箱唛。事件携带全量可用标签池,海外首扫才确权具体箱唛。
*/
/** 国内按实装箱数从可用标签池稳定随机抽取箱唛,重复发布同一计划时结果不变。 */
private Map<Long, List<String>> cartonLabelPools(List<Row> rows) {
Map<Long, List<String>> result = new java.util.LinkedHashMap<>();
for (Row row : rows) {
@@ -122,7 +121,14 @@ public class LoadingCompletedOutboxService {
if (labels.size() < Objects.requireNonNullElse(row.getPackageCount(), 0)) {
throw new BizException(ResultCode.CONFLICT, "装柜明细箱唛数量少于实装箱数,不能生成海外交接事件");
}
result.put(row.getLoadingPlanItemId(), labels);
int loadedPackages = Objects.requireNonNullElse(row.getPackageCount(), 0);
String seed = row.getLoadingPlanId() + ":" + row.getLoadingPlanItemId();
List<String> selected = labels.stream()
.sorted(Comparator.comparing(label -> EventPayloadHash.sha256(
(seed + ":" + label).getBytes(StandardCharsets.UTF_8))))
.limit(loadedPackages)
.toList();
result.put(row.getLoadingPlanItemId(), selected);
}
return result;
}
@@ -68,6 +68,25 @@ class LoadingCompletedOutboxServiceTest {
.hasMessageContaining("缺少 SKU");
}
@Test void freezesExactlyLoadedPackagesFromLabelPoolDeterministically() throws Exception {
Row value = row(11, 100, 101);
value.setPackageCount(2);
when(snapshots.rows(90L)).thenReturn(List.of(value));
when(manifests.labelPool(11L, 100L, 101L)).thenReturn(
List.of("BOX-001", "BOX-002", "BOX-003", "BOX-004"));
service.publish(90L);
service.publish(90L);
ArgumentCaptor<InboundOutboxEntity> captor = ArgumentCaptor.forClass(InboundOutboxEntity.class);
verify(outbox, org.mockito.Mockito.times(2)).insert(captor.capture());
EventEnvelope first = mapper.readValue(captor.getAllValues().get(0).getPayloadJson(), EventEnvelope.class);
EventEnvelope second = mapper.readValue(captor.getAllValues().get(1).getPayloadJson(), EventEnvelope.class);
assertThat(first.payload().at("/orders/0/items/0/cartonMarks")).hasSize(2);
assertThat(first.payload().at("/orders/0/items/0/cartonMarks"))
.isEqualTo(second.payload().at("/orders/0/items/0/cartonMarks"));
}
@Test void snapshotOnlyPublishesFinalizedLoadedInbounds() throws Exception {
String sql = String.join(" ", LoadingCompletedEventMapper.class.getMethod("rows", long.class)
.getAnnotation(Select.class).value()).replaceAll("\\s+", " ");