优化
This commit is contained in:
+1
-1
@@ -28,7 +28,7 @@
|
||||
| 分柜与预装 | `split/controller/PlatformSplitPreloadController.java`、`controller/PlatformLoadingListController.java` |
|
||||
| 货柜与智能排柜 | `controller/PlatformContainerController.java`、`PlatformSmartLoadingController.java`、`service/SmartLoadingService.java`、`repository/SmartLoadingRepository.java`、`loading/`;按入仓码候选与箱数分配分别从 `/inbound-candidates`、`/plans/{planId}/inbound-assignments` 进入,明细规格拆分留在服务端 |
|
||||
| PDA 拣货与装柜 | `controller/PlatformLoadingPdaController.java`、`loading/controller/PlatformLoadingDeliveryController.java` |
|
||||
| 装柜完成跨区事件 | `service/LoadingCompletedOutboxService.java`、`LoadingCompletedOutboxRelay.java`、`repository/LoadingCompletedOutboxRepository.java`、`mapper/LoadingCartonManifestMapper.java`、`client/EventBridgeClient.java`;按最终实装与稳定箱码冻结 V2,真实生产验收 `src/test/java/com/baiyue/firstleg/service/TaillegRealProducerAcceptanceTest.java`;事件模型见 `../common-event-contract/AGENTS.md` |
|
||||
| 装柜完成跨区事件 | `service/LoadingCompletedOutboxService.java`、`LoadingCompletedOutboxRelay.java`、`repository/LoadingCompletedOutboxRepository.java`、`mapper/LoadingCartonManifestMapper.java`、`client/EventBridgeClient.java`;从 `fl_inbound_order.destination_type` 冻结 V2 `orders[].destinationType=FBA|OVERSEAS`,OVERSEAS 按最终实装与稳定箱码冻结,FBA 可仅冻结箱数;真实生产验收 `src/test/java/com/baiyue/firstleg/service/TaillegRealProducerAcceptanceTest.java`;事件模型见 `../common-event-contract/AGENTS.md` |
|
||||
| 海外卸柜完成事件回写 | 内部入口 `controller/InternalOverseasContainerEventController.java`、消费者 `service/OverseasContainerEventConsumer.java`、Inbox `entity/ContainerEventInboxEntity.java` 与 `mapper/ContainerEventInboxMapper.java`;事件模型见 `../common-event-contract/AGENTS.md` |
|
||||
| 三期海运订舱与出仓前船期资料 | `oceanbooking/controller/PlatformOceanBookingController.java`、`oceanbooking/service/OceanBookingService.java`、`portschedule/controller/PlatformPortScheduleController.java`;订舱契约见 `src/main/resources/openapi/ocean-bookings-v1.yaml`,表结构见 `src/main/resources/db/phase3-ocean-booking-schema.sql` |
|
||||
| 四期国内出仓后船节点定时查询 | `outbound/dispatch/service/OutboundDispatchService.java` 在确认出仓事务内幂等创建现有 `CarrierScheduleQuery`;编排与租约 `portschedule/service/CarrierScheduleQueryScheduler.java`,任务/十节点事实/异常事实/不可变 attempt `CarrierScheduleQueryService.java`,平台查询与人工补录入口 `PlatformCarrierScheduleQueryController.java` 的 `/{queryId}/manual-nodes`;停止只由实际 `EMPTY_RETURNED` 节点触发,不提供无节点停止入口;外部船司/轨迹调用经 `integration-service/`;标准见 `../../docs/四期/船节点标准化契约.md` |
|
||||
|
||||
@@ -19,7 +19,7 @@ public interface LoadingCompletedEventMapper {
|
||||
COALESCE(o.root_inbound_id, o.id) AS root_inbound_id,
|
||||
o.order_id AS source_order_id, o.order_no AS source_order_no,
|
||||
COALESCE(root_order.order_id, o.order_id) AS root_order_id,
|
||||
o.inbound_code, o.business_type, o.destination_warehouse_id, o.destination_code AS destination_warehouse_code,
|
||||
o.inbound_code, o.business_type, o.destination_type, o.destination_warehouse_id, o.destination_code AS destination_warehouse_code,
|
||||
o.destination_name AS destination_warehouse_name,
|
||||
i.id AS loading_plan_item_id, i.inbound_detail_id AS source_inbound_detail_id, i.sku_code,
|
||||
i.product_name_zh, i.product_name_en, i.package_count, i.product_quantity,
|
||||
@@ -73,7 +73,7 @@ public interface LoadingCompletedEventMapper {
|
||||
private Integer productQuantity; private Integer palletCount; private BigDecimal grossWeightKg;
|
||||
private BigDecimal netWeightKg; private BigDecimal dimensionalWeightKg; private BigDecimal volumeCbm;
|
||||
private String fbaTrackingNo; private String shipmentNo; private String shippingMark;
|
||||
private String businessType;
|
||||
private String businessType; private String destinationType;
|
||||
private String customerRemark; private String internalRemark; private String warehouseRemark;
|
||||
}
|
||||
|
||||
|
||||
+14
-1
@@ -103,13 +103,15 @@ public class LoadingCompletedOutboxService {
|
||||
first.getDestinationWarehouseCode(), first.getDestinationWarehouseName(), sumInt(sorted, Row::getPackageCount),
|
||||
sumNullableInt(sorted, Row::getProductQuantity), "ONE_CLICK_DROPSHIP".equals(orderType)
|
||||
? pallets.size() : 0, sum(sorted, Row::getGrossWeightKg), sum(sorted, Row::getNetWeightKg),
|
||||
sum(sorted, Row::getVolumeCbm), items, string(first.getAccountId()), orderType, pallets);
|
||||
sum(sorted, Row::getVolumeCbm), items, string(first.getAccountId()), orderType, pallets,
|
||||
destinationType(sorted));
|
||||
}
|
||||
|
||||
/** 国内按实装箱数从可用标签池稳定随机抽取箱唛,重复发布同一计划时结果不变。 */
|
||||
private Map<Long, List<String>> cartonLabelPools(List<Row> rows) {
|
||||
Map<Long, List<String>> result = new java.util.LinkedHashMap<>();
|
||||
Map<LabelPoolKey, List<Row>> byInbound = rows.stream().filter(row -> !dropship(row.getBusinessType()))
|
||||
.filter(row -> "OVERSEAS".equals(destinationType(List.of(row))))
|
||||
.collect(java.util.stream.Collectors.groupingBy(
|
||||
row -> new LabelPoolKey(row.getAccountId(), row.getSourceInboundId()),
|
||||
LinkedHashMap::new, java.util.stream.Collectors.toList()));
|
||||
@@ -155,6 +157,17 @@ public class LoadingCompletedOutboxService {
|
||||
return result;
|
||||
}
|
||||
|
||||
private String destinationType(List<Row> rows) {
|
||||
Set<String> values = rows.stream().map(Row::getDestinationType)
|
||||
.map(value -> value == null ? "" : value.trim().toUpperCase(java.util.Locale.ROOT))
|
||||
.collect(java.util.stream.Collectors.toSet());
|
||||
if (values.size() != 1 || (!values.contains("FBA") && !values.contains("OVERSEAS"))) {
|
||||
throw new BizException(ResultCode.CONFLICT,
|
||||
"装柜入仓单目的类型必须唯一且为 FBA 或 OVERSEAS,不能生成海外交接事件");
|
||||
}
|
||||
return values.iterator().next();
|
||||
}
|
||||
|
||||
private record LabelPoolKey(long accountId, long inboundId) { }
|
||||
|
||||
private List<FirstLegLoadingPlanCompletedV2.PalletSnapshot> palletSnapshots(List<Row> values, List<PalletRow> source) {
|
||||
|
||||
+27
@@ -58,6 +58,7 @@ class LoadingCompletedOutboxServiceTest {
|
||||
assertThat(first.payload().at("/orders/0/items/0/sourceInboundDetailId").asText()).isEqualTo("101");
|
||||
assertThat(first.payload().at("/orders/0/items/0/skuCode").asText()).isEqualTo("SKU101");
|
||||
assertThat(first.payload().at("/orders/0/items/0/cartonMarks/0").asText()).isEqualTo("BOX-101");
|
||||
assertThat(first.payload().at("/orders/0/destinationType").asText()).isEqualTo("OVERSEAS");
|
||||
assertThat(rows.get(0).getEventKey()).isEqualTo("FIRSTLEG_LOADING_COMPLETED:90:11:2");
|
||||
assertThat(first.eventVersion()).isEqualTo(2);
|
||||
}
|
||||
@@ -71,6 +72,31 @@ class LoadingCompletedOutboxServiceTest {
|
||||
.hasMessageContaining("缺少 SKU");
|
||||
}
|
||||
|
||||
@Test void fbaFreezesPackageCountWithoutRequiringCartonLabels() throws Exception {
|
||||
Row value = row(11, 100, 101);
|
||||
value.setDestinationType("FBA");
|
||||
when(snapshots.rows(90L)).thenReturn(List.of(value));
|
||||
|
||||
service.publish(90L);
|
||||
|
||||
ArgumentCaptor<InboundOutboxEntity> captor = ArgumentCaptor.forClass(InboundOutboxEntity.class);
|
||||
verify(outbox).insert(captor.capture());
|
||||
EventEnvelope event = mapper.readValue(captor.getValue().getPayloadJson(), EventEnvelope.class);
|
||||
assertThat(event.payload().at("/orders/0/destinationType").asText()).isEqualTo("FBA");
|
||||
assertThat(event.payload().at("/orders/0/items/0/cartonMarks")).isEmpty();
|
||||
verify(manifests, org.mockito.Mockito.never()).labelPool(11L, 100L);
|
||||
}
|
||||
|
||||
@Test void rejectsUnfrozenDestinationType() {
|
||||
Row value = row(11, 100, 101);
|
||||
value.setDestinationType(null);
|
||||
when(snapshots.rows(90L)).thenReturn(List.of(value));
|
||||
|
||||
assertThatThrownBy(() -> service.publish(90L))
|
||||
.hasMessageContaining("目的类型必须唯一且为 FBA 或 OVERSEAS");
|
||||
verify(outbox, org.mockito.Mockito.never()).insert(org.mockito.ArgumentMatchers.any());
|
||||
}
|
||||
|
||||
@Test void freezesExactlyLoadedPackagesFromLabelPoolDeterministically() throws Exception {
|
||||
Row value = row(11, 100, 101);
|
||||
value.setPackageCount(2);
|
||||
@@ -148,6 +174,7 @@ class LoadingCompletedOutboxServiceTest {
|
||||
r.setSourceOrderId(inbound+1000); r.setSourceOrderNo("FL"+inbound); r.setRootOrderId(1099L);
|
||||
r.setInboundCode("RK"+inbound); r.setLoadingPlanItemId(detail); r.setDestinationWarehouseId(93L); r.setDestinationWarehouseCode("ONT8");
|
||||
r.setDestinationWarehouseName("ONT8"); r.setSourceInboundDetailId(detail); r.setSkuCode("SKU"+detail);
|
||||
r.setDestinationType("OVERSEAS");
|
||||
r.setPackageCount(1); r.setProductQuantity(10); r.setPalletCount(1); r.setGrossWeightKg(BigDecimal.ONE);
|
||||
r.setNetWeightKg(BigDecimal.ONE); r.setVolumeCbm(BigDecimal.ONE); return r;
|
||||
}
|
||||
|
||||
+2
-2
@@ -88,9 +88,9 @@ class TaillegRealProducerAcceptanceTest {
|
||||
|
||||
private void seedInbound(long id, long orderId, String code, String type, String status, String destination) {
|
||||
jdbc.update("INSERT IGNORE INTO fl_inbound_order(id,tenant_id,order_id,order_no,inbound_code,process_status," +
|
||||
"order_status_snapshot,customer_name,warehouse_id,warehouse_name,business_type,destination_warehouse_id," +
|
||||
"order_status_snapshot,customer_name,warehouse_id,warehouse_name,business_type,destination_type,destination_warehouse_id," +
|
||||
"destination_code,destination_name,package_count,actual_volume_cbm,created_by,updated_by) " +
|
||||
"VALUES(?,?,?,?,?,?, 'IN_TRANSIT',?,92,'真实验收海外仓',?,9001,?,?,?,0.4000,1,1)",
|
||||
"VALUES(?,?,?,?,?,?, 'IN_TRANSIT',?,92,'真实验收海外仓',?,'FBA',9001,?,?,?,0.4000,1,1)",
|
||||
id, CUSTOMER, orderId, MARKER + "-ORDER-" + id, code, status, "真实验收客户", type,
|
||||
destination, "FBA " + destination, type.equals("ONE_CLICK_DROPSHIP") ? 2 : 3);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user