修改提交

This commit is contained in:
2026-08-22 12:35:32 +08:00
parent 996102e77d
commit e398f1f7e0
31 changed files with 540 additions and 27 deletions
Binary file not shown.
+39
View File
@@ -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 <database> <sql>");
}
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();
}
}
}
}
}
@@ -16245,6 +16245,14 @@ SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE tab
'SELECT 1')); 'SELECT 1'));
PREPARE lastleg_inventory_dimension_stmt FROM @ddl; EXECUTE lastleg_inventory_dimension_stmt; DEALLOCATE PREPARE lastleg_inventory_dimension_stmt; PREPARE lastleg_inventory_dimension_stmt FROM @ddl; EXECUTE lastleg_inventory_dimension_stmt; DEALLOCATE PREPARE lastleg_inventory_dimension_stmt;
ALTER TABLE ll_inventory_balance COMMENT='客户入仓单与海外目的仓维度实时库存余额'; 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(); CALL baiyue_firstleg.assert_unified_final_schema();
DROP PROCEDURE IF EXISTS baiyue_firstleg.assert_unified_final_schema; DROP PROCEDURE IF EXISTS baiyue_firstleg.assert_unified_final_schema;
+10 -1
View File
@@ -152,7 +152,7 @@ CREATE TABLE IF NOT EXISTS ll_us_truck_order_status_history (
) ENGINE=InnoDB COMMENT='美国卡车订单状态历史'; ) ENGINE=InnoDB COMMENT='美国卡车订单状态历史';
CREATE TABLE IF NOT EXISTS ll_local_looseboard_order ( 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_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 '起运详细地址', 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 '目的详细地址、码头或仓号', 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 '创建时间', 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), 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_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) 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='本土散板订单'; ) 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 ( 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', id BIGINT NOT NULL COMMENT '主键ID', order_id BIGINT NOT NULL COMMENT '本土散板订单ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID',
@@ -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);
}
}
@@ -10,6 +10,7 @@ public record LastlegOrderSummary(
Long id, Long id,
String orderNo, String orderNo,
String orderType, String orderType,
String orderTypeName,
String sourceInboundId, String sourceInboundId,
String inboundCode, String inboundCode,
String sourceOrderNo, String sourceOrderNo,
@@ -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;
}
@@ -1,3 +1,12 @@
package com.baiyue.lastleg.entity; package com.baiyue.lastleg.entity;
import com.baomidou.mybatisplus.annotation.*; import lombok.Data; import java.math.BigDecimal; import com.baomidou.mybatisplus.annotation.*;
@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 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;
}
@@ -1,3 +1,6 @@
package com.baiyue.lastleg.entity; package com.baiyue.lastleg.entity;
import com.baomidou.mybatisplus.annotation.*; import lombok.Data; import java.time.LocalDateTime; 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;
}
@@ -12,6 +12,7 @@ import java.math.BigDecimal;
public class InboundPlanLine { public class InboundPlanLine {
@TableId(type = IdType.INPUT) @TableId(type = IdType.INPUT)
private Long id; private Long id;
private String customerId;
private Long planId; private Long planId;
private Long orderId; private Long orderId;
private String inboundCode; private String inboundCode;
@@ -13,6 +13,7 @@ import java.time.LocalDateTime;
public class InboundPlanPallet { public class InboundPlanPallet {
@TableId(type = IdType.INPUT) @TableId(type = IdType.INPUT)
private Long id; private Long id;
private String customerId;
private Long planId; private Long planId;
private String palletNo; private String palletNo;
private String destinationWarehouseId; private String destinationWarehouseId;
@@ -12,6 +12,7 @@ import java.math.BigDecimal;
public class InboundPlanPalletAllocation { public class InboundPlanPalletAllocation {
@TableId(type = IdType.INPUT) @TableId(type = IdType.INPUT)
private Long id; private Long id;
private String customerId;
private Long planId; private Long planId;
private Long lineId; private Long lineId;
private Long palletId; private Long palletId;
@@ -43,8 +43,8 @@ public interface InboundPlanOrderMapper {
@Param("planNo") String planNo, @Param("palletCount") int palletCount, @Param("planNo") String planNo, @Param("palletCount") int palletCount,
@Param("updatedAt") LocalDateTime updatedAt); @Param("updatedAt") LocalDateTime updatedAt);
@Insert("INSERT INTO ll_loading_order_status_history(order_id,from_status,to_status,remark,created_at) " + @Insert("INSERT INTO ll_loading_order_status_history(customer_id,order_id,from_status,to_status,remark,created_at) " +
"VALUES(#{orderId},#{fromStatus},'INBOUND_PLANNED',#{remark},#{createdAt})") "VALUES(#{customerId},#{orderId},#{fromStatus},'INBOUND_PLANNED',#{remark},#{createdAt})")
int insertHistory(@Param("orderId") Long orderId, @Param("fromStatus") String fromStatus, int insertHistory(@Param("customerId") String customerId, @Param("orderId") Long orderId, @Param("fromStatus") String fromStatus,
@Param("remark") String remark, @Param("createdAt") LocalDateTime createdAt); @Param("remark") String remark, @Param("createdAt") LocalDateTime createdAt);
} }
@@ -108,7 +108,7 @@ public class InboundPlanService {
if (orderMapper.markPlanned(customerId, order.orderId(), plan.getPlanNo(), count, now) != 1) { if (orderMapper.markPlanned(customerId, order.orderId(), plan.getPlanNo(), count, now) != 1) {
throw new IllegalStateException("Failed to update order " + order.orderId()); 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); "Inbound order converted to inbound plan " + plan.getPlanNo(), now);
} }
return detail(customerId, plan); return detail(customerId, plan);
@@ -158,6 +158,7 @@ public class InboundPlanService {
for (SourceOrderSnapshot order : orders) { for (SourceOrderSnapshot order : orders) {
InboundPlanLine line = new InboundPlanLine(); InboundPlanLine line = new InboundPlanLine();
line.setId(IdWorker.getId()); line.setId(IdWorker.getId());
line.setCustomerId(order.customerId());
line.setPlanId(plan.getId()); line.setPlanId(plan.getId());
line.setOrderId(order.orderId()); line.setOrderId(order.orderId());
line.setInboundCode(order.inboundCode()); line.setInboundCode(order.inboundCode());
@@ -182,6 +183,7 @@ public class InboundPlanService {
SourceOrderSnapshot sample = warehouseSamples.get(draft.warehouseKey()); SourceOrderSnapshot sample = warehouseSamples.get(draft.warehouseKey());
InboundPlanPallet pallet = new InboundPlanPallet(); InboundPlanPallet pallet = new InboundPlanPallet();
pallet.setId(IdWorker.getId()); pallet.setId(IdWorker.getId());
pallet.setCustomerId(plan.getCustomerId());
pallet.setPlanId(plan.getId()); pallet.setPlanId(plan.getId());
pallet.setPalletNo(plan.getPlanNo() + "-P" + String.format("%03d", result.size() + 1)); pallet.setPalletNo(plan.getPlanNo() + "-P" + String.format("%03d", result.size() + 1));
pallet.setDestinationWarehouseId(sample.destinationWarehouseId()); pallet.setDestinationWarehouseId(sample.destinationWarehouseId());
@@ -205,6 +207,7 @@ public class InboundPlanService {
for (PalletAllocator.AllocationDraft draft : allocation.allocations()) { for (PalletAllocator.AllocationDraft draft : allocation.allocations()) {
InboundPlanPalletAllocation item = new InboundPlanPalletAllocation(); InboundPlanPalletAllocation item = new InboundPlanPalletAllocation();
item.setId(IdWorker.getId()); item.setId(IdWorker.getId());
item.setCustomerId(plan.getCustomerId());
item.setPlanId(plan.getId()); item.setPlanId(plan.getId());
item.setLineId(lineByOrder.get(draft.orderId()).getId()); item.setLineId(lineByOrder.get(draft.orderId()).getId());
item.setPalletId(pallets.get(draft.pallet()).getId()); item.setPalletId(pallets.get(draft.pallet()).getId());
@@ -14,6 +14,7 @@ import java.time.LocalDateTime;
public class LocalLooseboardOrder { public class LocalLooseboardOrder {
@TableId(type = IdType.INPUT) private Long id; @TableId(type = IdType.INPUT) private Long id;
private String customerId; private String customerId;
private Long lastlegOrderId;
private String orderNo; private String orderNo;
private String transportMode; private String transportMode;
private String palletRequired; private String palletRequired;
@@ -4,12 +4,17 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker; import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baiyue.common.core.api.PageResult; 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.LocalLooseboardRequests;
import com.baiyue.lastleg.looseboard.dto.LocalLooseboardResponses; import com.baiyue.lastleg.looseboard.dto.LocalLooseboardResponses;
import com.baiyue.lastleg.looseboard.entity.LocalLooseboardHistory; import com.baiyue.lastleg.looseboard.entity.LocalLooseboardHistory;
import com.baiyue.lastleg.looseboard.entity.LocalLooseboardOrder; import com.baiyue.lastleg.looseboard.entity.LocalLooseboardOrder;
import com.baiyue.lastleg.looseboard.mapper.LocalLooseboardHistoryMapper; import com.baiyue.lastleg.looseboard.mapper.LocalLooseboardHistoryMapper;
import com.baiyue.lastleg.looseboard.mapper.LocalLooseboardOrderMapper; import com.baiyue.lastleg.looseboard.mapper.LocalLooseboardOrderMapper;
import com.baiyue.lastleg.service.LastlegOrderNoGenerator;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
@@ -46,6 +51,9 @@ public class LocalLooseboardService {
@Resource private LocalLooseboardOrderMapper orders; @Resource private LocalLooseboardOrderMapper orders;
@Resource private LocalLooseboardHistoryMapper histories; @Resource private LocalLooseboardHistoryMapper histories;
@Resource private LastlegOrderMapper lastlegOrders;
@Resource private LastlegOrderStatusHistoryMapper lastlegHistories;
@Resource private LastlegOrderNoGenerator lastlegOrderNoGenerator;
@Resource private ObjectMapper objectMapper; @Resource private ObjectMapper objectMapper;
@Transactional @Transactional
@@ -71,6 +79,8 @@ public class LocalLooseboardService {
order.setVersion(0L); order.setVersion(0L);
order.setCreatedAt(now); order.setCreatedAt(now);
order.setUpdatedAt(now); order.setUpdatedAt(now);
LastlegOrder lastlegOrder = createLastlegOrder(order, requestHash, now);
order.setLastlegOrderId(lastlegOrder.getId());
try { try {
orders.insert(order); orders.insert(order);
} catch (DuplicateKeyException exception) { } catch (DuplicateKeyException exception) {
@@ -79,6 +89,8 @@ public class LocalLooseboardService {
throw exception; throw exception;
} }
history(order, "CREATE", null, PENDING_ACCEPTANCE, operatorId, null, request.remark(), now); 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()); return detail(customerId, order.getOrderNo());
} }
@@ -147,6 +159,7 @@ public class LocalLooseboardService {
order.setStatus(next); order.setStatus(next);
touch(order, now); touch(order, now);
orders.updateById(order); orders.updateById(order);
syncLastlegOrder(order, next, now);
history(order, "STATUS", from, next, operatorId, null, request.remark(), now); history(order, "STATUS", from, next, operatorId, null, request.remark(), now);
return response(order); return response(order);
} }
@@ -220,6 +233,58 @@ public class LocalLooseboardService {
histories.insert(history); 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) { private void validateQuery(LocalLooseboardRequests.Query query) {
if (StringUtils.hasText(query.status()) && !STATUSES.contains(norm(query.status()))) { if (StringUtils.hasText(query.status()) && !STATUSES.contains(norm(query.status()))) {
throw badRequest("Unsupported looseboard order status"); throw badRequest("Unsupported looseboard order status");
@@ -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<LastlegInboundOrder> {
}
@@ -24,4 +24,13 @@ public enum LastlegOrderType {
public String label() { public String label() {
return 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;
}
}
} }
@@ -118,7 +118,8 @@ public class RelabelApplicationService {
for (RelabelRequests.Item source : request.items()) insertItem(customerId, applicationId, source, now); for (RelabelRequests.Item source : request.items()) insertItem(customerId, applicationId, source, now);
insertHistory(customerId, applicationId, null, PENDING_PROCESSING, operatorId, "Relabel application submitted", 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); return detail(customerId, relabelNo);
} }
@@ -194,7 +195,7 @@ public class RelabelApplicationService {
orders.updateById(order); orders.updateById(order);
insertHistory(customerId, application.getId(), before, next, operatorId, request.remark(), now); insertHistory(customerId, application.getId(), before, next, operatorId, request.remark(), now);
if (!Objects.equals(orderBefore, orderStatus)) { 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); 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.setApplicationId(id); value.setFromStatus(from); value.setToStatus(to); value.setOperatorId(trim(operator));
value.setRemark(trim(remark)); value.setCreatedAt(now); histories.insert(value); value.setRemark(trim(remark)); value.setCreatedAt(now); histories.insert(value);
} }
private void insertOrderHistory(Long id, String from, String to, String remark, LocalDateTime now) { private void insertOrderHistory(String customerId, Long id, String from, String to, String remark,
LastlegOrderStatusHistory value = new LastlegOrderStatusHistory(); value.setOrderId(id); value.setFromStatus(from); 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); value.setToStatus(to); value.setEventId("RELABEL_APPLICATION"); value.setRemark(trim(remark)); value.setCreatedAt(now);
orderHistories.insert(value); orderHistories.insert(value);
} }
@@ -12,6 +12,7 @@ import com.baiyue.lastleg.entity.LastlegOrderStatusHistory;
import com.baiyue.lastleg.mapper.LastlegOrderDetailMapper; import com.baiyue.lastleg.mapper.LastlegOrderDetailMapper;
import com.baiyue.lastleg.mapper.LastlegOrderMapper; import com.baiyue.lastleg.mapper.LastlegOrderMapper;
import com.baiyue.lastleg.mapper.LastlegOrderStatusHistoryMapper; import com.baiyue.lastleg.mapper.LastlegOrderStatusHistoryMapper;
import com.baiyue.lastleg.model.LastlegOrderType;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -106,7 +107,8 @@ public class LastlegOrderService {
lines.add("ID,尾程单号,订单类型,入仓码,头程单号,货主,海外仓,转仓,转仓编码,FBA仓库,集装箱号,货柜状态,是否异常,是否暂扣,托数,箱数,体积(m3),数量,体积重量,预计到港时间,实际到港时间,预计到仓,入库单号,换标单号,换标状态,合托标记,主流程状态,子单状态,备注(仅自己),备注(客户),备注(仓库)"); lines.add("ID,尾程单号,订单类型,入仓码,头程单号,货主,海外仓,转仓,转仓编码,FBA仓库,集装箱号,货柜状态,是否异常,是否暂扣,托数,箱数,体积(m3),数量,体积重量,预计到港时间,实际到港时间,预计到仓,入库单号,换标单号,换标状态,合托标记,主流程状态,子单状态,备注(仅自己),备注(客户),备注(仓库)");
for (LastlegOrder order : orders) { for (LastlegOrder order : orders) {
lines.add(String.join(",", List.of( 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.getSourceOrderNo()), csv(order.getCustomerName()), csv(order.getOverseasWarehouseName()),
csv(order.getTransferWarehouseName()), csv(order.getTransferWarehouseCode()), csv(order.getFbaWarehouseCode()), csv(order.getTransferWarehouseName()), csv(order.getTransferWarehouseCode()), csv(order.getFbaWarehouseCode()),
csv(order.getContainerNo()), csv(order.getContainerStatus()), csv(order.getAbnormal()), csv(order.getDetained()), csv(order.getContainerNo()), csv(order.getContainerStatus()), csv(order.getAbnormal()), csv(order.getDetained()),
@@ -161,7 +163,8 @@ public class LastlegOrderService {
} }
private LastlegOrderSummary summary(LastlegOrder o) { 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.getInboundCode(), o.getSourceOrderNo(), o.getCustomerId(), o.getCustomerName(), o.getOverseasWarehouseId(),
o.getOverseasWarehouseName(), o.getTransferWarehouseId(), o.getTransferWarehouseCode(), o.getOverseasWarehouseName(), o.getTransferWarehouseId(), o.getTransferWarehouseCode(),
o.getTransferWarehouseName(), o.getFbaWarehouseCode(), o.getContainerNo(), o.getContainerStatus(), o.getTransferWarehouseName(), o.getFbaWarehouseCode(), o.getContainerNo(), o.getContainerStatus(),
@@ -31,6 +31,7 @@ import com.fasterxml.jackson.databind.JsonNode;
public class LoadingCompletedConsumer { public class LoadingCompletedConsumer {
@Resource private LastlegEventInboxMapper inboxMapper; @Resource private LastlegEventInboxMapper inboxMapper;
@Resource private LastlegOrderMapper orderMapper; @Resource private LastlegOrderMapper orderMapper;
@Resource private LastlegInboundOrderMapper inboundOrderMapper;
@Resource private LastlegOrderDetailMapper detailMapper; @Resource private LastlegOrderDetailMapper detailMapper;
@Resource private LastlegOrderStatusHistoryMapper historyMapper; @Resource private LastlegOrderStatusHistoryMapper historyMapper;
@Resource private InboundPlanCartonMapper cartonMapper; @Resource private InboundPlanCartonMapper cartonMapper;
@@ -173,9 +174,11 @@ public class LoadingCompletedConsumer {
if (raced != null && Objects.equals(raced.getSourcePayloadHash(), orderHash)) return; if (raced != null && Objects.equals(raced.getSourcePayloadHash(), orderHash)) return;
throw exception; throw exception;
} }
createInboundOrderSnapshot(event, payload, source, order, orderHash);
for(var item:source.items()) { for(var item:source.items()) {
LastlegOrderDetail detail=new LastlegOrderDetail(); LastlegOrderDetail detail=new LastlegOrderDetail();
detail.setCustomerId(customerId);
detail.setOrderId(order.getId()); detail.setSourceInboundDetailId(item.sourceInboundDetailId()); detail.setSkuCode(item.skuCode()); 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.setProductNameZh(item.productNameZh()); detail.setProductNameEn(item.productNameEn()); detail.setPackageCount(item.packageCount());
detail.setProductQuantity(item.productQuantity()); detail.setPalletCount(item.palletCount()); detail.setGrossWeightKg(item.grossWeightKg()); 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); detail.setShipmentNo(item.shipmentNo()); detail.setShippingMark(item.shippingMark()); detailMapper.insert(detail);
createCartonLabels(customerId, order.getId(), item); 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.setOrderId(order.getId()); history.setToStatus("PENDING_ASSIGNMENT"); history.setEventId(event.eventId());
history.setRemark("头程装柜完成自动生成"); history.setCreatedAt(LocalDateTime.now()); historyMapper.insert(history); history.setRemark("头程装柜完成自动生成"); history.setCreatedAt(LocalDateTime.now()); historyMapper.insert(history);
if ("FIRST_LEG".equals(orderType)) { 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, private void registerInTransit(EventEnvelope event, FirstLegLoadingPlanCompletedV1 payload,
FirstLegLoadingPlanCompletedV1.Order source, LastlegOrder order, FirstLegLoadingPlanCompletedV1.Order source, LastlegOrder order,
String inboundOrderNo) { String inboundOrderNo) {
@@ -152,9 +152,9 @@ public class WarehouseTransferService {
for (int index = 0; index < request.items().size(); index++) { for (int index = 0; index < request.items().size(); index++) {
WarehouseTransferRequests.Item source = request.items().get(index); WarehouseTransferRequests.Item source = request.items().get(index);
insertTransferItem(customerId, transferId, source, now); 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); "Warehouse transfer submitted for review", now);
if (request.needRelabel()) createRelabel(customerId, operatorId, transfer, order, request, now); if (request.needRelabel()) createRelabel(customerId, operatorId, transfer, order, request, now);
return detail(customerId, transferNo); return detail(customerId, transferNo);
@@ -258,7 +258,7 @@ public class WarehouseTransferService {
order.setSubOrderStatus("WAREHOUSE_TRANSFER:" + next); order.setSubOrderStatus("WAREHOUSE_TRANSFER:" + next);
order.setUpdatedAt(now); orders.updateById(order); order.setUpdatedAt(now); orders.updateById(order);
if (!java.util.Objects.equals(before, orderStatus)) { 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); "Warehouse transfer reviewed: " + next, now);
} }
return detail(customerId, transferNo); return detail(customerId, transferNo);
@@ -337,7 +337,7 @@ public class WarehouseTransferService {
String before = order.getCurrentStatus(); order.setCurrentStatus("WAIT_OUTBOUND"); String before = order.getCurrentStatus(); order.setCurrentStatus("WAIT_OUTBOUND");
order.setSubOrderStatus("WAREHOUSE_TRANSFER:ALLOCATED"); order.setUpdatedAt(now); order.setSubOrderStatus("WAREHOUSE_TRANSFER:ALLOCATED"); order.setUpdatedAt(now);
orders.updateById(order); orders.updateById(order);
insertOrderHistory(order.getId(), before, order.getCurrentStatus(), transferNo, insertOrderHistory(customerId, order.getId(), before, order.getCurrentStatus(), transferNo,
"Warehouse transfer inventory allocated", now); "Warehouse transfer inventory allocated", now);
return detail(customerId, transferNo); return detail(customerId, transferNo);
} }
@@ -388,7 +388,7 @@ public class WarehouseTransferService {
String before = order.getCurrentStatus(); order.setCurrentStatus("IN_TRANSIT"); String before = order.getCurrentStatus(); order.setCurrentStatus("IN_TRANSIT");
order.setSubOrderStatus("WAREHOUSE_TRANSFER:OUTBOUNDED"); order.setUpdatedAt(now); order.setSubOrderStatus("WAREHOUSE_TRANSFER:OUTBOUNDED"); order.setUpdatedAt(now);
orders.updateById(order); orders.updateById(order);
insertOrderHistory(order.getId(), before, order.getCurrentStatus(), transferNo, insertOrderHistory(customerId, order.getId(), before, order.getCurrentStatus(), transferNo,
"Warehouse transfer left source warehouse", now); "Warehouse transfer left source warehouse", now);
return detail(customerId, transferNo); return detail(customerId, transferNo);
} }
@@ -455,7 +455,7 @@ public class WarehouseTransferService {
String before = order.getCurrentStatus(); order.setCurrentStatus(complete ? "COMPLETED" : "IN_TRANSIT"); String before = order.getCurrentStatus(); order.setCurrentStatus(complete ? "COMPLETED" : "IN_TRANSIT");
order.setSubOrderStatus("WAREHOUSE_TRANSFER:" + (complete ? RECEIVED : PARTIALLY_RECEIVED)); order.setSubOrderStatus("WAREHOUSE_TRANSFER:" + (complete ? RECEIVED : PARTIALLY_RECEIVED));
order.setUpdatedAt(now); orders.updateById(order); 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); "Warehouse transfer inventory received at destination", now);
return detail(customerId, transferNo); return detail(customerId, transferNo);
} }
@@ -490,9 +490,10 @@ public class WarehouseTransferService {
transferItems.insert(item); 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) { WarehouseTransferRequests.Create request, LocalDateTime now) {
LastlegOrderDetail detail = new LastlegOrderDetail(); LastlegOrderDetail detail = new LastlegOrderDetail();
detail.setCustomerId(customerId);
detail.setOrderId(orderId); detail.setSourceInboundDetailId(transferNo + ":" + index); detail.setOrderId(orderId); detail.setSourceInboundDetailId(transferNo + ":" + index);
detail.setSkuCode(source.skuCode().trim()); detail.setProductNameZh(source.productNameZh().trim()); detail.setSkuCode(source.skuCode().trim()); detail.setProductNameZh(source.productNameZh().trim());
detail.setPackageCount(source.packageCount()); detail.setDestinationWarehouseCode(request.fbaWarehouseCode().trim()); detail.setPackageCount(source.packageCount()); detail.setDestinationWarehouseCode(request.fbaWarehouseCode().trim());
@@ -537,9 +538,10 @@ public class WarehouseTransferService {
order.setUpdatedAt(now); orders.updateById(order); order.setUpdatedAt(now); orders.updateById(order);
} }
private void insertOrderHistory(long orderId, String fromStatus, String toStatus, String transferNo, private void insertOrderHistory(String customerId, long orderId, String fromStatus, String toStatus,
String remark, LocalDateTime now) { String transferNo, String remark, LocalDateTime now) {
LastlegOrderStatusHistory history = new LastlegOrderStatusHistory(); LastlegOrderStatusHistory history = new LastlegOrderStatusHistory();
history.setCustomerId(customerId);
history.setOrderId(orderId); history.setFromStatus(fromStatus); history.setToStatus(toStatus); history.setOrderId(orderId); history.setFromStatus(fromStatus); history.setToStatus(toStatus);
history.setEventId("WAREHOUSE_TRANSFER:" + transferNo); history.setRemark(remark); history.setEventId("WAREHOUSE_TRANSFER:" + transferNo); history.setRemark(remark);
history.setCreatedAt(now); orderHistories.insert(history); history.setCreatedAt(now); orderHistories.insert(history);
@@ -1304,6 +1304,7 @@ CREATE TABLE IF NOT EXISTS ll_us_truck_order_status_history (
CREATE TABLE IF NOT EXISTS ll_local_looseboard_order ( CREATE TABLE IF NOT EXISTS ll_local_looseboard_order (
id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', 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 '运输方式:拼车或优先拼载', 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 '运输类型', 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 '起运详细地址', 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 '创建时间', 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 '更新时间', 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), 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), 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_list(customer_id,status,created_at),
KEY idx_ll_local_looseboard_date(customer_id,requested_date,created_at) KEY idx_ll_local_looseboard_date(customer_id,requested_date,created_at)
@@ -89,7 +89,7 @@ class InboundPlanServiceTest {
assertEquals(1, allocations.getAllValues().stream() assertEquals(1, allocations.getAllValues().stream()
.filter(item -> "PENDING_COUNT".equals(item.getStatus())).count()); .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).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); verify(orderMapper).lockOrder("100", 2L);
assertEquals(2L, result.sourceOrderId()); assertEquals(2L, result.sourceOrderId());
assertEquals("CONT-1", result.containerNo()); assertEquals("CONT-1", result.containerNo());
@@ -17,5 +17,7 @@ class LocalLooseboardSchemaContractTest {
assertTrue(sql.contains("CREATE TABLE IF NOT EXISTS ll_local_looseboard_history")); assertTrue(sql.contains("CREATE TABLE IF NOT EXISTS ll_local_looseboard_history"));
assertTrue(sql.contains("operation_type VARCHAR(32) NOT NULL")); assertTrue(sql.contains("operation_type VARCHAR(32) NOT NULL"));
assertTrue(sql.contains("uk_ll_local_looseboard_no(customer_id,order_no)")); 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)"));
} }
} }
@@ -5,6 +5,10 @@ import com.baiyue.lastleg.looseboard.entity.LocalLooseboardHistory;
import com.baiyue.lastleg.looseboard.entity.LocalLooseboardOrder; import com.baiyue.lastleg.looseboard.entity.LocalLooseboardOrder;
import com.baiyue.lastleg.looseboard.mapper.LocalLooseboardHistoryMapper; import com.baiyue.lastleg.looseboard.mapper.LocalLooseboardHistoryMapper;
import com.baiyue.lastleg.looseboard.mapper.LocalLooseboardOrderMapper; 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 com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@@ -31,6 +35,9 @@ import static org.mockito.Mockito.when;
class LocalLooseboardServiceTest { class LocalLooseboardServiceTest {
@Mock LocalLooseboardOrderMapper orders; @Mock LocalLooseboardOrderMapper orders;
@Mock LocalLooseboardHistoryMapper histories; @Mock LocalLooseboardHistoryMapper histories;
@Mock LastlegOrderMapper lastlegOrders;
@Mock LastlegOrderStatusHistoryMapper lastlegHistories;
@Mock LastlegOrderNoGenerator lastlegOrderNoGenerator;
private LocalLooseboardService service; private LocalLooseboardService service;
@BeforeEach @BeforeEach
@@ -38,7 +45,11 @@ class LocalLooseboardServiceTest {
service = new LocalLooseboardService(); service = new LocalLooseboardService();
ReflectionTestUtils.setField(service, "orders", orders); ReflectionTestUtils.setField(service, "orders", orders);
ReflectionTestUtils.setField(service, "histories", histories); 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()); ReflectionTestUtils.setField(service, "objectMapper", new ObjectMapper().findAndRegisterModules());
org.mockito.Mockito.lenient().when(lastlegOrderNoGenerator.next()).thenReturn("LMO-LOOSE-1");
} }
@Test @Test
@@ -56,6 +67,9 @@ class LocalLooseboardServiceTest {
assertEquals("100", detail.summary().customerId()); assertEquals("100", detail.summary().customerId());
assertEquals(LocalLooseboardService.PENDING_ACCEPTANCE, detail.summary().status()); assertEquals(LocalLooseboardService.PENDING_ACCEPTANCE, detail.summary().status());
assertEquals("SHARED_TRUCK", detail.summary().transportMode()); assertEquals("SHARED_TRUCK", detail.summary().transportMode());
verify(lastlegOrders).insert(org.mockito.ArgumentMatchers.<LastlegOrder>argThat(value ->
"LOCAL_LOOSEBOARD".equals(value.getOrderType())
&& "LOCAL_LOOSEBOARD:PENDING_ACCEPTANCE".equals(value.getSubOrderStatus())));
verify(histories).insert(org.mockito.ArgumentMatchers.<LocalLooseboardHistory>argThat(value -> verify(histories).insert(org.mockito.ArgumentMatchers.<LocalLooseboardHistory>argThat(value ->
"CREATE".equals(value.getOperationType()))); "CREATE".equals(value.getOperationType())));
} }
@@ -15,5 +15,7 @@ class LastlegOrderTypeTest {
Arrays.stream(LastlegOrderType.values()).map(Enum::name).toList()); Arrays.stream(LastlegOrderType.values()).map(Enum::name).toList());
assertEquals("换标下单", LastlegOrderType.RELABEL_APPLICATION.label()); assertEquals("换标下单", LastlegOrderType.RELABEL_APPLICATION.label());
assertEquals("本土散板", LastlegOrderType.LOCAL_LOOSEBOARD.label()); assertEquals("本土散板", LastlegOrderType.LOCAL_LOOSEBOARD.label());
assertEquals("一件代发", LastlegOrderType.labelOf("ONE_CLICK_DROPSHIP"));
assertEquals("LEGACY_TYPE", LastlegOrderType.labelOf("LEGACY_TYPE"));
} }
} }
@@ -53,6 +53,8 @@ class LastlegOrderServiceTest {
assertEquals(11L, response.order().id()); assertEquals(11L, response.order().id());
assertEquals("100", response.order().customerId()); assertEquals("100", response.order().customerId());
assertEquals("ONE_CLICK_DROPSHIP", response.order().orderType());
assertEquals("一件代发", response.order().orderTypeName());
assertTrue(java.util.Arrays.stream(response.getClass().getRecordComponents()) assertTrue(java.util.Arrays.stream(response.getClass().getRecordComponents())
.noneMatch(component -> "dropshipPallets".equals(component.getName()))); .noneMatch(component -> "dropshipPallets".equals(component.getName())));
assertTrue(java.util.Arrays.stream(com.baiyue.lastleg.dto.LastlegOrderDetailResponse.History.class.getRecordComponents()) assertTrue(java.util.Arrays.stream(com.baiyue.lastleg.dto.LastlegOrderDetailResponse.History.class.getRecordComponents())
@@ -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.*; 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) @ExtendWith(org.mockito.junit.jupiter.MockitoExtension.class)
class LoadingCompletedConsumerTest { 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; @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,"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())); } @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<LastlegOrder> 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 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<LastlegOrder> 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 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)); } @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)); }
@@ -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;
}
}
+8
View File
@@ -3677,6 +3677,14 @@ SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE tab
'SELECT 1')); 'SELECT 1'));
PREPARE phase4_inventory_dimension_030 FROM @ddl; EXECUTE phase4_inventory_dimension_030; DEALLOCATE PREPARE phase4_inventory_dimension_030; PREPARE phase4_inventory_dimension_030 FROM @ddl; EXECUTE phase4_inventory_dimension_030; DEALLOCATE PREPARE phase4_inventory_dimension_030;
ALTER TABLE ll_inventory_balance COMMENT='客户入仓单与海外目的仓维度实时库存余额'; 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 -- 2026-08-20 / 029