feat(phase3): 收口父子单与全链路验收

This commit is contained in:
2026-08-10 12:23:43 +08:00
parent b294695169
commit 75d5fd4e26
55 changed files with 1110 additions and 115 deletions
@@ -49,6 +49,10 @@ public class OrderEntity extends BaseEntity {
private String amazonWarehouseCode;
private String shipmentId;
private String referenceId;
private String orderRole;
private Long parentOrderId;
private Long rootOrderId;
private Integer splitSequence;
private String expressCompany;
private String expressTrackingNo;
private LocalDateTime submittedAt;
@@ -0,0 +1,34 @@
package com.baiyue.order.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
/** 父订单→子订单→柜次的数量台账;柜次ID仅为Firstleg逻辑引用。 */
@Getter @Setter
@TableName("ord_order_fulfillment_split")
public class OrderFulfillmentSplitEntity {
@TableId(type = IdType.ASSIGN_ID) private Long id;
private Long tenantId;
private Long parentOrderId;
private Long childOrderId;
private Long loadingPlanId;
private Long containerBookingId;
private Integer sourcePackageCount;
private Integer loadedPackageCount;
private Integer remainingPackageCount;
private String sourceDetailJson;
private String operationId;
private String idempotencyKey;
private String requestHash;
private String splitRole;
private Integer expectedParentVersion;
private Long createdBy;
private String createdByName;
private LocalDateTime createdAt;
private Integer version;
}
@@ -0,0 +1,8 @@
package com.baiyue.order.mapper;
import com.baiyue.order.entity.OrderFulfillmentSplitEntity;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface OrderFulfillmentSplitMapper extends BaseMapper<OrderFulfillmentSplitEntity> {}
+38
View File
@@ -37,6 +37,10 @@ CREATE TABLE IF NOT EXISTS ord_order (
amazon_warehouse_code VARCHAR(32) NULL COMMENT '亚马逊仓库编码',
shipment_id VARCHAR(64) NULL COMMENT 'Amazon Shipment ID',
reference_id VARCHAR(64) NULL COMMENT 'Amazon Reference ID',
order_role VARCHAR(12) NOT NULL DEFAULT 'MASTER' COMMENT '订单层级:MASTER主订单、CHILD子订单',
parent_order_id BIGINT UNSIGNED NULL COMMENT '直属父订单ID;主订单为空;禁止子订单嵌套',
root_order_id BIGINT UNSIGNED NULL COMMENT '根主订单ID',
split_sequence INT UNSIGNED NULL COMMENT '同一父订单下的同级子订单顺序',
express_company VARCHAR(64) NULL COMMENT '履约快递公司名称快照',
express_tracking_no VARCHAR(128) NULL COMMENT '履约快递单号',
submitted_at DATETIME(3) NULL COMMENT '提交时间',
@@ -55,6 +59,40 @@ CREATE TABLE IF NOT EXISTS ord_order (
KEY idx_order_tenant_warehouse (tenant_id, destination_warehouse_id)
) ENGINE=InnoDB COMMENT='头程订单主表:保存商户委托、目的仓、运输方式和订单当前状态';
CREATE TABLE IF NOT EXISTS ord_order_fulfillment_split (
id BIGINT UNSIGNED NOT NULL COMMENT '拆分台账ID',
tenant_id BIGINT UNSIGNED NOT NULL,
parent_order_id BIGINT UNSIGNED NOT NULL,
child_order_id BIGINT UNSIGNED NOT NULL,
split_role VARCHAR(12) NOT NULL COMMENT '本台账子片:LOADED或REMAINDER',
loading_plan_id BIGINT UNSIGNED NULL COMMENT 'Firstleg装柜计划逻辑ID',
container_booking_id BIGINT UNSIGNED NULL COMMENT 'Firstleg柜次逻辑ID',
source_package_count INT UNSIGNED NOT NULL,
loaded_package_count INT UNSIGNED NOT NULL,
remaining_package_count INT UNSIGNED NOT NULL,
source_detail_json JSON NULL COMMENT '来源货物明细及箱数快照',
operation_id VARCHAR(64) NOT NULL,
idempotency_key VARCHAR(128) NOT NULL,
request_hash CHAR(64) NOT NULL,
expected_parent_version INT NOT NULL,
created_by BIGINT UNSIGNED NOT NULL,
created_by_name VARCHAR(64) NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
version INT NOT NULL DEFAULT 0,
PRIMARY KEY (id),
UNIQUE KEY uk_order_split_operation (tenant_id, operation_id, split_role),
UNIQUE KEY uk_order_split_idempotency (tenant_id, parent_order_id, idempotency_key, split_role),
UNIQUE KEY uk_order_split_child (tenant_id, child_order_id),
KEY idx_order_split_parent (tenant_id, parent_order_id, created_at, id),
KEY idx_order_split_container (tenant_id, container_booking_id, created_at, id),
CONSTRAINT fk_order_split_parent FOREIGN KEY (parent_order_id) REFERENCES ord_order(id),
CONSTRAINT fk_order_split_child FOREIGN KEY (child_order_id) REFERENCES ord_order(id),
CONSTRAINT chk_order_split_quantity CHECK (
source_package_count > 0 AND loaded_package_count > 0
AND loaded_package_count + remaining_package_count = source_package_count
)
) ENGINE=InnoDB COMMENT='父订单到子订单的装柜数量及柜次关联台账';
CREATE TABLE IF NOT EXISTS ord_order_party (
id BIGINT UNSIGNED NOT NULL COMMENT '参与方快照ID',
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID',
@@ -0,0 +1,155 @@
SET NAMES utf8mb4;
USE baiyue_order;
DROP PROCEDURE IF EXISTS phase3_order_add_column;
DELIMITER $$
CREATE PROCEDURE phase3_order_add_column(IN p_table VARCHAR(64), IN p_column VARCHAR(64), IN p_definition TEXT)
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE()
AND table_name=p_table AND column_name=p_column) THEN
SET @ddl = CONCAT('ALTER TABLE `', p_table, '` ADD COLUMN `', p_column, '` ', p_definition);
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
END IF;
END$$
DELIMITER ;
CALL phase3_order_add_column('ord_order','order_role',"VARCHAR(12) NOT NULL DEFAULT 'MASTER' COMMENT '订单层级:MASTER、CHILD'");
CALL phase3_order_add_column('ord_order','parent_order_id','BIGINT UNSIGNED NULL COMMENT ''直属父订单ID;禁止子订单嵌套''');
CALL phase3_order_add_column('ord_order','root_order_id','BIGINT UNSIGNED NULL COMMENT ''根主订单ID''');
CALL phase3_order_add_column('ord_order','split_sequence','INT UNSIGNED NULL COMMENT ''同一父订单下的同级顺序''');
DROP PROCEDURE IF EXISTS phase3_order_add_column;
DROP PROCEDURE IF EXISTS phase3_order_add_index;
DELIMITER $$
CREATE PROCEDURE phase3_order_add_index(IN p_table VARCHAR(64), IN p_name VARCHAR(64), IN p_definition TEXT)
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE()
AND table_name=p_table AND index_name=p_name) THEN
SET @ddl = CONCAT('ALTER TABLE `',p_table,'` ADD ',p_definition);
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
END IF;
END$$
DELIMITER ;
CALL phase3_order_add_index('ord_order','uk_order_parent_sequence',
'UNIQUE KEY `uk_order_parent_sequence` (`tenant_id`,`parent_order_id`,`split_sequence`)');
CALL phase3_order_add_index('ord_order','idx_order_root_role',
'KEY `idx_order_root_role` (`tenant_id`,`root_order_id`,`order_role`,`created_at`,`id`)');
DROP PROCEDURE IF EXISTS phase3_order_add_index;
DROP PROCEDURE IF EXISTS phase3_order_parent_fk;
DELIMITER $$
CREATE PROCEDURE phase3_order_parent_fk()
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.table_constraints WHERE constraint_schema=DATABASE()
AND table_name='ord_order' AND constraint_name='fk_order_parent') THEN
ALTER TABLE ord_order DROP FOREIGN KEY fk_order_parent;
END IF;
ALTER TABLE ord_order ADD CONSTRAINT fk_order_parent FOREIGN KEY (parent_order_id) REFERENCES ord_order(id);
END$$
DELIMITER ;
CALL phase3_order_parent_fk();
DROP PROCEDURE IF EXISTS phase3_order_parent_fk;
CREATE TABLE IF NOT EXISTS ord_order_fulfillment_split (
id BIGINT UNSIGNED NOT NULL,
tenant_id BIGINT UNSIGNED NOT NULL,
parent_order_id BIGINT UNSIGNED NOT NULL,
child_order_id BIGINT UNSIGNED NOT NULL,
split_role VARCHAR(12) NOT NULL COMMENT 'LOADED或REMAINDER',
loading_plan_id BIGINT UNSIGNED NULL,
container_booking_id BIGINT UNSIGNED NULL,
source_package_count INT UNSIGNED NOT NULL,
loaded_package_count INT UNSIGNED NOT NULL,
remaining_package_count INT UNSIGNED NOT NULL,
source_detail_json JSON NULL,
operation_id VARCHAR(64) NOT NULL,
idempotency_key VARCHAR(128) NOT NULL,
request_hash CHAR(64) NOT NULL,
expected_parent_version INT NOT NULL,
created_by BIGINT UNSIGNED NOT NULL,
created_by_name VARCHAR(64) NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
version INT NOT NULL DEFAULT 0,
PRIMARY KEY(id),
UNIQUE KEY uk_order_split_operation (tenant_id,operation_id,split_role),
UNIQUE KEY uk_order_split_idempotency (tenant_id,parent_order_id,idempotency_key,split_role),
UNIQUE KEY uk_order_split_child (tenant_id,child_order_id),
KEY idx_order_split_parent (tenant_id,parent_order_id,created_at,id),
KEY idx_order_split_container (tenant_id,container_booking_id,created_at,id),
CONSTRAINT fk_order_split_parent FOREIGN KEY(parent_order_id) REFERENCES ord_order(id),
CONSTRAINT fk_order_split_child FOREIGN KEY(child_order_id) REFERENCES ord_order(id),
CONSTRAINT chk_order_split_quantity CHECK (source_package_count > 0 AND loaded_package_count > 0
AND loaded_package_count + remaining_package_count = source_package_count)
) ENGINE=InnoDB COMMENT='父订单→子订单→柜次数量台账';
DROP TRIGGER IF EXISTS trg_ord_order_parent_insert;
DROP TRIGGER IF EXISTS trg_ord_order_parent_update;
DELIMITER $$
CREATE TRIGGER trg_ord_order_parent_insert BEFORE INSERT ON ord_order FOR EACH ROW
BEGIN
IF NEW.order_role='MASTER' THEN
SET NEW.parent_order_id=NULL;
IF NEW.root_order_id IS NULL THEN SET NEW.root_order_id=NEW.id; END IF;
ELSEIF NEW.order_role='CHILD' THEN
IF NEW.parent_order_id IS NULL OR NEW.root_order_id IS NULL OR NEW.split_sequence IS NULL THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='子订单必须有父订单、根订单和顺序号';
END IF;
IF NEW.root_order_id<>NEW.parent_order_id OR EXISTS(
SELECT 1 FROM ord_order p WHERE p.id=NEW.parent_order_id AND p.order_role<>'MASTER') THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='子订单只允许直接挂在主订单下';
END IF;
IF NOT EXISTS(SELECT 1 FROM ord_order p WHERE p.id=NEW.parent_order_id AND p.tenant_id=NEW.tenant_id AND p.deleted=0) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='父订单不存在或客户归属不一致';
END IF;
ELSE
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='订单层级非法';
END IF;
END$$
CREATE TRIGGER trg_ord_order_parent_update BEFORE UPDATE ON ord_order FOR EACH ROW
BEGIN
IF NEW.order_role<>OLD.order_role OR NOT (NEW.parent_order_id<=>OLD.parent_order_id)
OR NOT (NEW.root_order_id<=>OLD.root_order_id) OR NOT (NEW.split_sequence<=>OLD.split_sequence) THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='订单父子关系不可修改';
END IF;
END$$
DELIMITER ;
CALL phase3_order_add_column('ord_order_fulfillment_split','split_role',"VARCHAR(12) NOT NULL DEFAULT 'LOADED' COMMENT 'LOADED或REMAINDER'");
CALL phase3_order_add_column('ord_order_fulfillment_split','request_hash',"CHAR(64) NULL COMMENT '请求内容SHA-256;存量记录允许为空'");
DROP PROCEDURE IF EXISTS phase3_order_drop_split_index;
DELIMITER $$
CREATE PROCEDURE phase3_order_drop_split_index()
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE()
AND table_name='ord_order_fulfillment_split' AND index_name='uk_order_split_operation') THEN
ALTER TABLE ord_order_fulfillment_split DROP INDEX uk_order_split_operation;
END IF;
IF EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE()
AND table_name='ord_order_fulfillment_split' AND index_name='uk_order_split_idempotency') THEN
ALTER TABLE ord_order_fulfillment_split DROP INDEX uk_order_split_idempotency;
END IF;
END$$
DELIMITER ;
CALL phase3_order_drop_split_index();
DROP PROCEDURE IF EXISTS phase3_order_drop_split_index;
CALL phase3_order_add_index('ord_order_fulfillment_split','uk_order_split_operation',
'UNIQUE KEY `uk_order_split_operation` (`tenant_id`,`operation_id`,`split_role`)');
CALL phase3_order_add_index('ord_order_fulfillment_split','uk_order_split_idempotency',
'UNIQUE KEY `uk_order_split_idempotency` (`tenant_id`,`parent_order_id`,`idempotency_key`,`split_role`)');
DROP PROCEDURE IF EXISTS phase3_order_assert;
DELIMITER $$
CREATE PROCEDURE phase3_order_assert()
BEGIN
IF (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema=DATABASE()
AND table_name='ord_order' AND column_name IN ('order_role','parent_order_id','root_order_id','split_sequence'))<>4 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='订单父子字段未完整创建';
END IF;
IF (SELECT COUNT(*) FROM information_schema.tables WHERE table_schema=DATABASE()
AND table_name='ord_order_fulfillment_split')<>1 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='订单拆分台账未创建';
END IF;
END$$
DELIMITER ;
CALL phase3_order_assert();
DROP PROCEDURE IF EXISTS phase3_order_assert;