diff --git a/.runtime-tailleg/DbQuery.class b/.runtime-tailleg/DbQuery.class new file mode 100644 index 000000000..1d188b78e Binary files /dev/null and b/.runtime-tailleg/DbQuery.class differ diff --git a/.runtime-tailleg/DbQuery.java b/.runtime-tailleg/DbQuery.java new file mode 100644 index 000000000..42d412c31 --- /dev/null +++ b/.runtime-tailleg/DbQuery.java @@ -0,0 +1,39 @@ +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.Statement; + +public final class DbQuery { + public static void main(String[] args) throws Exception { + if (args.length < 2) { + throw new IllegalArgumentException("usage: DbQuery "); + } + String url = "jdbc:mysql://127.0.0.1:3306/" + args[0] + + "?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai" + + "&useSSL=false&allowPublicKeyRetrieval=true"; + try (Connection connection = DriverManager.getConnection(url, "root", "123456"); + Statement statement = connection.createStatement()) { + boolean hasResult = statement.execute(args[1]); + if (!hasResult) { + System.out.println("updateCount=" + statement.getUpdateCount()); + return; + } + try (ResultSet result = statement.getResultSet()) { + ResultSetMetaData meta = result.getMetaData(); + for (int column = 1; column <= meta.getColumnCount(); column++) { + if (column > 1) System.out.print("\t"); + System.out.print(meta.getColumnLabel(column)); + } + System.out.println(); + while (result.next()) { + for (int column = 1; column <= meta.getColumnCount(); column++) { + if (column > 1) System.out.print("\t"); + System.out.print(String.valueOf(result.getObject(column))); + } + System.out.println(); + } + } + } + } +} diff --git a/deploy/sql/头程数据库统一增量-MySQL8.sql b/deploy/sql/头程数据库统一增量-MySQL8.sql index 04ffdcd30..2bdf80d11 100644 --- a/deploy/sql/头程数据库统一增量-MySQL8.sql +++ b/deploy/sql/头程数据库统一增量-MySQL8.sql @@ -16245,6 +16245,14 @@ SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE tab 'SELECT 1')); PREPARE lastleg_inventory_dimension_stmt FROM @ddl; EXECUTE lastleg_inventory_dimension_stmt; DEALLOCATE PREPARE lastleg_inventory_dimension_stmt; ALTER TABLE ll_inventory_balance COMMENT='客户入仓单与海外目的仓维度实时库存余额'; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() + AND table_name='ll_local_looseboard_order' AND column_name='lastleg_order_id'), 'SELECT 1', + 'ALTER TABLE ll_local_looseboard_order ADD COLUMN lastleg_order_id BIGINT NULL COMMENT ''关联尾程主订单ID'' AFTER customer_id')); +PREPARE lastleg_looseboard_tail_stmt FROM @ddl; EXECUTE lastleg_looseboard_tail_stmt; DEALLOCATE PREPARE lastleg_looseboard_tail_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() + AND table_name='ll_local_looseboard_order' AND index_name='uk_ll_local_looseboard_lastleg'), 'SELECT 1', + 'ALTER TABLE ll_local_looseboard_order ADD UNIQUE KEY uk_ll_local_looseboard_lastleg(lastleg_order_id)')); +PREPARE lastleg_looseboard_tail_stmt FROM @ddl; EXECUTE lastleg_looseboard_tail_stmt; DEALLOCATE PREPARE lastleg_looseboard_tail_stmt; CALL baiyue_firstleg.assert_unified_final_schema(); DROP PROCEDURE IF EXISTS baiyue_firstleg.assert_unified_final_schema; diff --git a/deploy/sql/海外区域统一增量-MySQL8.sql b/deploy/sql/海外区域统一增量-MySQL8.sql index 225fa8dc1..c0593faa6 100644 --- a/deploy/sql/海外区域统一增量-MySQL8.sql +++ b/deploy/sql/海外区域统一增量-MySQL8.sql @@ -152,7 +152,7 @@ CREATE TABLE IF NOT EXISTS ll_us_truck_order_status_history ( ) ENGINE=InnoDB COMMENT='美国卡车订单状态历史'; CREATE TABLE IF NOT EXISTS ll_local_looseboard_order ( - id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', order_no VARCHAR(64) NOT NULL COMMENT '本土散板订单号', + id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', lastleg_order_id BIGINT NULL COMMENT '关联尾程主订单ID', order_no VARCHAR(64) NOT NULL COMMENT '本土散板订单号', transport_mode VARCHAR(32) NOT NULL COMMENT '运输方式:拼车或优先拼载', pallet_required VARCHAR(8) NOT NULL COMMENT '是否需要打盘:YES或NO', transport_type VARCHAR(32) NOT NULL COMMENT '运输类型', origin VARCHAR(255) NOT NULL COMMENT '起运地', origin_address VARCHAR(500) NULL COMMENT '起运详细地址', destination VARCHAR(255) NOT NULL COMMENT '目的地', destination_address VARCHAR(500) NULL COMMENT '目的详细地址、码头或仓号', @@ -166,8 +166,17 @@ CREATE TABLE IF NOT EXISTS ll_local_looseboard_order ( created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间', PRIMARY KEY(id), UNIQUE KEY uk_ll_local_looseboard_no(customer_id,order_no), UNIQUE KEY uk_ll_local_looseboard_key(customer_id,idempotency_key), + UNIQUE KEY uk_ll_local_looseboard_lastleg(lastleg_order_id), KEY idx_ll_local_looseboard_list(customer_id,status,created_at), KEY idx_ll_local_looseboard_date(customer_id,requested_date,created_at) ) ENGINE=InnoDB COMMENT='本土散板订单'; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() + AND table_name='ll_local_looseboard_order' AND column_name='lastleg_order_id'), 'SELECT 1', + 'ALTER TABLE ll_local_looseboard_order ADD COLUMN lastleg_order_id BIGINT NULL COMMENT ''关联尾程主订单ID'' AFTER customer_id')); +PREPARE phase4_looseboard_tail_stmt FROM @ddl; EXECUTE phase4_looseboard_tail_stmt; DEALLOCATE PREPARE phase4_looseboard_tail_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() + AND table_name='ll_local_looseboard_order' AND index_name='uk_ll_local_looseboard_lastleg'), 'SELECT 1', + 'ALTER TABLE ll_local_looseboard_order ADD UNIQUE KEY uk_ll_local_looseboard_lastleg(lastleg_order_id)')); +PREPARE phase4_looseboard_tail_stmt FROM @ddl; EXECUTE phase4_looseboard_tail_stmt; DEALLOCATE PREPARE phase4_looseboard_tail_stmt; CREATE TABLE IF NOT EXISTS ll_local_looseboard_history ( id BIGINT NOT NULL COMMENT '主键ID', order_id BIGINT NOT NULL COMMENT '本土散板订单ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', diff --git a/firstleg/src/test/java/com/baiyue/firstleg/service/TaillegRealProducerAcceptanceTest.java b/firstleg/src/test/java/com/baiyue/firstleg/service/TaillegRealProducerAcceptanceTest.java new file mode 100644 index 000000000..6f49e1202 --- /dev/null +++ b/firstleg/src/test/java/com/baiyue/firstleg/service/TaillegRealProducerAcceptanceTest.java @@ -0,0 +1,117 @@ +package com.baiyue.firstleg.service; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfSystemProperty; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Import; +import org.springframework.jdbc.core.JdbcTemplate; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SpringBootTest(classes = TaillegRealProducerAcceptanceTest.AcceptanceApplication.class, + webEnvironment = SpringBootTest.WebEnvironment.NONE, properties = { + "spring.sql.init.mode=never", "spring.cloud.nacos.discovery.enabled=false", + "spring.cloud.nacos.discovery.register-enabled=false", "spring.cloud.nacos.config.enabled=false", + "spring.cloud.nacos.config.import-check.enabled=false", "spring.task.scheduling.enabled=false" + }) +@EnabledIfSystemProperty(named = "tailleg.real.producer", matches = "true") +class TaillegRealProducerAcceptanceTest { + static final long CUSTOMER = 990022L; + static final long BOOKING = 990022021L; + static final long PLAN = 990022022L; + static final long FIRST_INBOUND = 990022121L; + static final long DROPSHIP_INBOUND = 990022122L; + static final String MARKER = "REAL-TAIL-20260822-R3"; + + @Autowired JdbcTemplate jdbc; + @Autowired LoadingCompletedOutboxService outbox; + + @SpringBootConfiguration + @EnableAutoConfiguration(excludeName = "com.baiyue.common.security.BaiyueSecurityAutoConfiguration") + @Import(LoadingCompletedOutboxService.class) + @MapperScan("com.baiyue.firstleg.mapper") + static class AcceptanceApplication { } + + @Test + void completedLoadingPlanPublishesFirstLegAndDropshipOrders() { + String eventKey = "FIRSTLEG_LOADING_COMPLETED:" + PLAN + ":" + CUSTOMER + ":2"; + Integer existing = jdbc.queryForObject("SELECT COUNT(*) FROM fl_inbound_outbox WHERE event_key=?", + Integer.class, eventKey); + if (existing == 0) { + seedCompletedLoadingPlan(); + outbox.publish(PLAN); + } + assertEquals(1, jdbc.queryForObject("SELECT COUNT(*) FROM fl_inbound_outbox WHERE event_key=?", + Integer.class, eventKey)); + assertEquals(2, jdbc.queryForObject("SELECT JSON_LENGTH(JSON_EXTRACT(payload_json,'$.payload.orders')) " + + "FROM fl_inbound_outbox WHERE event_key=?", Integer.class, + eventKey)); + } + + private void seedCompletedLoadingPlan() { + jdbc.update("INSERT IGNORE INTO fl_container_booking(id,booking_no,container_no,cabinet_no,warehouse_id," + + "warehouse_name,container_type,created_by,updated_by) VALUES(?,?,?,?,?,?,?,1,1)", + BOOKING, MARKER + "-BOOK", MARKER + "-CNTR", MARKER + "-CAB", 92L, "真实验收海外仓", "40HQ"); + jdbc.update("INSERT IGNORE INTO fl_loading_plan(id,plan_no,container_booking_id,plan_status,created_by," + + "updated_by,version) VALUES(?,?,?,'LOADING',1,1,0)", PLAN, MARKER + "-PLAN", BOOKING); + seedInbound(FIRST_INBOUND, 990022221L, MARKER + "-FIRST-IN", "FBA_SEA", "LOADED", "ONT8"); + seedInbound(DROPSHIP_INBOUND, 990022222L, MARKER + "-DROP-IN", "ONE_CLICK_DROPSHIP", "LOADED", "LAX9"); + seedItem(990022321L, FIRST_INBOUND, 990022421L, 990022221L, MARKER + "-FIRST-IN", "SKU-REAL-A", 3, "ONT8"); + seedItem(990022322L, DROPSHIP_INBOUND, 990022422L, 990022222L, MARKER + "-DROP-IN", "SKU-REAL-D", 2, "LAX9"); + for (int i = 1; i <= 3; i++) seedLabel(990022540L + i, FIRST_INBOUND, 990022221L, 990022421L, + MARKER + "-FIRST-IN", "FIRST-BOX-" + i, i, 3); + for (int i = 1; i <= 2; i++) seedLabel(990022550L + i, DROPSHIP_INBOUND, 990022222L, 990022422L, + MARKER + "-DROP-IN", "DROP-BOX-" + i, i, 2); + jdbc.update("INSERT IGNORE INTO fl_inbound_pallet_allocation(id,tenant_id,inbound_id,allocation_no," + + "warehouse_id,warehouse_resource_tenant_id,pallet_id,pallet_code,pallet_capacity_cbm," + + "allocated_volume_cbm,allocated_package_count,picked_package_count,location_id,location_code," + + "reservation_no,allocation_status) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,'CONFIRMED')", + 990022621L, CUSTOMER, DROPSHIP_INBOUND, 1, 92L, 0L, 990022721L, MARKER + "-PALLET-D", + 1.2, 0.4, 2, 2, 990022821L, "A-01", MARKER + "-RES"); + jdbc.update("INSERT INTO fl_loading_pick_task(id,loading_plan_id,container_booking_id,cabinet_no," + + "container_no,task_status,started_by,started_by_name) VALUES(?,?,?,?,?,'LOADING',1,'真实验收')", + 990022901L, PLAN, BOOKING, MARKER + "-CAB", MARKER + "-CNTR"); + seedPickItem(990022911L, 990022321L, FIRST_INBOUND, 990022421L, 3); + seedPickItem(990022912L, 990022322L, DROPSHIP_INBOUND, 990022422L, 2); + jdbc.update("UPDATE fl_loading_plan SET plan_status='COMPLETED',confirmed_at=NOW(3),version=version+1 WHERE id=?", PLAN); + } + + private void seedPickItem(long id, long planItemId, long inboundId, long detailId, int packages) { + jdbc.update("INSERT INTO fl_loading_pick_item(id,pick_task_id,loading_plan_item_id,inbound_id," + + "inbound_detail_id,planned_package_count,picked_package_count,item_status) VALUES(?,?,?,?,?,?,?,'PICKED')", + id, 990022901L, planItemId, inboundId, detailId, packages, packages); + } + + 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," + + "destination_code,destination_name,package_count,actual_volume_cbm,created_by,updated_by) " + + "VALUES(?,?,?,?,?,?, 'IN_TRANSIT',?,92,'真实验收海外仓',?,9001,?,?,?,0.4000,1,1)", + id, CUSTOMER, orderId, MARKER + "-ORDER-" + id, code, status, "真实验收客户", type, + destination, "FBA " + destination, type.equals("ONE_CLICK_DROPSHIP") ? 2 : 3); + } + + private void seedItem(long id, long inboundId, long detailId, long orderId, String inboundCode, + String sku, int packages, String destination) { + jdbc.update("INSERT IGNORE INTO fl_loading_plan_item(id,loading_plan_id,account_id,customer_name,inbound_id," + + "inbound_detail_id,order_id,order_no,inbound_code,destination_warehouse_id,destination_warehouse_code," + + "destination_warehouse_name,sku_code,product_name_zh,product_name_en,package_count,product_quantity," + + "pallet_count,gross_weight_kg,net_weight_kg,dimensional_weight_kg,volume_cbm,shipment_no,shipping_mark," + + "created_by,updated_by) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,1,1)", + id, PLAN, CUSTOMER, "真实验收客户", inboundId, detailId, orderId, MARKER + "-ORDER-" + inboundId, + inboundCode, 9001L, destination, "FBA " + destination, sku, "真实验收商品", "Real product", + packages, packages * 10, 1, 30.0, 27.0, 35.0, 0.4, MARKER + "-SHIP", MARKER + "-MARK"); + } + + private void seedLabel(long id, long inboundId, long orderId, long detailId, String inboundCode, + String suffix, int index, int total) { + jdbc.update("INSERT IGNORE INTO fl_inbound_carton_label(id,tenant_id,inbound_id,order_id,inbound_detail_id," + + "cargo_item_no,cargo_name,detail_package_count,inbound_code,box_code,total_carton_count,carton_index," + + "box_specification) VALUES(?,?,?,?,?,1,'真实验收商品',?,?,?,?,?, '验收箱规')", + id, CUSTOMER, inboundId, orderId, detailId, total, inboundCode, MARKER + "-" + suffix, total, index); + } +} diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/dto/LastlegOrderSummary.java b/lastleg-service/src/main/java/com/baiyue/lastleg/dto/LastlegOrderSummary.java index f745d4506..bfe09c241 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/dto/LastlegOrderSummary.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/dto/LastlegOrderSummary.java @@ -10,6 +10,7 @@ public record LastlegOrderSummary( Long id, String orderNo, String orderType, + String orderTypeName, String sourceInboundId, String inboundCode, String sourceOrderNo, diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/entity/LastlegInboundOrder.java b/lastleg-service/src/main/java/com/baiyue/lastleg/entity/LastlegInboundOrder.java new file mode 100644 index 000000000..5c9927d88 --- /dev/null +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/entity/LastlegInboundOrder.java @@ -0,0 +1,43 @@ +package com.baiyue.lastleg.entity; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Getter; +import lombok.Setter; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Getter +@Setter +@TableName("ll_inbound_order") +public class LastlegInboundOrder { + @TableId private Long id; + private String tenantId; + private String customerId; + private String sourceRegion; + private String sourceInboundId; + private String rootInboundId; + private String sourceOrderId; + private String sourceOrderNo; + private String rootOrderId; + private String inboundCode; + private String destinationWarehouseId; + private String destinationWarehouseCode; + private String destinationWarehouseName; + private Integer packageCount; + private Integer productQuantity; + private Integer palletCount; + private BigDecimal grossWeightKg; + private BigDecimal netWeightKg; + private BigDecimal volumeCbm; + private String sourceEventId; + private String sourceLoadingPlanId; + private Long sourceLoadingPlanVersion; + private Long handoverVersion; + private String payloadHash; + private String status; + private String exceptionFlag; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; +} diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/entity/LastlegOrderDetail.java b/lastleg-service/src/main/java/com/baiyue/lastleg/entity/LastlegOrderDetail.java index 557f7d7df..b18b16cd1 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/entity/LastlegOrderDetail.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/entity/LastlegOrderDetail.java @@ -1,3 +1,12 @@ package com.baiyue.lastleg.entity; -import com.baomidou.mybatisplus.annotation.*; import lombok.Data; import java.math.BigDecimal; -@Data @TableName("ll_order_detail") public class LastlegOrderDetail { @TableId(type=IdType.AUTO) private Long id; private Long orderId; private String sourceInboundDetailId; private String skuCode; private String productNameZh; private String productNameEn; private Integer packageCount; private Integer productQuantity; private Integer palletCount; private BigDecimal grossWeightKg; private BigDecimal netWeightKg; private BigDecimal dimensionalWeightKg; private BigDecimal volumeCbm; private String destinationWarehouseId; private String destinationWarehouseCode; private String destinationWarehouseName; private String fbaTrackingNo; private String shipmentNo; private String shippingMark; } +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.math.BigDecimal; +@Data @TableName("ll_order_detail") public class LastlegOrderDetail { + @TableId(type=IdType.AUTO) private Long id; private String customerId; private Long orderId; + private String sourceInboundDetailId; private String skuCode; private String productNameZh; private String productNameEn; + private Integer packageCount; private Integer productQuantity; private Integer palletCount; private BigDecimal grossWeightKg; + private BigDecimal netWeightKg; private BigDecimal dimensionalWeightKg; private BigDecimal volumeCbm; + private String destinationWarehouseId; private String destinationWarehouseCode; private String destinationWarehouseName; + private String fbaTrackingNo; private String shipmentNo; private String shippingMark; +} diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/entity/LastlegOrderStatusHistory.java b/lastleg-service/src/main/java/com/baiyue/lastleg/entity/LastlegOrderStatusHistory.java index 4ca0f92a2..6577b61fa 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/entity/LastlegOrderStatusHistory.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/entity/LastlegOrderStatusHistory.java @@ -1,3 +1,6 @@ package com.baiyue.lastleg.entity; import com.baomidou.mybatisplus.annotation.*; import lombok.Data; import java.time.LocalDateTime; -@Data @TableName("ll_loading_order_status_history") public class LastlegOrderStatusHistory { @TableId(type=IdType.AUTO) private Long id; private Long orderId; private String fromStatus; private String toStatus; private String eventId; private String remark; private LocalDateTime createdAt; } +@Data @TableName("ll_loading_order_status_history") public class LastlegOrderStatusHistory { + @TableId(type=IdType.AUTO) private Long id; private String customerId; private Long orderId; + private String fromStatus; private String toStatus; private String eventId; private String remark; private LocalDateTime createdAt; +} diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/entity/InboundPlanLine.java b/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/entity/InboundPlanLine.java index 070d1805f..21026bf8d 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/entity/InboundPlanLine.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/entity/InboundPlanLine.java @@ -12,6 +12,7 @@ import java.math.BigDecimal; public class InboundPlanLine { @TableId(type = IdType.INPUT) private Long id; + private String customerId; private Long planId; private Long orderId; private String inboundCode; diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/entity/InboundPlanPallet.java b/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/entity/InboundPlanPallet.java index b998ed6fc..28eea73e4 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/entity/InboundPlanPallet.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/entity/InboundPlanPallet.java @@ -13,6 +13,7 @@ import java.time.LocalDateTime; public class InboundPlanPallet { @TableId(type = IdType.INPUT) private Long id; + private String customerId; private Long planId; private String palletNo; private String destinationWarehouseId; diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/entity/InboundPlanPalletAllocation.java b/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/entity/InboundPlanPalletAllocation.java index e716cbe98..a828873fe 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/entity/InboundPlanPalletAllocation.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/entity/InboundPlanPalletAllocation.java @@ -12,6 +12,7 @@ import java.math.BigDecimal; public class InboundPlanPalletAllocation { @TableId(type = IdType.INPUT) private Long id; + private String customerId; private Long planId; private Long lineId; private Long palletId; diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/mapper/InboundPlanOrderMapper.java b/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/mapper/InboundPlanOrderMapper.java index e1e02b377..56d3dda53 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/mapper/InboundPlanOrderMapper.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/mapper/InboundPlanOrderMapper.java @@ -43,8 +43,8 @@ public interface InboundPlanOrderMapper { @Param("planNo") String planNo, @Param("palletCount") int palletCount, @Param("updatedAt") LocalDateTime updatedAt); - @Insert("INSERT INTO ll_loading_order_status_history(order_id,from_status,to_status,remark,created_at) " + - "VALUES(#{orderId},#{fromStatus},'INBOUND_PLANNED',#{remark},#{createdAt})") - int insertHistory(@Param("orderId") Long orderId, @Param("fromStatus") String fromStatus, + @Insert("INSERT INTO ll_loading_order_status_history(customer_id,order_id,from_status,to_status,remark,created_at) " + + "VALUES(#{customerId},#{orderId},#{fromStatus},'INBOUND_PLANNED',#{remark},#{createdAt})") + int insertHistory(@Param("customerId") String customerId, @Param("orderId") Long orderId, @Param("fromStatus") String fromStatus, @Param("remark") String remark, @Param("createdAt") LocalDateTime createdAt); } diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/service/InboundPlanService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/service/InboundPlanService.java index ad16b8e77..8ec5e1d9d 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/service/InboundPlanService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/service/InboundPlanService.java @@ -108,7 +108,7 @@ public class InboundPlanService { if (orderMapper.markPlanned(customerId, order.orderId(), plan.getPlanNo(), count, now) != 1) { throw new IllegalStateException("Failed to update order " + order.orderId()); } - orderMapper.insertHistory(order.orderId(), order.currentStatus(), + orderMapper.insertHistory(customerId, order.orderId(), order.currentStatus(), "Inbound order converted to inbound plan " + plan.getPlanNo(), now); } return detail(customerId, plan); @@ -158,6 +158,7 @@ public class InboundPlanService { for (SourceOrderSnapshot order : orders) { InboundPlanLine line = new InboundPlanLine(); line.setId(IdWorker.getId()); + line.setCustomerId(order.customerId()); line.setPlanId(plan.getId()); line.setOrderId(order.orderId()); line.setInboundCode(order.inboundCode()); @@ -182,6 +183,7 @@ public class InboundPlanService { SourceOrderSnapshot sample = warehouseSamples.get(draft.warehouseKey()); InboundPlanPallet pallet = new InboundPlanPallet(); pallet.setId(IdWorker.getId()); + pallet.setCustomerId(plan.getCustomerId()); pallet.setPlanId(plan.getId()); pallet.setPalletNo(plan.getPlanNo() + "-P" + String.format("%03d", result.size() + 1)); pallet.setDestinationWarehouseId(sample.destinationWarehouseId()); @@ -205,6 +207,7 @@ public class InboundPlanService { for (PalletAllocator.AllocationDraft draft : allocation.allocations()) { InboundPlanPalletAllocation item = new InboundPlanPalletAllocation(); item.setId(IdWorker.getId()); + item.setCustomerId(plan.getCustomerId()); item.setPlanId(plan.getId()); item.setLineId(lineByOrder.get(draft.orderId()).getId()); item.setPalletId(pallets.get(draft.pallet()).getId()); diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/looseboard/entity/LocalLooseboardOrder.java b/lastleg-service/src/main/java/com/baiyue/lastleg/looseboard/entity/LocalLooseboardOrder.java index f8b383c09..1017aaf3d 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/looseboard/entity/LocalLooseboardOrder.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/looseboard/entity/LocalLooseboardOrder.java @@ -14,6 +14,7 @@ import java.time.LocalDateTime; public class LocalLooseboardOrder { @TableId(type = IdType.INPUT) private Long id; private String customerId; + private Long lastlegOrderId; private String orderNo; private String transportMode; private String palletRequired; diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/looseboard/service/LocalLooseboardService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/looseboard/service/LocalLooseboardService.java index e59dbbf6e..7775c196a 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/looseboard/service/LocalLooseboardService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/looseboard/service/LocalLooseboardService.java @@ -4,12 +4,17 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.IdWorker; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baiyue.common.core.api.PageResult; +import com.baiyue.lastleg.entity.LastlegOrder; +import com.baiyue.lastleg.entity.LastlegOrderStatusHistory; +import com.baiyue.lastleg.mapper.LastlegOrderMapper; +import com.baiyue.lastleg.mapper.LastlegOrderStatusHistoryMapper; import com.baiyue.lastleg.looseboard.dto.LocalLooseboardRequests; import com.baiyue.lastleg.looseboard.dto.LocalLooseboardResponses; import com.baiyue.lastleg.looseboard.entity.LocalLooseboardHistory; import com.baiyue.lastleg.looseboard.entity.LocalLooseboardOrder; import com.baiyue.lastleg.looseboard.mapper.LocalLooseboardHistoryMapper; import com.baiyue.lastleg.looseboard.mapper.LocalLooseboardOrderMapper; +import com.baiyue.lastleg.service.LastlegOrderNoGenerator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.annotation.Resource; @@ -46,6 +51,9 @@ public class LocalLooseboardService { @Resource private LocalLooseboardOrderMapper orders; @Resource private LocalLooseboardHistoryMapper histories; + @Resource private LastlegOrderMapper lastlegOrders; + @Resource private LastlegOrderStatusHistoryMapper lastlegHistories; + @Resource private LastlegOrderNoGenerator lastlegOrderNoGenerator; @Resource private ObjectMapper objectMapper; @Transactional @@ -71,6 +79,8 @@ public class LocalLooseboardService { order.setVersion(0L); order.setCreatedAt(now); order.setUpdatedAt(now); + LastlegOrder lastlegOrder = createLastlegOrder(order, requestHash, now); + order.setLastlegOrderId(lastlegOrder.getId()); try { orders.insert(order); } catch (DuplicateKeyException exception) { @@ -79,6 +89,8 @@ public class LocalLooseboardService { throw exception; } history(order, "CREATE", null, PENDING_ACCEPTANCE, operatorId, null, request.remark(), now); + lastlegHistory(customerId, lastlegOrder.getId(), null, lastlegOrder.getCurrentStatus(), + "Local looseboard order submitted", now); return detail(customerId, order.getOrderNo()); } @@ -147,6 +159,7 @@ public class LocalLooseboardService { order.setStatus(next); touch(order, now); orders.updateById(order); + syncLastlegOrder(order, next, now); history(order, "STATUS", from, next, operatorId, null, request.remark(), now); return response(order); } @@ -220,6 +233,58 @@ public class LocalLooseboardService { histories.insert(history); } + private LastlegOrder createLastlegOrder(LocalLooseboardOrder source, String requestHash, LocalDateTime now) { + LastlegOrder order = new LastlegOrder(); + order.setId(IdWorker.getId()); + order.setOrderNo(lastlegOrderNoGenerator.next()); + order.setCustomerId(source.getCustomerId()); + order.setOrderType("LOCAL_LOOSEBOARD"); + order.setInboundCode(source.getOrderNo()); + order.setSourceRegion("OVERSEAS_LOCAL"); + order.setSourceInboundId("LOCAL_LOOSEBOARD:" + source.getId()); + order.setSourceOrderId(String.valueOf(source.getId())); + order.setSourceOrderNo(source.getOrderNo()); + order.setPalletCount(0); + order.setPackageCount(Objects.requireNonNullElse(source.getPackageCount(), 0)); + order.setPickedPackageCount(0); + order.setShippedPackageCount(0); + order.setVolumeCbm(source.getTotalVolumeCbm()); + order.setCurrentStatus("PENDING_ASSIGNMENT"); + order.setSubOrderStatus("LOCAL_LOOSEBOARD:" + PENDING_ACCEPTANCE); + order.setCustomerRemark(source.getRemark()); + order.setSourceEventId("LOCAL_LOOSEBOARD:" + source.getId()); + order.setSourceLoadingPlanId("LOCAL_LOOSEBOARD"); + order.setSourcePayloadHash("sha256:" + requestHash); + order.setCreatedAt(now); + order.setUpdatedAt(now); + lastlegOrders.insert(order); + return order; + } + + private void syncLastlegOrder(LocalLooseboardOrder source, String status, LocalDateTime now) { + if (source.getLastlegOrderId() == null) return; + LastlegOrder order = lastlegOrders.selectById(source.getLastlegOrderId()); + if (order == null || !source.getCustomerId().equals(order.getCustomerId())) { + throw conflict("Linked tail-leg order is missing"); + } + order.setSubOrderStatus("LOCAL_LOOSEBOARD:" + status); + order.setUpdatedAt(now); + lastlegOrders.updateById(order); + } + + private void lastlegHistory(String customerId, Long orderId, String from, String to, String remark, + LocalDateTime now) { + LastlegOrderStatusHistory history = new LastlegOrderStatusHistory(); + history.setCustomerId(customerId); + history.setOrderId(orderId); + history.setFromStatus(from); + history.setToStatus(to); + history.setEventId("LOCAL_LOOSEBOARD"); + history.setRemark(remark); + history.setCreatedAt(now); + lastlegHistories.insert(history); + } + private void validateQuery(LocalLooseboardRequests.Query query) { if (StringUtils.hasText(query.status()) && !STATUSES.contains(norm(query.status()))) { throw badRequest("Unsupported looseboard order status"); diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/mapper/LastlegInboundOrderMapper.java b/lastleg-service/src/main/java/com/baiyue/lastleg/mapper/LastlegInboundOrderMapper.java new file mode 100644 index 000000000..40b003ea3 --- /dev/null +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/mapper/LastlegInboundOrderMapper.java @@ -0,0 +1,9 @@ +package com.baiyue.lastleg.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baiyue.lastleg.entity.LastlegInboundOrder; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface LastlegInboundOrderMapper extends BaseMapper { +} diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/model/LastlegOrderType.java b/lastleg-service/src/main/java/com/baiyue/lastleg/model/LastlegOrderType.java index 8a9d7ae15..4036c2b43 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/model/LastlegOrderType.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/model/LastlegOrderType.java @@ -24,4 +24,13 @@ public enum LastlegOrderType { public String label() { return label; } + + public static String labelOf(String code) { + if (code == null || code.isBlank()) return code; + try { + return valueOf(code.trim()).label; + } catch (IllegalArgumentException exception) { + return code; + } + } } diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/relabel/service/RelabelApplicationService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/relabel/service/RelabelApplicationService.java index 10846b8a8..c9bce8008 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/relabel/service/RelabelApplicationService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/relabel/service/RelabelApplicationService.java @@ -118,7 +118,8 @@ public class RelabelApplicationService { for (RelabelRequests.Item source : request.items()) insertItem(customerId, applicationId, source, now); insertHistory(customerId, applicationId, null, PENDING_PROCESSING, operatorId, "Relabel application submitted", now); - insertOrderHistory(order.getId(), null, order.getCurrentStatus(), "Relabel application submitted", now); + insertOrderHistory(customerId, order.getId(), null, order.getCurrentStatus(), + "Relabel application submitted", now); return detail(customerId, relabelNo); } @@ -194,7 +195,7 @@ public class RelabelApplicationService { orders.updateById(order); insertHistory(customerId, application.getId(), before, next, operatorId, request.remark(), now); if (!Objects.equals(orderBefore, orderStatus)) { - insertOrderHistory(order.getId(), orderBefore, orderStatus, request.remark(), now); + insertOrderHistory(customerId, order.getId(), orderBefore, orderStatus, request.remark(), now); } return detail(customerId, relabelNo); } @@ -248,8 +249,9 @@ public class RelabelApplicationService { value.setApplicationId(id); value.setFromStatus(from); value.setToStatus(to); value.setOperatorId(trim(operator)); value.setRemark(trim(remark)); value.setCreatedAt(now); histories.insert(value); } - private void insertOrderHistory(Long id, String from, String to, String remark, LocalDateTime now) { - LastlegOrderStatusHistory value = new LastlegOrderStatusHistory(); value.setOrderId(id); value.setFromStatus(from); + private void insertOrderHistory(String customerId, Long id, String from, String to, String remark, + LocalDateTime now) { + LastlegOrderStatusHistory value = new LastlegOrderStatusHistory(); value.setCustomerId(customerId); value.setOrderId(id); value.setFromStatus(from); value.setToStatus(to); value.setEventId("RELABEL_APPLICATION"); value.setRemark(trim(remark)); value.setCreatedAt(now); orderHistories.insert(value); } diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/service/LastlegOrderService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/service/LastlegOrderService.java index 3ca6411e6..2d144ffc4 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/service/LastlegOrderService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/service/LastlegOrderService.java @@ -12,6 +12,7 @@ import com.baiyue.lastleg.entity.LastlegOrderStatusHistory; import com.baiyue.lastleg.mapper.LastlegOrderDetailMapper; import com.baiyue.lastleg.mapper.LastlegOrderMapper; import com.baiyue.lastleg.mapper.LastlegOrderStatusHistoryMapper; +import com.baiyue.lastleg.model.LastlegOrderType; import jakarta.annotation.Resource; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; @@ -106,7 +107,8 @@ public class LastlegOrderService { lines.add("ID,尾程单号,订单类型,入仓码,头程单号,货主,海外仓,转仓,转仓编码,FBA仓库,集装箱号,货柜状态,是否异常,是否暂扣,托数,箱数,体积(m3),数量,体积重量,预计到港时间,实际到港时间,预计到仓,入库单号,换标单号,换标状态,合托标记,主流程状态,子单状态,备注(仅自己),备注(客户),备注(仓库)"); for (LastlegOrder order : orders) { lines.add(String.join(",", List.of( - csv(order.getId()), csv(order.getOrderNo()), csv(order.getOrderType()), csv(order.getInboundCode()), + csv(order.getId()), csv(order.getOrderNo()), csv(LastlegOrderType.labelOf(order.getOrderType())), + csv(order.getInboundCode()), csv(order.getSourceOrderNo()), csv(order.getCustomerName()), csv(order.getOverseasWarehouseName()), csv(order.getTransferWarehouseName()), csv(order.getTransferWarehouseCode()), csv(order.getFbaWarehouseCode()), csv(order.getContainerNo()), csv(order.getContainerStatus()), csv(order.getAbnormal()), csv(order.getDetained()), @@ -161,7 +163,8 @@ public class LastlegOrderService { } private LastlegOrderSummary summary(LastlegOrder o) { - return new LastlegOrderSummary(o.getId(), o.getOrderNo(), o.getOrderType(), o.getSourceInboundId(), + return new LastlegOrderSummary(o.getId(), o.getOrderNo(), o.getOrderType(), + LastlegOrderType.labelOf(o.getOrderType()), o.getSourceInboundId(), o.getInboundCode(), o.getSourceOrderNo(), o.getCustomerId(), o.getCustomerName(), o.getOverseasWarehouseId(), o.getOverseasWarehouseName(), o.getTransferWarehouseId(), o.getTransferWarehouseCode(), o.getTransferWarehouseName(), o.getFbaWarehouseCode(), o.getContainerNo(), o.getContainerStatus(), diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/service/LoadingCompletedConsumer.java b/lastleg-service/src/main/java/com/baiyue/lastleg/service/LoadingCompletedConsumer.java index 4b0080e36..e7407e43b 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/service/LoadingCompletedConsumer.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/service/LoadingCompletedConsumer.java @@ -31,6 +31,7 @@ import com.fasterxml.jackson.databind.JsonNode; public class LoadingCompletedConsumer { @Resource private LastlegEventInboxMapper inboxMapper; @Resource private LastlegOrderMapper orderMapper; + @Resource private LastlegInboundOrderMapper inboundOrderMapper; @Resource private LastlegOrderDetailMapper detailMapper; @Resource private LastlegOrderStatusHistoryMapper historyMapper; @Resource private InboundPlanCartonMapper cartonMapper; @@ -173,9 +174,11 @@ public class LoadingCompletedConsumer { if (raced != null && Objects.equals(raced.getSourcePayloadHash(), orderHash)) return; throw exception; } + createInboundOrderSnapshot(event, payload, source, order, orderHash); for(var item:source.items()) { LastlegOrderDetail detail=new LastlegOrderDetail(); + detail.setCustomerId(customerId); detail.setOrderId(order.getId()); detail.setSourceInboundDetailId(item.sourceInboundDetailId()); detail.setSkuCode(item.skuCode()); detail.setProductNameZh(item.productNameZh()); detail.setProductNameEn(item.productNameEn()); detail.setPackageCount(item.packageCount()); detail.setProductQuantity(item.productQuantity()); detail.setPalletCount(item.palletCount()); detail.setGrossWeightKg(item.grossWeightKg()); @@ -185,7 +188,7 @@ public class LoadingCompletedConsumer { detail.setShipmentNo(item.shipmentNo()); detail.setShippingMark(item.shippingMark()); detailMapper.insert(detail); createCartonLabels(customerId, order.getId(), item); } - LastlegOrderStatusHistory history=new LastlegOrderStatusHistory(); + LastlegOrderStatusHistory history=new LastlegOrderStatusHistory(); history.setCustomerId(customerId); history.setOrderId(order.getId()); history.setToStatus("PENDING_ASSIGNMENT"); history.setEventId(event.eventId()); history.setRemark("头程装柜完成自动生成"); history.setCreatedAt(LocalDateTime.now()); historyMapper.insert(history); if ("FIRST_LEG".equals(orderType)) { @@ -201,6 +204,28 @@ public class LoadingCompletedConsumer { } } + private void createInboundOrderSnapshot(EventEnvelope event, FirstLegLoadingPlanCompletedV1 payload, + FirstLegLoadingPlanCompletedV1.Order source, LastlegOrder order, + String orderHash) { + LastlegInboundOrder snapshot = new LastlegInboundOrder(); + snapshot.setId(order.getId()); snapshot.setTenantId("BAIYUE"); + snapshot.setCustomerId(order.getCustomerId()); snapshot.setSourceRegion(event.sourceRegion().name()); + snapshot.setSourceInboundId(source.sourceInboundId()); snapshot.setRootInboundId(source.rootInboundId()); + snapshot.setSourceOrderId(source.sourceOrderId()); snapshot.setSourceOrderNo(source.sourceOrderNo()); + snapshot.setRootOrderId(source.rootOrderId()); snapshot.setInboundCode(source.inboundCode()); + snapshot.setDestinationWarehouseId(source.destinationWarehouseId()); + snapshot.setDestinationWarehouseCode(source.destinationWarehouseCode()); + snapshot.setDestinationWarehouseName(source.destinationWarehouseName()); + snapshot.setPackageCount(source.packageCount()); snapshot.setProductQuantity(source.productQuantity()); + snapshot.setPalletCount(source.palletCount()); snapshot.setGrossWeightKg(source.grossWeightKg()); + snapshot.setNetWeightKg(source.netWeightKg()); snapshot.setVolumeCbm(source.volumeCbm()); + snapshot.setSourceEventId(event.eventId()); snapshot.setSourceLoadingPlanId(payload.loadingPlanId()); + snapshot.setSourceLoadingPlanVersion(event.aggregateVersion()); snapshot.setHandoverVersion(event.handoverVersion()); + snapshot.setPayloadHash(orderHash); snapshot.setStatus("READY"); snapshot.setExceptionFlag("NONE"); + snapshot.setCreatedAt(order.getCreatedAt()); snapshot.setUpdatedAt(order.getUpdatedAt()); + inboundOrderMapper.insert(snapshot); + } + private void registerInTransit(EventEnvelope event, FirstLegLoadingPlanCompletedV1 payload, FirstLegLoadingPlanCompletedV1.Order source, LastlegOrder order, String inboundOrderNo) { diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/transfer/service/WarehouseTransferService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/transfer/service/WarehouseTransferService.java index 65f6e37ee..ddafeed42 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/transfer/service/WarehouseTransferService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/transfer/service/WarehouseTransferService.java @@ -152,9 +152,9 @@ public class WarehouseTransferService { for (int index = 0; index < request.items().size(); index++) { WarehouseTransferRequests.Item source = request.items().get(index); insertTransferItem(customerId, transferId, source, now); - insertOrderDetail(order.getId(), transferNo, index, source, request, now); + insertOrderDetail(customerId, order.getId(), transferNo, index, source, request, now); } - insertOrderHistory(order.getId(), null, PENDING_REVIEW, transferNo, + insertOrderHistory(customerId, order.getId(), null, PENDING_REVIEW, transferNo, "Warehouse transfer submitted for review", now); if (request.needRelabel()) createRelabel(customerId, operatorId, transfer, order, request, now); return detail(customerId, transferNo); @@ -258,7 +258,7 @@ public class WarehouseTransferService { order.setSubOrderStatus("WAREHOUSE_TRANSFER:" + next); order.setUpdatedAt(now); orders.updateById(order); if (!java.util.Objects.equals(before, orderStatus)) { - insertOrderHistory(order.getId(), before, orderStatus, transferNo, + insertOrderHistory(customerId, order.getId(), before, orderStatus, transferNo, "Warehouse transfer reviewed: " + next, now); } return detail(customerId, transferNo); @@ -337,7 +337,7 @@ public class WarehouseTransferService { String before = order.getCurrentStatus(); order.setCurrentStatus("WAIT_OUTBOUND"); order.setSubOrderStatus("WAREHOUSE_TRANSFER:ALLOCATED"); order.setUpdatedAt(now); orders.updateById(order); - insertOrderHistory(order.getId(), before, order.getCurrentStatus(), transferNo, + insertOrderHistory(customerId, order.getId(), before, order.getCurrentStatus(), transferNo, "Warehouse transfer inventory allocated", now); return detail(customerId, transferNo); } @@ -388,7 +388,7 @@ public class WarehouseTransferService { String before = order.getCurrentStatus(); order.setCurrentStatus("IN_TRANSIT"); order.setSubOrderStatus("WAREHOUSE_TRANSFER:OUTBOUNDED"); order.setUpdatedAt(now); orders.updateById(order); - insertOrderHistory(order.getId(), before, order.getCurrentStatus(), transferNo, + insertOrderHistory(customerId, order.getId(), before, order.getCurrentStatus(), transferNo, "Warehouse transfer left source warehouse", now); return detail(customerId, transferNo); } @@ -455,7 +455,7 @@ public class WarehouseTransferService { String before = order.getCurrentStatus(); order.setCurrentStatus(complete ? "COMPLETED" : "IN_TRANSIT"); order.setSubOrderStatus("WAREHOUSE_TRANSFER:" + (complete ? RECEIVED : PARTIALLY_RECEIVED)); order.setUpdatedAt(now); orders.updateById(order); - insertOrderHistory(order.getId(), before, order.getCurrentStatus(), transferNo, + insertOrderHistory(customerId, order.getId(), before, order.getCurrentStatus(), transferNo, "Warehouse transfer inventory received at destination", now); return detail(customerId, transferNo); } @@ -490,9 +490,10 @@ public class WarehouseTransferService { transferItems.insert(item); } - private void insertOrderDetail(long orderId, String transferNo, int index, WarehouseTransferRequests.Item source, + private void insertOrderDetail(String customerId, long orderId, String transferNo, int index, WarehouseTransferRequests.Item source, WarehouseTransferRequests.Create request, LocalDateTime now) { LastlegOrderDetail detail = new LastlegOrderDetail(); + detail.setCustomerId(customerId); detail.setOrderId(orderId); detail.setSourceInboundDetailId(transferNo + ":" + index); detail.setSkuCode(source.skuCode().trim()); detail.setProductNameZh(source.productNameZh().trim()); detail.setPackageCount(source.packageCount()); detail.setDestinationWarehouseCode(request.fbaWarehouseCode().trim()); @@ -537,9 +538,10 @@ public class WarehouseTransferService { order.setUpdatedAt(now); orders.updateById(order); } - private void insertOrderHistory(long orderId, String fromStatus, String toStatus, String transferNo, - String remark, LocalDateTime now) { + private void insertOrderHistory(String customerId, long orderId, String fromStatus, String toStatus, + String transferNo, String remark, LocalDateTime now) { LastlegOrderStatusHistory history = new LastlegOrderStatusHistory(); + history.setCustomerId(customerId); history.setOrderId(orderId); history.setFromStatus(fromStatus); history.setToStatus(toStatus); history.setEventId("WAREHOUSE_TRANSFER:" + transferNo); history.setRemark(remark); history.setCreatedAt(now); orderHistories.insert(history); diff --git a/lastleg-service/src/main/resources/db/lastleg.sql b/lastleg-service/src/main/resources/db/lastleg.sql index 293421ede..941b7842d 100644 --- a/lastleg-service/src/main/resources/db/lastleg.sql +++ b/lastleg-service/src/main/resources/db/lastleg.sql @@ -1304,6 +1304,7 @@ CREATE TABLE IF NOT EXISTS ll_us_truck_order_status_history ( CREATE TABLE IF NOT EXISTS ll_local_looseboard_order ( id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', + lastleg_order_id BIGINT NULL COMMENT '关联尾程主订单ID', order_no VARCHAR(64) NOT NULL COMMENT '本土散板订单号', transport_mode VARCHAR(32) NOT NULL COMMENT '运输方式:拼车或优先拼载', pallet_required VARCHAR(8) NOT NULL COMMENT '是否需要打盘:YES或NO', transport_type VARCHAR(32) NOT NULL COMMENT '运输类型', origin VARCHAR(255) NOT NULL COMMENT '起运地', origin_address VARCHAR(500) NULL COMMENT '起运详细地址', @@ -1319,6 +1320,7 @@ CREATE TABLE IF NOT EXISTS ll_local_looseboard_order ( version BIGINT NOT NULL DEFAULT 0 COMMENT '乐观锁版本', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间', PRIMARY KEY(id), UNIQUE KEY uk_ll_local_looseboard_no(customer_id,order_no), + UNIQUE KEY uk_ll_local_looseboard_lastleg(lastleg_order_id), UNIQUE KEY uk_ll_local_looseboard_key(customer_id,idempotency_key), KEY idx_ll_local_looseboard_list(customer_id,status,created_at), KEY idx_ll_local_looseboard_date(customer_id,requested_date,created_at) diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/service/InboundPlanServiceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/service/InboundPlanServiceTest.java index 471884835..7dd827a96 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/service/InboundPlanServiceTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/service/InboundPlanServiceTest.java @@ -89,7 +89,7 @@ class InboundPlanServiceTest { assertEquals(1, allocations.getAllValues().stream() .filter(item -> "PENDING_COUNT".equals(item.getStatus())).count()); verify(orderMapper).markPlanned(any(), org.mockito.ArgumentMatchers.eq(2L), any(), org.mockito.ArgumentMatchers.anyInt(), any()); - verify(orderMapper).insertHistory(org.mockito.ArgumentMatchers.eq(2L), any(), any(), any()); + verify(orderMapper).insertHistory(any(), org.mockito.ArgumentMatchers.eq(2L), any(), any(), any()); verify(orderMapper).lockOrder("100", 2L); assertEquals(2L, result.sourceOrderId()); assertEquals("CONT-1", result.containerNo()); diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/looseboard/LocalLooseboardSchemaContractTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/looseboard/LocalLooseboardSchemaContractTest.java index 341cbe27c..8aef0fe85 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/looseboard/LocalLooseboardSchemaContractTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/looseboard/LocalLooseboardSchemaContractTest.java @@ -17,5 +17,7 @@ class LocalLooseboardSchemaContractTest { assertTrue(sql.contains("CREATE TABLE IF NOT EXISTS ll_local_looseboard_history")); assertTrue(sql.contains("operation_type VARCHAR(32) NOT NULL")); assertTrue(sql.contains("uk_ll_local_looseboard_no(customer_id,order_no)")); + assertTrue(sql.contains("lastleg_order_id BIGINT NULL")); + assertTrue(sql.contains("uk_ll_local_looseboard_lastleg(lastleg_order_id)")); } } diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/looseboard/service/LocalLooseboardServiceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/looseboard/service/LocalLooseboardServiceTest.java index 575f0a6e8..c3ab0e0b5 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/looseboard/service/LocalLooseboardServiceTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/looseboard/service/LocalLooseboardServiceTest.java @@ -5,6 +5,10 @@ import com.baiyue.lastleg.looseboard.entity.LocalLooseboardHistory; import com.baiyue.lastleg.looseboard.entity.LocalLooseboardOrder; import com.baiyue.lastleg.looseboard.mapper.LocalLooseboardHistoryMapper; import com.baiyue.lastleg.looseboard.mapper.LocalLooseboardOrderMapper; +import com.baiyue.lastleg.entity.LastlegOrder; +import com.baiyue.lastleg.mapper.LastlegOrderMapper; +import com.baiyue.lastleg.mapper.LastlegOrderStatusHistoryMapper; +import com.baiyue.lastleg.service.LastlegOrderNoGenerator; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -31,6 +35,9 @@ import static org.mockito.Mockito.when; class LocalLooseboardServiceTest { @Mock LocalLooseboardOrderMapper orders; @Mock LocalLooseboardHistoryMapper histories; + @Mock LastlegOrderMapper lastlegOrders; + @Mock LastlegOrderStatusHistoryMapper lastlegHistories; + @Mock LastlegOrderNoGenerator lastlegOrderNoGenerator; private LocalLooseboardService service; @BeforeEach @@ -38,7 +45,11 @@ class LocalLooseboardServiceTest { service = new LocalLooseboardService(); ReflectionTestUtils.setField(service, "orders", orders); ReflectionTestUtils.setField(service, "histories", histories); + ReflectionTestUtils.setField(service, "lastlegOrders", lastlegOrders); + ReflectionTestUtils.setField(service, "lastlegHistories", lastlegHistories); + ReflectionTestUtils.setField(service, "lastlegOrderNoGenerator", lastlegOrderNoGenerator); ReflectionTestUtils.setField(service, "objectMapper", new ObjectMapper().findAndRegisterModules()); + org.mockito.Mockito.lenient().when(lastlegOrderNoGenerator.next()).thenReturn("LMO-LOOSE-1"); } @Test @@ -56,6 +67,9 @@ class LocalLooseboardServiceTest { assertEquals("100", detail.summary().customerId()); assertEquals(LocalLooseboardService.PENDING_ACCEPTANCE, detail.summary().status()); assertEquals("SHARED_TRUCK", detail.summary().transportMode()); + verify(lastlegOrders).insert(org.mockito.ArgumentMatchers.argThat(value -> + "LOCAL_LOOSEBOARD".equals(value.getOrderType()) + && "LOCAL_LOOSEBOARD:PENDING_ACCEPTANCE".equals(value.getSubOrderStatus()))); verify(histories).insert(org.mockito.ArgumentMatchers.argThat(value -> "CREATE".equals(value.getOperationType()))); } diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/model/LastlegOrderTypeTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/model/LastlegOrderTypeTest.java index daffc9182..9bc6cb12f 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/model/LastlegOrderTypeTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/model/LastlegOrderTypeTest.java @@ -15,5 +15,7 @@ class LastlegOrderTypeTest { Arrays.stream(LastlegOrderType.values()).map(Enum::name).toList()); assertEquals("换标下单", LastlegOrderType.RELABEL_APPLICATION.label()); assertEquals("本土散板", LastlegOrderType.LOCAL_LOOSEBOARD.label()); + assertEquals("一件代发", LastlegOrderType.labelOf("ONE_CLICK_DROPSHIP")); + assertEquals("LEGACY_TYPE", LastlegOrderType.labelOf("LEGACY_TYPE")); } } diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/service/LastlegOrderServiceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/service/LastlegOrderServiceTest.java index 6d691750a..5c71ee8db 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/service/LastlegOrderServiceTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/service/LastlegOrderServiceTest.java @@ -53,6 +53,8 @@ class LastlegOrderServiceTest { assertEquals(11L, response.order().id()); assertEquals("100", response.order().customerId()); + assertEquals("ONE_CLICK_DROPSHIP", response.order().orderType()); + assertEquals("一件代发", response.order().orderTypeName()); assertTrue(java.util.Arrays.stream(response.getClass().getRecordComponents()) .noneMatch(component -> "dropshipPallets".equals(component.getName()))); assertTrue(java.util.Arrays.stream(com.baiyue.lastleg.dto.LastlegOrderDetailResponse.History.class.getRecordComponents()) diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/service/LoadingCompletedConsumerTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/service/LoadingCompletedConsumerTest.java index 47357a0b1..34dae31ba 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/service/LoadingCompletedConsumerTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/service/LoadingCompletedConsumerTest.java @@ -2,8 +2,8 @@ package com.baiyue.lastleg.service; import com.baiyue.common.event.*; import com.baiyue.lastleg.entity.*; import com.baiyue.lastleg.mapper.*; import com.baiyue.lastleg.dropship.dto.DropshipRequests; import com.baiyue.lastleg.dropship.service.DropshipOrderService; import com.baiyue.lastleg.inboundplan.dto.InboundPlanResponse; import com.baiyue.lastleg.inboundplan.mapper.InboundPlanCartonMapper; import com.baiyue.lastleg.inboundplan.service.InboundPlanService; import com.baiyue.lastleg.inventory.command.dto.InventoryCommandRequests; import com.baiyue.lastleg.inventory.command.service.InventoryCommandService; import com.fasterxml.jackson.databind.*; import org.junit.jupiter.api.*; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.*; import org.springframework.test.util.ReflectionTestUtils; import java.math.BigDecimal; import java.time.Instant; import java.util.*; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; @ExtendWith(org.mockito.junit.jupiter.MockitoExtension.class) class LoadingCompletedConsumerTest { - @Mock LastlegEventInboxMapper inbox; @Mock LastlegOrderMapper orders; @Mock LastlegOrderDetailMapper details; @Mock LastlegOrderStatusHistoryMapper histories; @Mock InboundPlanCartonMapper cartons; @Mock DropshipOrderService dropshipOrders; @Mock InboundPlanService inboundPlans; @Mock InventoryCommandService inventory; ObjectMapper json=new ObjectMapper().findAndRegisterModules(); LoadingCompletedConsumer service; - @BeforeEach void setup(){ service=new LoadingCompletedConsumer(); ReflectionTestUtils.setField(service,"inboxMapper",inbox); ReflectionTestUtils.setField(service,"orderMapper",orders); ReflectionTestUtils.setField(service,"detailMapper",details); ReflectionTestUtils.setField(service,"historyMapper",histories); ReflectionTestUtils.setField(service,"cartonMapper",cartons); ReflectionTestUtils.setField(service,"dropshipOrders",dropshipOrders); ReflectionTestUtils.setField(service,"inboundPlans",inboundPlans); ReflectionTestUtils.setField(service,"inventory",inventory); ReflectionTestUtils.setField(service,"objectMapper",json); ReflectionTestUtils.setField(service,"orderNoGenerator",new LastlegOrderNoGenerator()); lenient().doAnswer(i->{ LastlegOrder o=i.getArgument(0); o.setId(Math.abs(o.getSourceInboundId().hashCode())+1L); return 1; }).when(orders).insert(any(LastlegOrder.class)); lenient().when(inboundPlans.convert(anyString(),anyLong())).thenReturn(new InboundPlanResponse(1L,"LIP-1","100",1L,"MSCU1","PENDING_ASSIGNMENT",1,1,BigDecimal.ONE,List.of(),List.of(),List.of())); } + @Mock LastlegEventInboxMapper inbox; @Mock LastlegOrderMapper orders; @Mock LastlegInboundOrderMapper inboundOrders; @Mock LastlegOrderDetailMapper details; @Mock LastlegOrderStatusHistoryMapper histories; @Mock InboundPlanCartonMapper cartons; @Mock DropshipOrderService dropshipOrders; @Mock InboundPlanService inboundPlans; @Mock InventoryCommandService inventory; ObjectMapper json=new ObjectMapper().findAndRegisterModules(); LoadingCompletedConsumer service; + @BeforeEach void setup(){ service=new LoadingCompletedConsumer(); ReflectionTestUtils.setField(service,"inboxMapper",inbox); ReflectionTestUtils.setField(service,"orderMapper",orders); ReflectionTestUtils.setField(service,"inboundOrderMapper",inboundOrders); ReflectionTestUtils.setField(service,"detailMapper",details); ReflectionTestUtils.setField(service,"historyMapper",histories); ReflectionTestUtils.setField(service,"cartonMapper",cartons); ReflectionTestUtils.setField(service,"dropshipOrders",dropshipOrders); ReflectionTestUtils.setField(service,"inboundPlans",inboundPlans); ReflectionTestUtils.setField(service,"inventory",inventory); ReflectionTestUtils.setField(service,"objectMapper",json); ReflectionTestUtils.setField(service,"orderNoGenerator",new LastlegOrderNoGenerator()); lenient().doAnswer(i->{ LastlegOrder o=i.getArgument(0); o.setId(Math.abs(o.getSourceInboundId().hashCode())+1L); return 1; }).when(orders).insert(any(LastlegOrder.class)); lenient().when(inboundPlans.convert(anyString(),anyLong())).thenReturn(new InboundPlanResponse(1L,"LIP-1","100",1L,"MSCU1","PENDING_ASSIGNMENT",1,1,BigDecimal.ONE,List.of(),List.of(),List.of())); } @Test void createsOneOrderAndOneInboundPlanPerInbound(){ EventEnvelope event=event(); assertEquals(2,service.consume(event)); verify(orders,times(2)).insert(any(LastlegOrder.class)); verify(inboundPlans,times(2)).convert(eq("100"),anyLong()); verify(inventory,times(2)).registerInTransit(eq("100"),eq("SYSTEM"),any(InventoryCommandRequests.InTransit.class)); verify(details,times(2)).insert(any(LastlegOrderDetail.class)); verify(histories,times(2)).insert(any(LastlegOrderStatusHistory.class)); ArgumentCaptor c=ArgumentCaptor.forClass(LastlegOrder.class); verify(orders,times(2)).insert(c.capture()); assertEquals(Set.of("in-1","in-2"),c.getAllValues().stream().map(LastlegOrder::getSourceInboundId).collect(java.util.stream.Collectors.toSet())); assertTrue(c.getAllValues().stream().allMatch(o->"PENDING_ASSIGNMENT".equals(o.getCurrentStatus()))); assertTrue(c.getAllValues().stream().allMatch(o->o.getId()!=null)); assertTrue(c.getAllValues().stream().allMatch(o->"MSCU1".equals(o.getContainerNo()))); assertEquals(1,c.getAllValues().stream().map(LastlegOrder::getOrderNo).distinct().count()); assertEquals(new LastlegOrderNoGenerator().forContainerNo("MSCU1"),c.getAllValues().get(0).getOrderNo()); } @Test void repeatedEventReturnsExistingEffect(){ EventEnvelope event=event(); LastlegEventInbox row=new LastlegEventInbox(); row.setPayloadHash(event.payloadHash()); when(inbox.selectById(event.eventId())).thenReturn(row); when(orders.countContainer("100","MSCU1")).thenReturn(2L); assertEquals(2,service.consume(event)); verify(orders).countContainer("100","MSCU1"); verify(orders,never()).insert(any(LastlegOrder.class)); verify(details,never()).insert(any(LastlegOrderDetail.class)); } @Test void sameEventWithDifferentHashIsRejected(){ EventEnvelope event=event(); LastlegEventInbox row=new LastlegEventInbox(); row.setPayloadHash("sha256:"+"0".repeat(64)); when(inbox.selectById(event.eventId())).thenReturn(row); assertThrows(IllegalArgumentException.class,()->service.consume(event)); } diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/service/TaillegRealConsumerAcceptanceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/service/TaillegRealConsumerAcceptanceTest.java new file mode 100644 index 000000000..ff1840e5f --- /dev/null +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/service/TaillegRealConsumerAcceptanceTest.java @@ -0,0 +1,132 @@ +package com.baiyue.lastleg.service; + +import com.baiyue.common.event.EventEnvelope; +import com.baiyue.lastleg.dropship.service.DropshipOrderService; +import com.baiyue.lastleg.inboundplan.pda.gateway.WarehouseResourceGateway; +import com.baiyue.lastleg.inboundplan.service.InboundPlanService; +import com.baiyue.lastleg.inventory.command.service.InventoryCommandService; +import com.baiyue.lastleg.looseboard.dto.LocalLooseboardRequests; +import com.baiyue.lastleg.looseboard.service.LocalLooseboardService; +import com.baiyue.lastleg.relabel.dto.RelabelRequests; +import com.baiyue.lastleg.relabel.service.RelabelApplicationService; +import com.baiyue.lastleg.transfer.dto.WarehouseTransferRequests; +import com.baiyue.lastleg.transfer.service.WarehouseTransferService; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfSystemProperty; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.jdbc.core.JdbcTemplate; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@SpringBootTest(classes = TaillegRealConsumerAcceptanceTest.AcceptanceApplication.class, + webEnvironment = SpringBootTest.WebEnvironment.NONE, properties = { + "spring.sql.init.mode=never", "spring.cloud.nacos.discovery.enabled=false", + "spring.cloud.nacos.discovery.register-enabled=false", "spring.cloud.nacos.config.enabled=false", + "spring.cloud.nacos.config.import-check.enabled=false", "spring.task.scheduling.enabled=false" + }) +@EnabledIfSystemProperty(named = "tailleg.real.consumer", matches = "true") +class TaillegRealConsumerAcceptanceTest { + static final String CUSTOMER = "990022"; + static final String MARKER = "REAL-TAIL-20260822-R3"; + static final String EVENT_KEY = "FIRSTLEG_LOADING_COMPLETED:990022022:990022:2"; + static final LocalDateTime RELABEL_DEADLINE = LocalDateTime.of(2026, 8, 29, 11, 1, 21, 628_000_000); + static final LocalDateTime TRANSFER_ETA = LocalDateTime.of(2026, 9, 1, 11, 1, 21, 674_000_000); + static final LocalDateTime RETURN_RELABEL_ETA = LocalDateTime.of(2026, 9, 1, 11, 1, 21, 756_000_000); + static final LocalDate REQUESTED_DATE = LocalDate.of(2026, 8, 25); + + @Autowired JdbcTemplate jdbc; + @Autowired ObjectMapper json; + @Autowired LoadingCompletedConsumer consumer; + @Autowired RelabelApplicationService relabel; + @Autowired WarehouseTransferService transfer; + @Autowired LocalLooseboardService looseboard; + + @SpringBootConfiguration + @EnableAutoConfiguration(excludeName = "com.baiyue.common.security.BaiyueSecurityAutoConfiguration") + @Import({LoadingCompletedConsumer.class, LastlegOrderNoGenerator.class, DropshipOrderService.class, + InboundPlanService.class, InventoryCommandService.class, RelabelApplicationService.class, + WarehouseTransferService.class, LocalLooseboardService.class}) + @MapperScan({"com.baiyue.lastleg.mapper", "com.baiyue.lastleg.dropship.mapper", + "com.baiyue.lastleg.inboundplan.mapper", "com.baiyue.lastleg.inventory.mapper", + "com.baiyue.lastleg.inventory.command.mapper", "com.baiyue.lastleg.relabel.mapper", + "com.baiyue.lastleg.transfer.mapper", "com.baiyue.lastleg.looseboard.mapper"}) + static class AcceptanceApplication { + @Bean WarehouseResourceGateway warehouseResourceGateway() { + return new WarehouseResourceGateway() { + public WarehousePalletReference ensureDropshipPallet(String customerId, long warehouseId, String businessId) { + return new WarehousePalletReference(1L, "UNUSED", 1L); + } + public long reserve(String customerId, long warehouseId, long palletId, long locationId, + String businessType, String businessId, String reservationKey) { return 1L; } + public void scrap(String customerId, long resourceLockId) { } + public void scrapPallet(String customerId, long palletId) { } + }; + } + } + + @Test + void consumesRealOutboxAndCreatesEveryTaillegOrderType() throws Exception { + String envelopeJson = jdbc.queryForObject( + "SELECT payload_json FROM baiyue_firstleg.fl_inbound_outbox WHERE event_key=?", String.class, EVENT_KEY); + EventEnvelope envelope = json.readValue(envelopeJson, EventEnvelope.class); + assertEquals(2, consumer.consume(envelope)); + + Long sourceOrderId = jdbc.queryForObject("SELECT id FROM ll_order WHERE customer_id=? AND " + + "source_loading_plan_id='990022022' AND order_type='FIRST_LEG'", Long.class, CUSTOMER); + String inboundCode = jdbc.queryForObject("SELECT inbound_code FROM ll_order WHERE id=?", String.class, sourceOrderId); + assertNotNull(jdbc.queryForObject("SELECT id FROM ll_inbound_order WHERE customer_id=? AND inbound_code=?", + Long.class, CUSTOMER, inboundCode)); + + if (!exists("ll_relabel_application", MARKER + "-RELABEL-KEY")) { + relabel.create(CUSTOMER, "real-acceptance", new RelabelRequests.Create( + MARKER + "-RELABEL", "92", RELABEL_DEADLINE, "验收联系人", "13800000000", + MARKER, MARKER + "-RELABEL-KEY", List.of(new RelabelRequests.Item("SKU-REAL-A", null, null, + null, "真实验收商品", 3, 3, MARKER + "-SHIP", MARKER + "-TRACK", "SKU-REAL-A-NEW", + "换标验收", null, null, null)))); + } + + createTransfer(sourceOrderId, inboundCode, false, "TRANSFER"); + createTransfer(sourceOrderId, inboundCode, true, "RETURN-RELABEL"); + + if (!exists("ll_local_looseboard_order", MARKER + "-LOOSEBOARD-KEY")) { + looseboard.create(CUSTOMER, "real-acceptance", new LocalLooseboardRequests.Create( + "SHARED_TRUCK", "YES", "WAREHOUSE_TO_WAREHOUSE", "洛杉矶海外仓", "Los Angeles", + "ONT8", "FBA ONT8", "40HQ", 3, new BigDecimal("30.00"), new BigDecimal("0.40"), + new BigDecimal("120.00"), "USD", "验收联系人", "13800000000", REQUESTED_DATE, + MARKER, MARKER + "-LOOSEBOARD-KEY")); + } + + assertEquals(6, jdbc.queryForObject("SELECT COUNT(DISTINCT order_type) FROM ll_order WHERE customer_id=? " + + "AND (source_loading_plan_id='990022022' OR source_event_id LIKE ? OR customer_remark=?)", + Integer.class, CUSTOMER, "%" + MARKER + "%", MARKER)); + } + + private void createTransfer(Long sourceOrderId, String inboundCode, boolean needRelabel, String suffix) { + String idempotencyKey = MARKER + "-" + suffix + "-KEY"; + if (exists("ll_warehouse_transfer_order", idempotencyKey)) return; + transfer.create(CUSTOMER, "real-acceptance", new WarehouseTransferRequests.Create( + inboundCode, List.of(sourceOrderId), "92", needRelabel ? "LAX9" : "ONT8", "真实验收目的地址", + 3, new BigDecimal("0.40"), "TRUCK", needRelabel ? RETURN_RELABEL_ETA : TRANSFER_ETA, + MARKER + "-SHIP-" + suffix, + MARKER + "-TRACK", MARKER + "-BOXES", MARKER, needRelabel, idempotencyKey, + List.of(new WarehouseTransferRequests.Item("SKU-REAL-A", "真实验收商品", 3, MARKER + "-MARK")))); + } + + private boolean exists(String table, String idempotencyKey) { + return jdbc.queryForObject("SELECT COUNT(*) FROM " + table + " WHERE customer_id=? AND idempotency_key=?", + Integer.class, CUSTOMER, idempotencyKey) == 1; + } +} diff --git a/scripts/db/incremental.sql b/scripts/db/incremental.sql index ecfd02807..ab75274ca 100644 --- a/scripts/db/incremental.sql +++ b/scripts/db/incremental.sql @@ -3677,6 +3677,14 @@ SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE tab 'SELECT 1')); PREPARE phase4_inventory_dimension_030 FROM @ddl; EXECUTE phase4_inventory_dimension_030; DEALLOCATE PREPARE phase4_inventory_dimension_030; ALTER TABLE ll_inventory_balance COMMENT='客户入仓单与海外目的仓维度实时库存余额'; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() + AND table_name='ll_local_looseboard_order' AND column_name='lastleg_order_id'), 'SELECT 1', + 'ALTER TABLE ll_local_looseboard_order ADD COLUMN lastleg_order_id BIGINT NULL COMMENT ''关联尾程主订单ID'' AFTER customer_id')); +PREPARE phase4_looseboard_tail_030 FROM @ddl; EXECUTE phase4_looseboard_tail_030; DEALLOCATE PREPARE phase4_looseboard_tail_030; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() + AND table_name='ll_local_looseboard_order' AND index_name='uk_ll_local_looseboard_lastleg'), 'SELECT 1', + 'ALTER TABLE ll_local_looseboard_order ADD UNIQUE KEY uk_ll_local_looseboard_lastleg(lastleg_order_id)')); +PREPARE phase4_looseboard_tail_030 FROM @ddl; EXECUTE phase4_looseboard_tail_030; DEALLOCATE PREPARE phase4_looseboard_tail_030; -- ============================================================================ -- 2026-08-20 / 029