diff --git a/deploy/sql/README.md b/deploy/sql/README.md index 501c91676..e8d961513 100644 --- a/deploy/sql/README.md +++ b/deploy/sql/README.md @@ -3,8 +3,8 @@ ## 唯一数据库初始化与发布入口 - 国内区域固定入口:`头程数据库统一增量-MySQL8.sql`。文件名保留历史兼容,内容维护为当前完整数据库基线和存量环境兼容升级,不再按日期拆分发布脚本。 -- 海外区域固定增量入口:`海外区域统一增量-MySQL8.sql`;空库先分别执行 `lastleg-service/src/main/resources/db/lastleg.sql`与 `warehouse-overseas-service/src/main/resources/db/warehouse.sql` 基线,再执行该入口。固定入口会创建并校验独立的 `baiyue_event_bridge_overseas`,再升级 `baiyue_lastleg`与 `baiyue_lastlag_warehouse`;IAM 安全事件 Inbox 和可恢复投影已并入 `baiyue_lastleg.ll_security_*`,Redis 只作可重建镜像。 -- Lastleg 基线和海外固定增量仅确定性初始化国内固定 `platform_admin`(`authUserId=1000001`、`tenantId=0`)的 USER/AUTHORIZATION version=0 安全投影;其他既有开发账号不建立兼容表或长期补投入口。 +- 海外区域固定入口:`海外区域统一增量-MySQL8.sql`。该文件已经内联 Lastleg 与 Warehouse Overseas 的完整空库基线,会自行创建并初始化 `baiyue_lastleg`、`baiyue_lastlag_warehouse` 和独立的 `baiyue_event_bridge_overseas`,清库后不得再额外执行模块基线。IAM 安全事件 Inbox 和可恢复投影位于 `baiyue_lastleg.ll_security_*`,Redis 只作可重建镜像。 +- Lastleg 基线和海外固定增量仅确定性初始化国内固定 `platform_admin`(`authUserId=1000001`、`tenantId=0`)的 USER/AUTHORIZATION version=0 安全投影;AUTHORIZATION 同步冻结当前 `PLATFORM_ADMIN` 角色及 IAM 跨区前缀下的完整权限集合,保证空库重建后海外 Gateway 可立即鉴权。统一 SQL 重放只修正仍处于固定 baseline version=0 的该行,其他既有开发账号不建立兼容表或长期补投入口。 - 国内、海外 Compose 部署默认设置 `SPRING_SQL_INIT_MODE=never`,执行任一 SQL 均需用户单独授权。 - 该脚本汇总订单与客户、入库测量与计费及已实现国内货柜履约出仓能力的数据库基线、存量升级、IAM 权限资源和最终结构校验,适用于 MySQL 8.0。 - 正式上线前的数据库变更直接维护该文件中的当前基线、存量兼容处理和最终校验,不再创建按日期拆分的发布 SQL;正式上线后的迁移策略在生产发布规范中另行冻结。 @@ -239,7 +239,7 @@ SELECT - 派送三类资料当前基线包含 `ll_delivery_order.bol_no/documents_json` 与 `ll_delivery_document_revision`;海外固定入口和 Lastleg 模块基线同步维护这些定义。仅保存装车照片、BOL文件(含签名)、签收文件(POD签收/快递签收)三个类别,摘要按类别计数。资料历史不可变,文件对象由 File 按真实客户与派送单绑定;不按 BOL 文本复用文件,不生成旧类别迁移或清理脚本。 -- 海外仓出库与派送数据库部署仍只执行 `海外区域统一增量-MySQL8.sql`;空库先执行 Lastleg 与 Warehouse Overseas 当前基线,再完整执行固定入口并确认最末 `migration_status = PASS`。 +- 海外仓出库与派送数据库部署只执行 `海外区域统一增量-MySQL8.sql`;无论空库还是存量测试库都不得在此前单独执行 Lastleg 或 Warehouse Overseas 模块基线,完整执行固定入口并确认最末 `migration_status = PASS`。 - 该海外固定入口会在任何海外仓出库与派送 `ALTER` 与数据门禁前幂等创建排车 PDA、出库、派送、一件代发、异常订单和客户回访的完整当前结构,因此既有海外仓业务库不依赖模块基线或历史 `scripts/db/incremental.sql`。国内固定入口只保留 IAM 海外仓出库与派送权限目录,不创建或修改 `baiyue_lastleg` 业务表。 - 固定入口只补目标字段并做 fail-fast 校验,不回填、改号、清理或从其他表推断开发旧行。`ll_dispatch_pda_task.warehouse_id` 存在 `NULL`、空值或 `0`,或任一待收敛全局业务号/任务号、完成幂等键发生冲突时,脚本立即 `SIGNAL`;应重新造数,重置或清理仍需用户另行授权。 - 数据门禁通过后,脚本原位把 `dispatch_no`、`outbound_no`、排车 PDA `task_no`、出库 PDA `task_no` 收敛为全局唯一,并建立 `(customer_id, completion_idempotency_key)` 完成命令唯一约束;旧客户复合索引不保留。 diff --git a/deploy/sql/头程数据库统一增量-MySQL8.sql b/deploy/sql/头程数据库统一增量-MySQL8.sql index beb436e62..bf3caab7e 100644 --- a/deploy/sql/头程数据库统一增量-MySQL8.sql +++ b/deploy/sql/头程数据库统一增量-MySQL8.sql @@ -12831,6 +12831,40 @@ CALL queue_table_comment('baiyue_order', 'ord_order_status_history', '订单状 CALL queue_table_comment('baiyue_order', 'ord_outbox_event', '订单事务Outbox表:与订单业务变更在同一本地事务写入,供后台发布器可靠投递RocketMQ,保证数据库状态与领域事件最终一致;不是普通操作日志'); CALL queue_table_comment('baiyue_order', 'sys_audit_outbox', '可靠审计事件本地发送箱'); CALL queue_table_comment('baiyue_warehouse', 'sys_audit_outbox', '可靠审计事件本地发送箱'); + +USE baiyue_firstleg; + +CREATE TABLE IF NOT EXISTS fl_order_declaration_snapshot ( + tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和隔离键', + order_id BIGINT UNSIGNED NOT NULL COMMENT 'Order订单ID', + event_id VARCHAR(128) NOT NULL COMMENT 'Order Outbox事件ID', + order_version INT NOT NULL COMMENT 'Order聚合版本,严格单调消费', + request_hash CHAR(64) NOT NULL COMMENT '完整事件SHA-256', + customer_remark VARCHAR(1000) NULL COMMENT '客户备注快照', + expected_arrival_at DATETIME(3) NULL COMMENT '预计到仓时间快照', + channel_code VARCHAR(64) NOT NULL COMMENT '渠道编码快照', + channel_name VARCHAR(128) NOT NULL COMMENT '渠道名称快照', + cargoes_json JSON NOT NULL COMMENT '客户申报货物行完整快照,不覆盖实测事实', + changes_json JSON NULL COMMENT '本版本申报字段变化摘要', + occurred_at DATETIME(3) NOT NULL COMMENT '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 (tenant_id, order_id), + UNIQUE KEY uk_order_declaration_event (tenant_id, event_id), + KEY idx_order_declaration_version (tenant_id, order_id, order_version) +) ENGINE=InnoDB COMMENT='Order客户申报版本快照;与箱码、实测、托盘、PDA和计费事实隔离'; + +CREATE TABLE IF NOT EXISTS fl_order_declaration_event ( + tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和隔离键', + event_id VARCHAR(128) NOT NULL COMMENT '永久保留的Order Outbox事件ID', + order_id BIGINT UNSIGNED NOT NULL COMMENT 'Order订单ID', + order_version INT NOT NULL COMMENT '事件携带的Order聚合版本', + request_hash CHAR(64) NOT NULL COMMENT '完整事件SHA-256,用于异参重放拒绝', + consumed_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '事件消费完成时间', + PRIMARY KEY (tenant_id, event_id), + KEY idx_order_declaration_event_order (tenant_id, order_id, order_version) +) ENGINE=InnoDB COMMENT='Order客户申报事件永久幂等账本'; + CALL queue_table_comment('baiyue_firstleg', 'fl_order_declaration_event', 'Order客户申报事件永久幂等账本'); CALL queue_table_comment('baiyue_firstleg', 'fl_order_declaration_snapshot', 'Order客户申报版本快照;与箱码、实测、托盘、PDA和计费事实隔离'); CALL queue_table_comment('baiyue_warehouse', 'wh_express_company', '平台公共快递公司主数据,固定归属 tenant_id=0'); @@ -16873,6 +16907,12 @@ VALUES (5107061,0,'PLATFORM','BUTTON',5107060,'platform:overseas-warehouse:location:create','创建海外库位',NULL,'POST',761,'ACTIVE',1,0), (5107062,0,'PLATFORM','BUTTON',5107060,'platform:overseas-warehouse:location:edit','编辑海外库位',NULL,'PUT',762,'ACTIVE',1,0), (5107063,0,'PLATFORM','BUTTON',5107060,'platform:overseas-warehouse:location:delete','删除海外库位',NULL,'DELETE',763,'ACTIVE',1,0), + (5107230,0,'PLATFORM','MENU',0,'platform:overseas-warehouse:carton:list','海外箱号','/overseas-warehouse/cartons','GET',930,'ACTIVE',1,0), + (5107231,0,'PLATFORM','BUTTON',5107230,'platform:overseas-warehouse:carton:create','创建海外箱号',NULL,'POST',931,'ACTIVE',1,0), + (5107232,0,'PLATFORM','BUTTON',5107230,'platform:overseas-warehouse:carton:edit','编辑海外箱号',NULL,'PUT',932,'ACTIVE',1,0), + (5107240,0,'PLATFORM','MENU',0,'platform:overseas-warehouse:product:list','海外产品库','/overseas-warehouse/products','GET',940,'ACTIVE',1,0), + (5107241,0,'PLATFORM','BUTTON',5107240,'platform:overseas-warehouse:product:create','创建海外产品',NULL,'POST',941,'ACTIVE',1,0), + (5107242,0,'PLATFORM','BUTTON',5107240,'platform:overseas-warehouse:product:edit','编辑海外产品',NULL,'PUT',942,'ACTIVE',1,0), (5107090,0,'PLATFORM','MENU',0,'platform:overseas-warehouse:outbound:list','海外出库单','/overseas-warehouse/outbounds','GET',765,'ACTIVE',1,0), (5107091,0,'PLATFORM','BUTTON',5107090,'platform:overseas-warehouse:outbound:create','创建海外出库单',NULL,'POST',766,'ACTIVE',1,0), (5107092,0,'PLATFORM','MENU',0,'platform:overseas-warehouse:delivery:list','海外派送单','/overseas-warehouse/delivery-orders','GET',767,'ACTIVE',1,0), @@ -18111,7 +18151,12 @@ SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE tab PREPARE lx_order_ddl FROM @ddl; EXECUTE lx_order_ddl; DEALLOCATE PREPARE lx_order_ddl; SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='baiyue_firstleg' AND table_name='fl_inbound_order' AND column_name='lx_code'), 'SELECT 1', 'ALTER TABLE baiyue_firstleg.fl_inbound_order ADD COLUMN lx_code VARCHAR(128) NULL COMMENT ''一件代发领星入库单代码'' AFTER fulfillment_type')); PREPARE lx_inbound_ddl FROM @ddl; EXECUTE lx_inbound_ddl; DEALLOCATE PREPARE lx_inbound_ddl; -SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='baiyue_lastleg' AND table_name='ll_outbound_order' AND column_name='lx_codes'), 'SELECT 1', 'ALTER TABLE baiyue_lastleg.ll_outbound_order ADD COLUMN lx_codes VARCHAR(1000) NULL COMMENT ''关联一件代发领星入库单代码汇总'' AFTER outbound_type')); +SET @ddl = (SELECT IF( + NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema='baiyue_lastleg' AND table_name='ll_outbound_order') + OR EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='baiyue_lastleg' AND table_name='ll_outbound_order' AND column_name='lx_codes'), + 'SELECT 1', + 'ALTER TABLE baiyue_lastleg.ll_outbound_order ADD COLUMN lx_codes VARCHAR(1000) NULL COMMENT ''关联一件代发领星入库单代码汇总'' AFTER outbound_type' +)); PREPARE lx_outbound_ddl FROM @ddl; EXECUTE lx_outbound_ddl; DEALLOCATE PREPARE lx_outbound_ddl; ALTER TABLE baiyue_firstleg.fl_inbound_order diff --git a/deploy/sql/海外区域统一增量-MySQL8.sql b/deploy/sql/海外区域统一增量-MySQL8.sql index c9a6d0cdc..573d1ab37 100644 --- a/deploy/sql/海外区域统一增量-MySQL8.sql +++ b/deploy/sql/海外区域统一增量-MySQL8.sql @@ -1,8 +1,2459 @@ --- 海外区域固定增量入口。空库先执行 lastleg-service/src/main/resources/db/lastleg.sql 基线; --- 本入口同时初始化海外 Event Bridge 独立库,避免国内/海外实例误共用 Inbox。 +-- 海外区域唯一数据库初始化入口;空库和存量测试库均只执行本文件。 +-- 本文件内联 Lastleg 与海外仓空库基线,再执行统一增量和最终结构校验。 -- 本文件只由人工审核执行,服务部署默认不自动执行 SQL。 SET NAMES utf8mb4; +-- BEGIN INLINE EMPTY-DATABASE BASELINE: LASTLEG +SET NAMES utf8mb4; + +CREATE DATABASE IF NOT EXISTS baiyue_lastleg DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +USE baiyue_lastleg; + +CREATE TABLE IF NOT EXISTS ll_loading_event_inbox ( + event_id VARCHAR(128) NOT NULL COMMENT '事件全局唯一标识', + customer_id VARCHAR(128) NOT NULL COMMENT '租户/客户账号标识', + event_type VARCHAR(64) NOT NULL COMMENT '事件类型', + event_version INT NOT NULL COMMENT '事件契约版本', + payload_hash VARCHAR(71) NOT NULL COMMENT '规范化事件负载 SHA-256 摘要', + payload_json JSON NOT NULL COMMENT '事件冻结负载 JSON', + status VARCHAR(32) NOT NULL DEFAULT 'RECEIVED' COMMENT '消费状态:RECEIVED/PROCESSED', + received_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '接收时间', + processed_at DATETIME(3) NULL COMMENT '处理完成时间', + last_error VARCHAR(1000) NULL COMMENT '最近一次处理错误', + PRIMARY KEY(event_id), + UNIQUE KEY uk_ll_loading_inbox_tenant_key (customer_id,event_type,event_id) +) ENGINE=InnoDB COMMENT='头程装柜完成事件接收幂等箱'; + +CREATE TABLE IF NOT EXISTS ll_container_shipping_gate ( + container_booking_id VARCHAR(128) NOT NULL COMMENT '来源头程柜预订ID', + ocean_booking_id VARCHAR(128) NOT NULL COMMENT '来源海运订舱ID', container_no VARCHAR(64) NOT NULL COMMENT '实际集装箱号', + status VARCHAR(32) NOT NULL DEFAULT 'AWAITING_PICKUP_READY' COMMENT '海外收货门禁状态:AWAITING_PICKUP_READY、RECEIVING_AVAILABLE', + standard_node VARCHAR(64) NULL COMMENT '最新有效标准船节点', node_sequence INT NOT NULL DEFAULT 0 COMMENT '最新有效节点单调序号', + observed_at DATETIME(3) NULL COMMENT '供应商节点观测时间', actual_at DATETIME(3) NULL COMMENT '节点实际发生时间', + location_code VARCHAR(128) NULL COMMENT '节点地点编码', location_name VARCHAR(255) NULL COMMENT '节点地点名称', + provider_code VARCHAR(64) NULL COMMENT '船节点供应商编码', provider_event_id VARCHAR(128) NULL COMMENT '供应商事件ID', + provider_node_code VARCHAR(128) NULL COMMENT '供应商原始节点编码', source_reference VARCHAR(255) NULL COMMENT '供应商来源引用', + raw_payload_hash VARCHAR(64) NULL COMMENT '供应商原始负载SHA-256', aggregate_version BIGINT NOT NULL DEFAULT 0 COMMENT '来源查询聚合版本', + handover_version BIGINT NOT NULL DEFAULT 1 COMMENT '跨区交接版本', last_event_id VARCHAR(128) NULL COMMENT '最近采纳的跨区事件ID', + payload_hash VARCHAR(71) NULL 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(container_booking_id), KEY idx_ll_shipping_gate_candidate(container_no,status), + KEY idx_ll_shipping_gate_version(container_booking_id,aggregate_version,node_sequence) +) ENGINE=InnoDB COMMENT='海外PDA柜号船节点收货资格快照'; + +CREATE TABLE IF NOT EXISTS ll_order_no_daily_sequence ( + business_date DATE NOT NULL COMMENT '业务日期(Asia/Shanghai)', + last_sequence INT NOT NULL COMMENT '当日已分配尾程单流水号', + PRIMARY KEY(business_date) +) ENGINE=InnoDB COMMENT='尾程单号按日原子流水'; + +CREATE TABLE IF NOT EXISTS ll_order ( + id BIGINT NOT NULL COMMENT '雪花 ID', + order_no VARCHAR(32) NOT NULL COMMENT '尾程单号', + customer_id VARCHAR(128) NOT NULL COMMENT '租户/客户账号标识', + order_type VARCHAR(32) NOT NULL DEFAULT 'FIRST_LEG' COMMENT '订单类型:FIRST_LEG头程订单、RELABEL_APPLICATION换标下单、WAREHOUSE_TRANSFER仓储转运、LOCAL_LOOSEBOARD本土散板、RETURN_RELABEL退货换标、ONE_CLICK_DROPSHIP一件代发', + lx_code VARCHAR(128) NULL COMMENT '一件代发领星入库单代码', + destination_type VARCHAR(32) NULL COMMENT '目的类型:FBA按箱数接收、OVERSEAS逐箱扫码', + inbound_code VARCHAR(128) NULL COMMENT '真实来源入仓码;无入仓关系的海外本土业务为空', + source_region VARCHAR(32) NOT NULL COMMENT '来源区域/系统', + source_inbound_id VARCHAR(128) NOT NULL COMMENT '最终实装 LOADED 分片 ID,建单幂等键', + root_inbound_id VARCHAR(128) COMMENT '根入仓单 ID', + source_order_id VARCHAR(128) COMMENT '来源订单 ID', + source_order_no VARCHAR(128) COMMENT '来源订单号', + root_order_id VARCHAR(128) COMMENT '根订单 ID', + customer_name VARCHAR(255) COMMENT '货主名称', + source_warehouse_id VARCHAR(128) COMMENT '来源国内装柜仓 ID 快照', + source_warehouse_name VARCHAR(255) COMMENT '来源国内装柜仓名称快照', + overseas_warehouse_id VARCHAR(128) NOT NULL DEFAULT '193000000000200003' COMMENT '海外接柜仓ID,新产生普通尾程整柜默认洛杉矶仓', + overseas_warehouse_code VARCHAR(128) NOT NULL DEFAULT 'US-LAX-01' COMMENT '海外接柜仓业务编码快照', + overseas_warehouse_name VARCHAR(255) NOT NULL DEFAULT '洛杉矶仓' COMMENT '海外接柜仓名称快照', + receiving_warehouse_version INT NOT NULL DEFAULT 0 COMMENT '柜级接柜仓分配乐观锁版本', + transfer_warehouse_id VARCHAR(128) COMMENT '转入目的仓 ID', + transfer_warehouse_code VARCHAR(128) COMMENT '转入目的仓编码', + transfer_warehouse_name VARCHAR(255) COMMENT '转入目的仓名称', + fba_warehouse_code VARCHAR(128) COMMENT 'FBA 仓库编码', + container_no VARCHAR(64) COMMENT '集装箱号', + container_status VARCHAR(32) DEFAULT '待转柜' COMMENT '货柜状态', + abnormal TINYINT NOT NULL DEFAULT 0 COMMENT '是否异常:0否1是', + detained TINYINT NOT NULL DEFAULT 0 COMMENT '是否暂扣:0否1是', + hold_chargeable TINYINT NOT NULL DEFAULT 0 COMMENT '当前暂扣是否计费:0否1是', + hold_reason VARCHAR(500) NULL COMMENT '当前暂扣原因;解除后为空', + manual_node_override TINYINT NOT NULL DEFAULT 0 COMMENT '是否启用单票人工计划节点覆盖:0否1是,启用后不再继承柜级节点', + version INT NOT NULL DEFAULT 0 COMMENT '尾程订单命令乐观锁版本', + pallet_count INT NOT NULL DEFAULT 0 COMMENT '托数', + package_count INT NOT NULL DEFAULT 0 COMMENT '箱数', + picked_package_count INT NOT NULL DEFAULT 0 COMMENT '累计已拣箱数', + shipped_package_count INT NOT NULL DEFAULT 0 COMMENT '累计已出库箱数', + volume_cbm DECIMAL(18,4) COMMENT '体积(立方米)', + product_quantity INT COMMENT '货物数量', + volume_weight_kg DECIMAL(18,4) COMMENT '体积重量(千克)', + estimated_arrival_at DATETIME(3) COMMENT '预计到港时间', + actual_arrival_at DATETIME(3) COMMENT '实际到港时间', + estimated_warehouse_at DATETIME(3) COMMENT '预计到仓时间', + inbound_order_no VARCHAR(128) COMMENT '入库单号', + outbound_order_no VARCHAR(128) COMMENT '出库单号', + delivery_order_no VARCHAR(128) COMMENT '派送单号', + relabel_order_no VARCHAR(128) COMMENT '换标单号', + relabel_status VARCHAR(32) COMMENT '换标状态', + pallet_merge_flag VARCHAR(32) COMMENT '合托标记', + pallet_merge_batch_id VARCHAR(64) NULL COMMENT '平台预标记合托分组号', + current_status VARCHAR(32) NOT NULL DEFAULT 'PENDING_ASSIGNMENT' COMMENT '当前业务状态:PENDING_ASSIGNMENT待分配、PENDING_INBOUND待入库、INBOUND_COMPLETED入库完成、PENDING_DELIVERY待派送、DELIVERING派送中、DELIVERED派送完成', + sub_order_status VARCHAR(255) NULL COMMENT '子单据状态汇总', + private_remark VARCHAR(1000) COMMENT '备注(仅自己)', + customer_remark VARCHAR(1000) COMMENT '备注(客户)', + warehouse_remark VARCHAR(1000) COMMENT '备注(仓库)', + source_event_id VARCHAR(128) NOT NULL COMMENT '来源事件 ID', + source_loading_plan_id VARCHAR(128) NOT NULL COMMENT '来源装柜计划 ID', + source_container_booking_id VARCHAR(128) NULL COMMENT '来源头程货柜预订ID', + source_loading_plan_version BIGINT COMMENT '来源装柜计划版本', + source_handover_version BIGINT COMMENT '交接版本', + source_payload_hash VARCHAR(71) NOT NULL 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_order_source (customer_id,source_region,source_inbound_id), + KEY idx_ll_order_group(customer_id,order_no), + KEY idx_ll_order_container(customer_id,container_no), + KEY idx_ll_order_list(customer_id,current_status,created_at), + KEY idx_ll_order_container_progress(source_container_booking_id,estimated_warehouse_at,created_at,id) +) ENGINE=InnoDB COMMENT='尾程订单主表'; + +CREATE TABLE IF NOT EXISTS ll_order_source_pallet ( + id BIGINT NOT NULL COMMENT '来源托盘快照ID', + customer_id VARCHAR(128) NOT NULL COMMENT '客户隔离键', + order_id BIGINT NOT NULL COMMENT '尾程订单ID', + source_pallet_no VARCHAR(128) NOT NULL COMMENT '头程冻结托盘码', + sequence_no INT NOT NULL COMMENT '订单内稳定顺序', + source_event_id VARCHAR(128) NOT NULL COMMENT '来源装柜完成事件ID', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + PRIMARY KEY(id), + UNIQUE KEY uk_ll_source_pallet_no(source_pallet_no), + UNIQUE KEY uk_ll_source_pallet_sequence(customer_id,order_id,sequence_no), + KEY idx_ll_source_pallet_order(customer_id,order_id), + CONSTRAINT fk_ll_source_pallet_order FOREIGN KEY(order_id) REFERENCES ll_order(id) +) ENGINE=InnoDB COMMENT='头程交接的不可变托盘码快照'; + + +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_order' AND column_name='pallet_merge_batch_id'), 'SELECT 1', 'ALTER TABLE ll_order ADD COLUMN pallet_merge_batch_id VARCHAR(64) NULL COMMENT ''平台预标记合托分组号'' AFTER pallet_merge_flag')); +PREPARE ll_order_merge_batch_ddl FROM @ddl; EXECUTE ll_order_merge_batch_ddl; DEALLOCATE PREPARE ll_order_merge_batch_ddl; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_order' AND column_name='hold_chargeable'), 'SELECT 1', 'ALTER TABLE ll_order ADD COLUMN hold_chargeable TINYINT NOT NULL DEFAULT 0 COMMENT ''当前暂扣是否计费:0否1是'' AFTER detained')); +PREPARE ll_order_command_ddl FROM @ddl; EXECUTE ll_order_command_ddl; DEALLOCATE PREPARE ll_order_command_ddl; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_order' AND column_name='hold_reason'), 'SELECT 1', 'ALTER TABLE ll_order ADD COLUMN hold_reason VARCHAR(500) NULL COMMENT ''当前暂扣原因;解除后为空'' AFTER hold_chargeable')); +PREPARE ll_order_command_ddl FROM @ddl; EXECUTE ll_order_command_ddl; DEALLOCATE PREPARE ll_order_command_ddl; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_order' AND column_name='manual_node_override'), 'SELECT 1', 'ALTER TABLE ll_order ADD COLUMN manual_node_override TINYINT NOT NULL DEFAULT 0 COMMENT ''是否启用单票人工计划节点覆盖:0否1是'' AFTER hold_reason')); +PREPARE ll_order_command_ddl FROM @ddl; EXECUTE ll_order_command_ddl; DEALLOCATE PREPARE ll_order_command_ddl; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_order' AND column_name='version'), 'SELECT 1', 'ALTER TABLE ll_order ADD COLUMN version INT NOT NULL DEFAULT 0 COMMENT ''尾程订单命令乐观锁版本'' AFTER manual_node_override')); +PREPARE ll_order_command_ddl FROM @ddl; EXECUTE ll_order_command_ddl; DEALLOCATE PREPARE ll_order_command_ddl; + +CREATE TABLE IF NOT EXISTS ll_order_detail ( + id BIGINT NOT NULL AUTO_INCREMENT COMMENT '订单明细主键', + customer_id VARCHAR(128) NOT NULL COMMENT '客户数据隔离键', + order_id BIGINT NOT NULL COMMENT '尾程订单 ID', + source_inbound_detail_id VARCHAR(128) NOT NULL COMMENT '来源入仓明细 ID', + sku_code VARCHAR(128) COMMENT 'SKU 编码', + product_name_zh VARCHAR(255) COMMENT '中文品名', + product_name_en VARCHAR(255) COMMENT '英文品名', + package_count INT NOT NULL DEFAULT 0 COMMENT '箱数', + product_quantity INT COMMENT '数量', + pallet_count INT COMMENT '托数', + gross_weight_kg DECIMAL(18,4) COMMENT '毛重(千克)', + net_weight_kg DECIMAL(18,4) COMMENT '净重(千克)', + dimensional_weight_kg DECIMAL(18,4) COMMENT '体积重量(千克)', + volume_cbm DECIMAL(18,4) COMMENT '体积(立方米)', + destination_warehouse_id VARCHAR(128) COMMENT '目的仓 ID', + destination_warehouse_code VARCHAR(128) COMMENT '目的仓编码', + destination_warehouse_name VARCHAR(255) COMMENT '目的仓名称', + fba_tracking_no VARCHAR(255) COMMENT 'FBA跟踪号', + shipment_no VARCHAR(255) COMMENT '货件编号', + shipping_mark VARCHAR(255) COMMENT '唛头', + PRIMARY KEY(id), + UNIQUE KEY uk_ll_detail_source(order_id,source_inbound_detail_id), + KEY idx_ll_detail_order(order_id), + KEY idx_ll_detail_customer_order(customer_id,order_id,id) +) ENGINE=InnoDB COMMENT='尾程订单实装商品冻结明细'; + +CREATE TABLE IF NOT EXISTS ll_loading_order_status_history ( + id BIGINT NOT NULL AUTO_INCREMENT COMMENT '状态历史主键', + customer_id VARCHAR(128) NOT NULL COMMENT '客户数据隔离键', + order_id BIGINT NOT NULL COMMENT '尾程订单 ID', + from_status VARCHAR(32) COMMENT '变更前状态', + to_status VARCHAR(32) NOT NULL COMMENT '变更后状态', + event_id VARCHAR(128) COMMENT '触发事件 ID', + remark VARCHAR(500) COMMENT '状态变更备注', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '记录时间', + PRIMARY KEY(id), + KEY idx_ll_loading_history_customer_order(customer_id,order_id,created_at) +) ENGINE=InnoDB COMMENT='尾程订单状态变更历史'; + +CREATE TABLE IF NOT EXISTS ll_order_operation_log ( + id BIGINT NOT NULL COMMENT '操作记录主键', + customer_id VARCHAR(128) NOT NULL COMMENT '客户数据隔离键', + order_id BIGINT NOT NULL COMMENT '尾程订单ID', + operation_node VARCHAR(64) NOT NULL COMMENT '操作节点', + operator_name VARCHAR(128) NOT NULL COMMENT '操作人名称', + result VARCHAR(500) NOT NULL COMMENT '处理结果', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '操作时间', + PRIMARY KEY(id), + KEY idx_ll_order_operation_customer_order(customer_id,order_id,created_at) +) ENGINE=InnoDB COMMENT='尾程订单操作记录'; + +CREATE TABLE IF NOT EXISTS ll_order_command_record ( + id BIGINT NOT NULL COMMENT '尾程订单命令幂等记录ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户数据隔离键', + order_id BIGINT NOT NULL COMMENT '尾程订单ID', action VARCHAR(32) NOT NULL COMMENT '命令动作:MANUAL_PLAN、HOLD、MANUAL_NODES', + operator_id VARCHAR(128) NOT NULL COMMENT '平台操作人ID', idempotency_key VARCHAR(128) NOT NULL COMMENT '客户端幂等键', + request_hash VARCHAR(64) NOT NULL COMMENT '规范化请求SHA-256摘要', result_ref VARCHAR(128) NULL COMMENT '结果业务引用', + result_json LONGTEXT NOT NULL COMMENT '首次成功响应JSON快照', result_version INT NOT NULL COMMENT '命令成功后的订单版本', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '首次成功时间', PRIMARY KEY(id), + UNIQUE KEY uk_ll_order_command_key(customer_id,order_id,action,operator_id,idempotency_key), + KEY idx_ll_order_command_history(customer_id,order_id,created_at) +) ENGINE=InnoDB COMMENT='尾程订单写命令幂等与结果审计记录'; + +CREATE TABLE IF NOT EXISTS ll_order_hold_history ( + id BIGINT NOT NULL COMMENT '尾程订单暂扣历史ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户数据隔离键', + order_id BIGINT NOT NULL COMMENT '尾程订单ID', held TINYINT NOT NULL COMMENT '操作后是否暂扣:0否1是', + chargeable TINYINT NOT NULL COMMENT '本次暂扣是否计费:0否1是', reason VARCHAR(500) NOT NULL COMMENT '暂扣或解除原因', + order_version INT NOT NULL COMMENT '操作成功后的订单版本', operator_id VARCHAR(128) NOT NULL COMMENT '操作人ID', + operator_name VARCHAR(128) NOT NULL COMMENT '操作人名称', idempotency_key VARCHAR(128) NOT NULL COMMENT '客户端幂等键', + created_at DATETIME(3) NOT NULL COMMENT '操作时间', PRIMARY KEY(id), + UNIQUE KEY uk_ll_order_hold_key(customer_id,order_id,operator_id,idempotency_key), + KEY idx_ll_order_hold_history(customer_id,order_id,created_at) +) ENGINE=InnoDB COMMENT='尾程订单暂扣与解除不可变历史'; + +CREATE TABLE IF NOT EXISTS ll_order_manual_node ( + id BIGINT NOT NULL COMMENT '尾程订单人工节点ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户数据隔离键', + order_id BIGINT NOT NULL COMMENT '尾程订单ID', node_code VARCHAR(32) NOT NULL COMMENT '八行人工展示节点稳定代码,与Carrier标准节点隔离', + node_at DATETIME(3) NULL COMMENT '人工填写节点时间,允许为空', source VARCHAR(16) NOT NULL DEFAULT 'MANUAL' COMMENT '节点来源,固定MANUAL', + order_version INT NOT NULL COMMENT '最近编辑后的订单版本', updated_by VARCHAR(128) NOT NULL COMMENT '最近操作人ID', + updated_at DATETIME(3) NOT NULL COMMENT '最近编辑时间', PRIMARY KEY(id), + UNIQUE KEY uk_ll_order_manual_node(customer_id,order_id,node_code), KEY idx_ll_order_manual_node_order(customer_id,order_id,node_at) +) ENGINE=InnoDB COMMENT='尾程订单当前人工节点覆盖事实'; + +CREATE TABLE IF NOT EXISTS ll_order_manual_node_history ( + id BIGINT NOT NULL COMMENT '尾程订单人工节点历史ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户数据隔离键', + order_id BIGINT NOT NULL COMMENT '尾程订单ID', nodes_json LONGTEXT NOT NULL COMMENT '本次全量人工节点JSON快照', + order_version INT NOT NULL COMMENT '操作成功后的订单版本', operator_id VARCHAR(128) NOT NULL COMMENT '操作人ID', + operator_name VARCHAR(128) NOT NULL COMMENT '操作人名称', idempotency_key VARCHAR(128) NOT NULL COMMENT '客户端幂等键', + created_at DATETIME(3) NOT NULL COMMENT '操作时间', PRIMARY KEY(id), + UNIQUE KEY uk_ll_order_manual_node_history_key(customer_id,order_id,operator_id,idempotency_key), + KEY idx_ll_order_manual_node_history(customer_id,order_id,created_at) +) ENGINE=InnoDB COMMENT='尾程订单人工节点覆盖不可变历史'; + +CREATE TABLE IF NOT EXISTS ll_receiving_warehouse_operation ( + id BIGINT NOT NULL COMMENT '柜级接柜仓修改操作ID', + source_container_booking_id VARCHAR(128) NOT NULL COMMENT '稳定来源柜预订ID', + idempotency_key VARCHAR(255) NOT NULL COMMENT '平台请求幂等键', + request_hash VARCHAR(64) NOT NULL COMMENT '规范化修改请求SHA-256摘要', + result_version INT NOT NULL COMMENT '修改成功后的柜级分配版本', + operator_name VARCHAR(128) NOT NULL COMMENT '平台操作人名称', + remark VARCHAR(500) NULL COMMENT '接柜仓修改备注', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '操作时间', + PRIMARY KEY(id), + UNIQUE KEY uk_ll_receiving_warehouse_idempotency(idempotency_key), + KEY idx_ll_receiving_warehouse_container(source_container_booking_id,created_at) +) ENGINE=InnoDB COMMENT='柜级海外接柜仓修改幂等审计记录'; + +-- 派送单列表与详情快照、转派送费用及操作记录。 +CREATE TABLE IF NOT EXISTS ll_delivery_order ( + id BIGINT NOT NULL COMMENT '派送单主键ID', + customer_id VARCHAR(128) NOT NULL COMMENT '客户数据隔离键', + delivery_no VARCHAR(64) NOT NULL COMMENT '派送单号', + outbound_order_id BIGINT NOT NULL COMMENT '关联出库单ID', + outbound_no VARCHAR(64) NOT NULL COMMENT '关联出库单号', + dispatch_order_id BIGINT NOT NULL COMMENT '关联派车单ID', + dispatch_no VARCHAR(64) NOT NULL COMMENT '关联派车单号', + overseas_warehouse_id VARCHAR(128) NULL COMMENT '海外仓ID', + overseas_warehouse_name VARCHAR(255) NULL COMMENT '海外仓名称快照', + customer_name VARCHAR(255) NULL COMMENT '客户名称快照', + status VARCHAR(32) NOT NULL DEFAULT 'WAIT_DELIVERY' COMMENT '派送状态:WAIT_DELIVERY待派送及后续正式状态', + waybill_no VARCHAR(128) NULL COMMENT '承运运单号', + bol_no VARCHAR(128) NULL COMMENT '派送资料BOL号,不作为文件归属标识', + documents_json JSON NULL COMMENT '派送三类资料当前绑定及权威文件元数据快照', + delivery_method VARCHAR(32) NULL COMMENT '派送方式', + carrier_name VARCHAR(255) NULL COMMENT '派送承运商', + pickup_method VARCHAR(64) NULL COMMENT '提货方式', + loading_type VARCHAR(64) NULL COMMENT '装车类型', + vehicle_type VARCHAR(64) NULL COMMENT '车辆类型', + vehicle_size VARCHAR(64) NULL COMMENT '车辆大小', + vehicle_plate VARCHAR(64) NULL COMMENT '车牌号', + driver_name VARCHAR(128) NULL COMMENT '司机或车队名称', + driver_phone VARCHAR(64) NULL COMMENT '车队联系电话', + appointment_account VARCHAR(128) NULL COMMENT '预约账号', + seller_name VARCHAR(128) NULL COMMENT '卖家姓名', + package_count INT NOT NULL DEFAULT 0 COMMENT '派送箱数', + pallet_count INT NOT NULL DEFAULT 0 COMMENT '派送托盘数', + total_weight_kg DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT '总重量(千克)', + total_volume_cbm DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT '总体积(立方米)', + planned_delivery_at DATETIME(3) NULL COMMENT '预计派送时间', + dispatched_at DATETIME(3) NULL COMMENT '实际派送时间', + expected_arrival_at DATETIME(3) NULL COMMENT '预计到达时间', + signed_at DATETIME(3) NULL COMMENT '签收时间', + remark VARCHAR(1000) NULL COMMENT '派送备注', + idempotency_key VARCHAR(255) NOT NULL COMMENT '业务幂等键', + created_by VARCHAR(128) NULL COMMENT '创建人ID', + 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_delivery_no (customer_id,delivery_no), + UNIQUE KEY uk_ll_delivery_idempotency (customer_id,idempotency_key), + UNIQUE KEY uk_ll_delivery_outbound (customer_id,outbound_order_id), + KEY idx_ll_delivery_list (customer_id,status,created_at) +) ENGINE=InnoDB COMMENT='尾程派送单'; + +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_delivery_order' AND column_name='overseas_warehouse_name'), 'SELECT 1', 'ALTER TABLE ll_delivery_order ADD COLUMN overseas_warehouse_name VARCHAR(255) NULL COMMENT ''海外仓名称快照'' AFTER overseas_warehouse_id')); +PREPARE delivery_detail_stmt FROM @ddl; EXECUTE delivery_detail_stmt; DEALLOCATE PREPARE delivery_detail_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_delivery_order' AND column_name='customer_name'), 'SELECT 1', 'ALTER TABLE ll_delivery_order ADD COLUMN customer_name VARCHAR(255) NULL COMMENT ''客户名称快照'' AFTER overseas_warehouse_name')); +PREPARE delivery_detail_stmt FROM @ddl; EXECUTE delivery_detail_stmt; DEALLOCATE PREPARE delivery_detail_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_delivery_order' AND column_name='waybill_no'), 'SELECT 1', 'ALTER TABLE ll_delivery_order ADD COLUMN waybill_no VARCHAR(128) NULL COMMENT ''承运运单号'', ADD COLUMN delivery_method VARCHAR(32) NULL COMMENT ''派送方式'', ADD COLUMN carrier_name VARCHAR(255) NULL COMMENT ''派送承运商'', ADD COLUMN pickup_method VARCHAR(64) NULL COMMENT ''提货方式'', ADD COLUMN loading_type VARCHAR(64) NULL COMMENT ''装车类型'', ADD COLUMN vehicle_type VARCHAR(64) NULL COMMENT ''车辆类型'', ADD COLUMN vehicle_size VARCHAR(64) NULL COMMENT ''车辆大小'', ADD COLUMN vehicle_plate VARCHAR(64) NULL COMMENT ''车牌号'', ADD COLUMN driver_name VARCHAR(128) NULL COMMENT ''司机或车队名称'', ADD COLUMN driver_phone VARCHAR(64) NULL COMMENT ''车队联系电话'', ADD COLUMN appointment_account VARCHAR(128) NULL COMMENT ''预约账号'', ADD COLUMN seller_name VARCHAR(128) NULL COMMENT ''卖家姓名'' AFTER status')); +PREPARE delivery_detail_stmt FROM @ddl; EXECUTE delivery_detail_stmt; DEALLOCATE PREPARE delivery_detail_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_delivery_order' AND column_name='total_weight_kg'), 'SELECT 1', 'ALTER TABLE ll_delivery_order ADD COLUMN total_weight_kg DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT ''总重量(千克)'', ADD COLUMN total_volume_cbm DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT ''总体积(立方米)'', ADD COLUMN planned_delivery_at DATETIME(3) NULL COMMENT ''预计派送时间'', ADD COLUMN dispatched_at DATETIME(3) NULL COMMENT ''实际派送时间'', ADD COLUMN expected_arrival_at DATETIME(3) NULL COMMENT ''预计到达时间'', ADD COLUMN signed_at DATETIME(3) NULL COMMENT ''签收时间'', ADD COLUMN remark VARCHAR(1000) NULL COMMENT ''派送备注'' AFTER pallet_count')); +PREPARE delivery_detail_stmt FROM @ddl; EXECUTE delivery_detail_stmt; DEALLOCATE PREPARE delivery_detail_stmt; +CREATE TABLE IF NOT EXISTS ll_delivery_document_revision ( + id BIGINT NOT NULL COMMENT '派送资料历史ID', + customer_id VARCHAR(128) NOT NULL COMMENT '派送所属客户隔离键', + delivery_order_id BIGINT NOT NULL COMMENT '派送单ID', + version BIGINT NOT NULL COMMENT '保存成功时派送单版本', + idempotency_key VARCHAR(128) NOT NULL COMMENT '资料保存幂等键', + request_hash VARCHAR(64) NOT NULL COMMENT '完整保存请求哈希', + snapshot_json JSON NOT NULL COMMENT '不可变三类资料及BOL号结果快照', + operator_id VARCHAR(128) NOT NULL COMMENT '保存操作人ID', + created_at DATETIME(3) NOT NULL COMMENT '资料保存时间', + PRIMARY KEY(id), + UNIQUE KEY uk_delivery_document_command(customer_id,delivery_order_id,idempotency_key), + UNIQUE KEY uk_delivery_document_version(customer_id,delivery_order_id,version) +) ENGINE=InnoDB COMMENT='派送资料版本历史与幂等保存记录'; + +CREATE TABLE IF NOT EXISTS ll_delivery_operation ( + id BIGINT NOT NULL COMMENT '派送操作主键', customer_id VARCHAR(128) NOT NULL COMMENT '客户数据隔离键', delivery_order_id BIGINT NOT NULL COMMENT '派送单ID', + operation_node VARCHAR(64) NOT NULL COMMENT '操作节点', operator_id VARCHAR(128) NULL COMMENT '操作人ID', operator_name VARCHAR(128) NULL COMMENT '操作人名称', + result VARCHAR(500) NOT NULL COMMENT '处理结果', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '操作时间', + PRIMARY KEY(id), KEY idx_ll_delivery_operation_order(customer_id,delivery_order_id,created_at,id) +) ENGINE=InnoDB COMMENT='派送单操作记录'; + +CREATE TABLE IF NOT EXISTS ll_event_conflict ( + id BIGINT NOT NULL COMMENT '冲突记录主键', event_id VARCHAR(128) NOT NULL COMMENT '冲突事件 ID', + customer_id BIGINT NOT NULL COMMENT '租户 ID', conflict_type VARCHAR(64) NOT NULL COMMENT '冲突类型', + conflict_message VARCHAR(512) NOT NULL COMMENT '冲突说明', created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY(id), KEY idx_ll_conflict_event(event_id,customer_id) +) ENGINE=InnoDB COMMENT='事件冲突记录'; + +CREATE TABLE IF NOT EXISTS ll_event_inbox ( + id BIGINT NOT NULL COMMENT '事件接收记录主键', event_id VARCHAR(128) NOT NULL COMMENT '事件全局唯一 ID', + customer_id BIGINT NOT NULL COMMENT '租户 ID', event_type VARCHAR(128) NOT NULL COMMENT '事件类型', + aggregate_type VARCHAR(64) NOT NULL COMMENT '聚合类型', aggregate_id VARCHAR(128) NOT NULL COMMENT '聚合 ID', + aggregate_version BIGINT NOT NULL COMMENT '聚合版本', payload_hash VARCHAR(71) NOT NULL COMMENT '负载摘要', + status VARCHAR(32) NOT NULL COMMENT '消费状态', error_message VARCHAR(512) NULL COMMENT '错误信息', + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY(id), UNIQUE KEY uk_ll_inbox_event(event_id), KEY idx_ll_inbox_source(customer_id,aggregate_id) +) ENGINE=InnoDB COMMENT='通用事件接收幂等箱'; + +CREATE TABLE IF NOT EXISTS ll_inbound_order ( + id BIGINT NOT NULL COMMENT '入仓单主键', customer_id BIGINT NOT NULL COMMENT '客户账号标识', source_region VARCHAR(32) NOT NULL COMMENT '来源区域', + source_inbound_id VARCHAR(128) NOT NULL COMMENT '来源入仓分片 ID', root_inbound_id VARCHAR(128) NULL COMMENT '根入仓单 ID', + source_order_id VARCHAR(128) NULL COMMENT '来源订单 ID', source_order_no VARCHAR(128) NULL COMMENT '来源订单号', root_order_id VARCHAR(128) NULL COMMENT '根订单 ID', + inbound_code VARCHAR(128) NULL COMMENT '入仓码', lx_code VARCHAR(128) NULL COMMENT '一件代发领星入库单代码', destination_warehouse_id VARCHAR(128) NULL COMMENT '目的仓 ID', + destination_warehouse_code VARCHAR(128) NULL COMMENT '目的仓编码', destination_warehouse_name VARCHAR(255) NULL COMMENT '目的仓名称', + destination_type VARCHAR(32) NULL COMMENT '目的类型:FBA按箱数接收、OVERSEAS逐箱扫码', + package_count INT NOT NULL DEFAULT 0 COMMENT '箱数', product_quantity INT NULL COMMENT '数量', pallet_count INT NULL COMMENT '托数', + gross_weight_kg DECIMAL(18,3) NULL COMMENT '毛重(千克)', net_weight_kg DECIMAL(18,3) NULL COMMENT '净重(千克)', volume_cbm DECIMAL(18,6) NULL COMMENT '体积(立方米)', + source_event_id VARCHAR(128) NOT NULL COMMENT '来源事件 ID', source_loading_plan_id VARCHAR(128) NOT NULL COMMENT '装柜计划 ID', + source_loading_plan_version BIGINT NOT NULL COMMENT '装柜计划版本', handover_version BIGINT NOT NULL COMMENT '交接版本', payload_hash VARCHAR(71) NOT NULL COMMENT '负载摘要', + status VARCHAR(32) NOT NULL COMMENT '入仓单状态', exception_flag VARCHAR(32) NOT NULL DEFAULT 'NONE' COMMENT '异常标记', + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY(id), UNIQUE KEY uk_ll_order_source(customer_id,source_region,source_inbound_id), UNIQUE KEY uk_ll_order_event(source_event_id,source_inbound_id) +) ENGINE=InnoDB COMMENT='尾程入仓单快照'; + +CREATE TABLE IF NOT EXISTS ll_inbound_order_item ( + id BIGINT NOT NULL COMMENT '入仓明细主键', customer_id BIGINT NOT NULL COMMENT '租户 ID', order_id BIGINT NOT NULL COMMENT '入仓单 ID', + source_inbound_detail_id VARCHAR(128) NOT NULL COMMENT '来源入仓明细 ID', package_count INT NOT NULL COMMENT '箱数', product_quantity INT NULL COMMENT '数量', pallet_count INT NULL COMMENT '托数', + gross_weight_kg DECIMAL(18,3) NULL COMMENT '毛重(千克)', net_weight_kg DECIMAL(18,3) NULL COMMENT '净重(千克)', dimensional_weight_kg DECIMAL(18,3) NULL COMMENT '体积重量(千克)', volume_cbm DECIMAL(18,6) NULL COMMENT '体积(立方米)', + destination_warehouse_id VARCHAR(128) NULL COMMENT '目的仓 ID', destination_warehouse_code VARCHAR(128) NULL COMMENT '目的仓编码', destination_warehouse_name VARCHAR(255) NULL COMMENT '目的仓名称', + fba_tracking_no VARCHAR(128) NULL COMMENT 'FBA跟踪号', shipment_no VARCHAR(128) NULL COMMENT '货件编号', shipping_mark VARCHAR(255) NULL COMMENT '唛头', + sku_code VARCHAR(128) NULL COMMENT 'SKU 编码', product_name_zh VARCHAR(255) NULL COMMENT '中文品名', product_name_en VARCHAR(255) NULL COMMENT '英文品名', + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY(id), KEY idx_ll_item_order(customer_id,order_id) +) ENGINE=InnoDB COMMENT='尾程入仓商品明细快照'; + +CREATE TABLE IF NOT EXISTS ll_order_status_history ( + id BIGINT NOT NULL COMMENT '订单状态历史主键', customer_id BIGINT NOT NULL COMMENT '租户 ID', order_id BIGINT NOT NULL COMMENT '订单 ID', + from_status VARCHAR(32) NULL COMMENT '变更前状态', to_status VARCHAR(32) NOT NULL COMMENT '变更后状态', reason VARCHAR(255) NULL COMMENT '变更原因', + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录时间', PRIMARY KEY(id) +) ENGINE=InnoDB COMMENT='尾程订单状态历史'; + +-- 幂等注释迁移:CREATE TABLE IF NOT EXISTS 对已有表不会更新表/字段注释。 +ALTER TABLE ll_loading_event_inbox COMMENT='头程装柜完成事件接收幂等箱'; +ALTER TABLE ll_order COMMENT='尾程订单主表'; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_order' AND column_name='source_container_booking_id'), 'SELECT 1', 'ALTER TABLE ll_order ADD COLUMN source_container_booking_id VARCHAR(128) NULL COMMENT ''来源头程货柜预订ID'' AFTER source_loading_plan_id')); +PREPARE lastleg_order_container_booking_stmt FROM @ddl; EXECUTE lastleg_order_container_booking_stmt; DEALLOCATE PREPARE lastleg_order_container_booking_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_order' AND index_name='idx_ll_order_pending_container'), 'ALTER TABLE ll_order DROP INDEX idx_ll_order_pending_container', 'SELECT 1')); +PREPARE lastleg_order_pending_container_idx_stmt FROM @ddl; EXECUTE lastleg_order_pending_container_idx_stmt; DEALLOCATE PREPARE lastleg_order_pending_container_idx_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_order' AND index_name='idx_ll_order_container_progress'), 'SELECT 1', 'ALTER TABLE ll_order ADD INDEX idx_ll_order_container_progress(source_container_booking_id,estimated_warehouse_at,created_at,id)')); +PREPARE lastleg_order_container_progress_idx_stmt FROM @ddl; EXECUTE lastleg_order_container_progress_idx_stmt; DEALLOCATE PREPARE lastleg_order_container_progress_idx_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_order' AND column_name='destination_type'), 'SELECT 1', 'ALTER TABLE ll_order ADD COLUMN destination_type VARCHAR(32) NULL COMMENT ''目的类型:FBA按箱数接收、OVERSEAS逐箱扫码'' AFTER order_type')); +PREPARE lastleg_order_destination_type_stmt FROM @ddl; EXECUTE lastleg_order_destination_type_stmt; DEALLOCATE PREPARE lastleg_order_destination_type_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_order' AND column_name='source_warehouse_id'), 'SELECT 1', 'ALTER TABLE ll_order ADD COLUMN source_warehouse_id VARCHAR(128) NULL COMMENT ''来源国内装柜仓 ID 快照'' AFTER customer_name')); +PREPARE lastleg_order_source_warehouse_stmt FROM @ddl; EXECUTE lastleg_order_source_warehouse_stmt; DEALLOCATE PREPARE lastleg_order_source_warehouse_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_order' AND column_name='source_warehouse_name'), 'SELECT 1', 'ALTER TABLE ll_order ADD COLUMN source_warehouse_name VARCHAR(255) NULL COMMENT ''来源国内装柜仓名称快照'' AFTER source_warehouse_id')); +PREPARE lastleg_order_source_warehouse_stmt FROM @ddl; EXECUTE lastleg_order_source_warehouse_stmt; DEALLOCATE PREPARE lastleg_order_source_warehouse_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inbound_order' AND column_name='destination_type'), 'SELECT 1', 'ALTER TABLE ll_inbound_order ADD COLUMN destination_type VARCHAR(32) NULL COMMENT ''目的类型:FBA按箱数接收、OVERSEAS逐箱扫码'' AFTER destination_warehouse_name')); +PREPARE lastleg_inbound_destination_type_stmt FROM @ddl; EXECUTE lastleg_inbound_destination_type_stmt; DEALLOCATE PREPARE lastleg_inbound_destination_type_stmt; +ALTER TABLE ll_order_detail COMMENT='尾程订单实装商品冻结明细'; +ALTER TABLE ll_loading_order_status_history COMMENT='尾程订单状态变更历史'; +ALTER TABLE ll_event_conflict COMMENT='事件冲突记录'; +ALTER TABLE ll_event_inbox COMMENT='通用事件接收幂等箱'; +ALTER TABLE ll_inbound_order COMMENT='尾程入仓单快照'; +ALTER TABLE ll_inbound_order_item COMMENT='尾程入仓商品明细快照'; +ALTER TABLE ll_order_status_history COMMENT='尾程订单状态历史'; + +ALTER TABLE ll_event_conflict + MODIFY COLUMN id BIGINT NOT NULL COMMENT '冲突记录主键', MODIFY COLUMN event_id VARCHAR(128) NOT NULL COMMENT '冲突事件 ID', + MODIFY COLUMN customer_id BIGINT NOT NULL COMMENT '租户 ID', MODIFY COLUMN conflict_type VARCHAR(64) NOT NULL COMMENT '冲突类型', + MODIFY COLUMN conflict_message VARCHAR(512) NOT NULL COMMENT '冲突说明', MODIFY COLUMN created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'; +ALTER TABLE ll_event_inbox + MODIFY COLUMN id BIGINT NOT NULL COMMENT '事件接收记录主键', MODIFY COLUMN event_id VARCHAR(128) NOT NULL COMMENT '事件全局唯一 ID', MODIFY COLUMN customer_id BIGINT NOT NULL COMMENT '租户 ID', + MODIFY COLUMN event_type VARCHAR(128) NOT NULL COMMENT '事件类型', MODIFY COLUMN aggregate_type VARCHAR(64) NOT NULL COMMENT '聚合类型', MODIFY COLUMN aggregate_id VARCHAR(128) NOT NULL COMMENT '聚合 ID', MODIFY COLUMN aggregate_version BIGINT NOT NULL COMMENT '聚合版本', MODIFY COLUMN payload_hash VARCHAR(71) NOT NULL COMMENT '负载摘要', MODIFY COLUMN status VARCHAR(32) NOT NULL COMMENT '消费状态', MODIFY COLUMN error_message VARCHAR(512) NULL COMMENT '错误信息', MODIFY COLUMN created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', MODIFY COLUMN updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'; +ALTER TABLE ll_inbound_order + MODIFY COLUMN id BIGINT NOT NULL COMMENT '入仓单主键', MODIFY COLUMN customer_id BIGINT NOT NULL COMMENT '租户 ID', MODIFY COLUMN source_region VARCHAR(32) NOT NULL COMMENT '来源区域', MODIFY COLUMN source_inbound_id VARCHAR(128) NOT NULL COMMENT '来源入仓分片 ID', MODIFY COLUMN root_inbound_id VARCHAR(128) NULL COMMENT '根入仓单 ID', MODIFY COLUMN source_order_id VARCHAR(128) NULL COMMENT '来源订单 ID', MODIFY COLUMN source_order_no VARCHAR(128) NULL COMMENT '来源订单号', MODIFY COLUMN root_order_id VARCHAR(128) NULL COMMENT '根订单 ID', MODIFY COLUMN inbound_code VARCHAR(128) NULL COMMENT '入仓码', MODIFY COLUMN destination_warehouse_id VARCHAR(128) NULL COMMENT '目的仓 ID', MODIFY COLUMN destination_warehouse_code VARCHAR(128) NULL COMMENT '目的仓编码', MODIFY COLUMN destination_warehouse_name VARCHAR(255) NULL COMMENT '目的仓名称', MODIFY COLUMN package_count INT NOT NULL DEFAULT 0 COMMENT '箱数', MODIFY COLUMN product_quantity INT NULL COMMENT '数量', MODIFY COLUMN pallet_count INT NULL COMMENT '托数', MODIFY COLUMN gross_weight_kg DECIMAL(18,3) NULL COMMENT '毛重(千克)', MODIFY COLUMN net_weight_kg DECIMAL(18,3) NULL COMMENT '净重(千克)', MODIFY COLUMN volume_cbm DECIMAL(18,6) NULL COMMENT '体积(立方米)', MODIFY COLUMN source_event_id VARCHAR(128) NOT NULL COMMENT '来源事件 ID', MODIFY COLUMN source_loading_plan_id VARCHAR(128) NOT NULL COMMENT '装柜计划 ID', MODIFY COLUMN source_loading_plan_version BIGINT NOT NULL COMMENT '装柜计划版本', MODIFY COLUMN handover_version BIGINT NOT NULL COMMENT '交接版本', MODIFY COLUMN payload_hash VARCHAR(71) NOT NULL COMMENT '负载摘要', MODIFY COLUMN status VARCHAR(32) NOT NULL COMMENT '入仓单状态', MODIFY COLUMN exception_flag VARCHAR(32) NOT NULL DEFAULT 'NONE' COMMENT '异常标记', MODIFY COLUMN created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', MODIFY COLUMN updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'; +ALTER TABLE ll_inbound_order_item + MODIFY COLUMN id BIGINT NOT NULL COMMENT '入仓明细主键', MODIFY COLUMN customer_id BIGINT NOT NULL COMMENT '租户ID', MODIFY COLUMN order_id BIGINT NOT NULL COMMENT '入仓单ID', MODIFY COLUMN source_inbound_detail_id VARCHAR(128) NOT NULL COMMENT '来源入仓明细ID', MODIFY COLUMN package_count INT NOT NULL COMMENT '箱数', MODIFY COLUMN product_quantity INT NULL COMMENT '数量', MODIFY COLUMN pallet_count INT NULL COMMENT '托数', MODIFY COLUMN gross_weight_kg DECIMAL(18,3) NULL COMMENT '毛重(千克)', MODIFY COLUMN net_weight_kg DECIMAL(18,3) NULL COMMENT '净重(千克)', MODIFY COLUMN dimensional_weight_kg DECIMAL(18,3) NULL COMMENT '体积重量(千克)', MODIFY COLUMN volume_cbm DECIMAL(18,6) NULL COMMENT '体积(立方米)', MODIFY COLUMN destination_warehouse_id VARCHAR(128) NULL COMMENT '目的仓ID', MODIFY COLUMN destination_warehouse_code VARCHAR(128) NULL COMMENT '目的仓编码', MODIFY COLUMN destination_warehouse_name VARCHAR(255) NULL COMMENT '目的仓名称', MODIFY COLUMN fba_tracking_no VARCHAR(128) NULL COMMENT 'FBA跟踪号', MODIFY COLUMN shipment_no VARCHAR(128) NULL COMMENT '货件编号', MODIFY COLUMN shipping_mark VARCHAR(255) NULL COMMENT '唛头', MODIFY COLUMN sku_code VARCHAR(128) NULL COMMENT 'SKU编码', MODIFY COLUMN product_name_zh VARCHAR(255) NULL COMMENT '中文品名', MODIFY COLUMN product_name_en VARCHAR(255) NULL COMMENT '英文品名', MODIFY COLUMN created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'; +ALTER TABLE ll_order_status_history + MODIFY COLUMN id BIGINT NOT NULL COMMENT '订单状态历史主键', MODIFY COLUMN customer_id BIGINT NOT NULL COMMENT '租户 ID', MODIFY COLUMN order_id BIGINT NOT NULL COMMENT '订单 ID', MODIFY COLUMN from_status VARCHAR(32) NULL COMMENT '变更前状态', MODIFY COLUMN to_status VARCHAR(32) NOT NULL COMMENT '变更后状态', MODIFY COLUMN reason VARCHAR(255) NULL COMMENT '变更原因', MODIFY COLUMN created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录时间'; + +ALTER TABLE ll_loading_event_inbox + MODIFY COLUMN event_id VARCHAR(128) NOT NULL COMMENT '事件全局唯一标识', + MODIFY COLUMN customer_id VARCHAR(128) NOT NULL COMMENT '租户/客户账号标识', + MODIFY COLUMN event_type VARCHAR(64) NOT NULL COMMENT '事件类型', + MODIFY COLUMN event_version INT NOT NULL COMMENT '事件契约版本', + MODIFY COLUMN payload_hash VARCHAR(71) NOT NULL COMMENT '规范化事件负载 SHA-256 摘要', + MODIFY COLUMN payload_json JSON NOT NULL COMMENT '事件冻结负载 JSON', + MODIFY COLUMN status VARCHAR(32) NOT NULL DEFAULT 'RECEIVED' COMMENT '消费状态:RECEIVED/PROCESSED', + MODIFY COLUMN received_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '接收时间', + MODIFY COLUMN processed_at DATETIME(3) NULL COMMENT '处理完成时间', + MODIFY COLUMN last_error VARCHAR(1000) NULL COMMENT '最近一次处理错误'; + +ALTER TABLE ll_order + MODIFY COLUMN id BIGINT NOT NULL COMMENT '雪花 ID', + MODIFY COLUMN order_no VARCHAR(32) NOT NULL COMMENT '尾程单号', + MODIFY COLUMN customer_id VARCHAR(128) NOT NULL COMMENT '租户/客户账号标识', + MODIFY COLUMN order_type VARCHAR(32) NOT NULL DEFAULT 'FIRST_LEG' COMMENT '订单类型', + MODIFY COLUMN inbound_code VARCHAR(128) NULL COMMENT '真实来源入仓码;无入仓关系的海外本土业务为空', + MODIFY COLUMN source_region VARCHAR(32) NOT NULL COMMENT '来源区域/系统', + MODIFY COLUMN source_inbound_id VARCHAR(128) NOT NULL COMMENT '最终实装 LOADED 分片 ID,建单幂等键', + MODIFY COLUMN root_inbound_id VARCHAR(128) NULL COMMENT '根入仓单 ID', + MODIFY COLUMN source_order_id VARCHAR(128) NULL COMMENT '来源订单 ID', + MODIFY COLUMN source_order_no VARCHAR(128) NULL COMMENT '来源订单号', + MODIFY COLUMN root_order_id VARCHAR(128) NULL COMMENT '根订单 ID', + MODIFY COLUMN customer_name VARCHAR(255) NULL COMMENT '货主名称', + MODIFY COLUMN overseas_warehouse_id VARCHAR(128) NOT NULL DEFAULT '193000000000200003' COMMENT '海外接柜仓ID,新产生普通尾程整柜默认洛杉矶仓', + MODIFY COLUMN overseas_warehouse_code VARCHAR(128) NOT NULL DEFAULT 'US-LAX-01' COMMENT '海外接柜仓业务编码快照', + MODIFY COLUMN overseas_warehouse_name VARCHAR(255) NOT NULL DEFAULT '洛杉矶仓' COMMENT '海外接柜仓名称快照', + MODIFY COLUMN receiving_warehouse_version INT NOT NULL DEFAULT 0 COMMENT '柜级接柜仓分配乐观锁版本', + MODIFY COLUMN fba_warehouse_code VARCHAR(128) NULL COMMENT 'FBA 仓库编码', + MODIFY COLUMN container_no VARCHAR(64) NULL COMMENT '集装箱号', + MODIFY COLUMN container_status VARCHAR(32) NULL DEFAULT '待转柜' COMMENT '货柜状态', + MODIFY COLUMN abnormal TINYINT NOT NULL DEFAULT 0 COMMENT '是否异常:0否1是', + MODIFY COLUMN detained TINYINT NOT NULL DEFAULT 0 COMMENT '是否暂扣:0否1是', + MODIFY COLUMN pallet_count INT NOT NULL DEFAULT 0 COMMENT '托数', + MODIFY COLUMN package_count INT NOT NULL DEFAULT 0 COMMENT '箱数', + MODIFY COLUMN volume_cbm DECIMAL(18,4) NULL COMMENT '体积(立方米)', + MODIFY COLUMN product_quantity INT NULL COMMENT '货物数量', + MODIFY COLUMN volume_weight_kg DECIMAL(18,4) NULL COMMENT '体积重量(千克)', + MODIFY COLUMN estimated_arrival_at DATETIME(3) NULL COMMENT '预计到港时间', + MODIFY COLUMN actual_arrival_at DATETIME(3) NULL COMMENT '实际到港时间', + MODIFY COLUMN estimated_warehouse_at DATETIME(3) NULL COMMENT '预计到仓时间', + MODIFY COLUMN inbound_order_no VARCHAR(128) NULL COMMENT '入库单号', + MODIFY COLUMN outbound_order_no VARCHAR(128) NULL COMMENT '出库单号', + MODIFY COLUMN delivery_order_no VARCHAR(128) NULL COMMENT '派送单号', + MODIFY COLUMN relabel_order_no VARCHAR(128) NULL COMMENT '换标单号', + MODIFY COLUMN relabel_status VARCHAR(32) NULL COMMENT '换标状态', + MODIFY COLUMN pallet_merge_flag VARCHAR(32) NULL COMMENT '合托标记', + MODIFY COLUMN current_status VARCHAR(32) NOT NULL DEFAULT 'PENDING_ASSIGNMENT' COMMENT '当前业务状态', + MODIFY COLUMN private_remark VARCHAR(1000) NULL COMMENT '备注(仅自己)', + MODIFY COLUMN customer_remark VARCHAR(1000) NULL COMMENT '备注(客户)', + MODIFY COLUMN warehouse_remark VARCHAR(1000) NULL COMMENT '备注(仓库)', + MODIFY COLUMN source_event_id VARCHAR(128) NOT NULL COMMENT '来源事件 ID', + MODIFY COLUMN source_loading_plan_id VARCHAR(128) NOT NULL COMMENT '来源装柜计划 ID', + MODIFY COLUMN source_container_booking_id VARCHAR(128) NULL COMMENT '来源头程货柜预订ID', + MODIFY COLUMN source_loading_plan_version BIGINT NULL COMMENT '来源装柜计划版本', + MODIFY COLUMN source_handover_version BIGINT NULL COMMENT '交接版本', + MODIFY COLUMN source_payload_hash VARCHAR(71) NOT NULL COMMENT '来源事件负载摘要', + MODIFY COLUMN created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + MODIFY COLUMN updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间'; + +-- Existing installations: convert the row identity to an assigned Snowflake ID and +-- make order_no a container/loading-plan grouping number rather than a row-unique key. +SET @ll_add_transfer_warehouse_id = ( + SELECT IF(COUNT(*) = 0, + 'ALTER TABLE ll_order ADD COLUMN transfer_warehouse_id VARCHAR(128) NULL COMMENT ''转入目的仓ID'' AFTER overseas_warehouse_name', + 'SELECT 1') + FROM information_schema.columns + WHERE table_schema = DATABASE() AND table_name = 'll_order' + AND column_name = 'transfer_warehouse_id' +); +PREPARE ll_add_transfer_warehouse_id_stmt FROM @ll_add_transfer_warehouse_id; +EXECUTE ll_add_transfer_warehouse_id_stmt; +DEALLOCATE PREPARE ll_add_transfer_warehouse_id_stmt; + +SET @ll_add_transfer_warehouse_code = ( + SELECT IF(COUNT(*) = 0, + 'ALTER TABLE ll_order ADD COLUMN transfer_warehouse_code VARCHAR(128) NULL COMMENT ''转入目的仓编码'' AFTER transfer_warehouse_id', + 'SELECT 1') + FROM information_schema.columns + WHERE table_schema = DATABASE() AND table_name = 'll_order' + AND column_name = 'transfer_warehouse_code' +); +PREPARE ll_add_transfer_warehouse_code_stmt FROM @ll_add_transfer_warehouse_code; +EXECUTE ll_add_transfer_warehouse_code_stmt; +DEALLOCATE PREPARE ll_add_transfer_warehouse_code_stmt; + +SET @ll_add_transfer_warehouse_name = ( + SELECT IF(COUNT(*) = 0, + 'ALTER TABLE ll_order ADD COLUMN transfer_warehouse_name VARCHAR(255) NULL COMMENT ''转入目的仓名称'' AFTER transfer_warehouse_code', + 'SELECT 1') + FROM information_schema.columns + WHERE table_schema = DATABASE() AND table_name = 'll_order' + AND column_name = 'transfer_warehouse_name' +); +PREPARE ll_add_transfer_warehouse_name_stmt FROM @ll_add_transfer_warehouse_name; +EXECUTE ll_add_transfer_warehouse_name_stmt; +DEALLOCATE PREPARE ll_add_transfer_warehouse_name_stmt; + +ALTER TABLE ll_order + MODIFY COLUMN transfer_warehouse_id VARCHAR(128) NULL COMMENT '转入目的仓ID', + MODIFY COLUMN transfer_warehouse_code VARCHAR(128) NULL COMMENT '转入目的仓编码', + MODIFY COLUMN transfer_warehouse_name VARCHAR(255) NULL COMMENT '转入目的仓名称'; + +SET @ll_drop_order_no_unique = ( + SELECT IF(COUNT(*) > 0, + 'ALTER TABLE ll_order DROP INDEX uk_ll_order_no', + 'SELECT 1') + FROM information_schema.statistics + WHERE table_schema = DATABASE() AND table_name = 'll_order' + AND index_name = 'uk_ll_order_no' AND non_unique = 0 +); +PREPARE ll_drop_order_no_unique_stmt FROM @ll_drop_order_no_unique; +EXECUTE ll_drop_order_no_unique_stmt; +DEALLOCATE PREPARE ll_drop_order_no_unique_stmt; + +SET @ll_add_order_group_index = ( + SELECT IF(COUNT(*) = 0, + 'CREATE INDEX idx_ll_order_group ON ll_order(customer_id,order_no)', + 'SELECT 1') + FROM information_schema.statistics + WHERE table_schema = DATABASE() AND table_name = 'll_order' + AND index_name = 'idx_ll_order_group' +); +PREPARE ll_add_order_group_index_stmt FROM @ll_add_order_group_index; +EXECUTE ll_add_order_group_index_stmt; +DEALLOCATE PREPARE ll_add_order_group_index_stmt; + +ALTER TABLE ll_order_detail + MODIFY COLUMN id BIGINT NOT NULL AUTO_INCREMENT COMMENT '订单明细主键', + MODIFY COLUMN order_id BIGINT NOT NULL COMMENT '尾程订单 ID', + MODIFY COLUMN source_inbound_detail_id VARCHAR(128) NOT NULL COMMENT '来源入仓明细 ID', + MODIFY COLUMN sku_code VARCHAR(128) NULL COMMENT 'SKU 编码', + MODIFY COLUMN product_name_zh VARCHAR(255) NULL COMMENT '中文品名', + MODIFY COLUMN product_name_en VARCHAR(255) NULL COMMENT '英文品名', + MODIFY COLUMN package_count INT NOT NULL DEFAULT 0 COMMENT '箱数', + MODIFY COLUMN product_quantity INT NULL COMMENT '数量', + MODIFY COLUMN pallet_count INT NULL COMMENT '托数', + MODIFY COLUMN gross_weight_kg DECIMAL(18,4) NULL COMMENT '毛重(千克)', + MODIFY COLUMN net_weight_kg DECIMAL(18,4) NULL COMMENT '净重(千克)', + MODIFY COLUMN dimensional_weight_kg DECIMAL(18,4) NULL COMMENT '体积重量(千克)', + MODIFY COLUMN volume_cbm DECIMAL(18,4) NULL COMMENT '体积(立方米)', + MODIFY COLUMN destination_warehouse_id VARCHAR(128) NULL COMMENT '目的仓 ID', + MODIFY COLUMN destination_warehouse_code VARCHAR(128) NULL COMMENT '目的仓编码', + MODIFY COLUMN destination_warehouse_name VARCHAR(255) NULL COMMENT '目的仓名称', + MODIFY COLUMN fba_tracking_no VARCHAR(255) NULL COMMENT 'FBA跟踪号', + MODIFY COLUMN shipment_no VARCHAR(255) NULL COMMENT '货件编号', + MODIFY COLUMN shipping_mark VARCHAR(255) NULL COMMENT '唛头'; + +CREATE TABLE IF NOT EXISTS ll_inbound_plan ( + id BIGINT NOT NULL COMMENT '入库计划ID(雪花ID)', + plan_no VARCHAR(32) NOT NULL COMMENT '入库单号', + customer_id VARCHAR(128) NOT NULL COMMENT '租户/客户账号标识', + source_order_id BIGINT NOT NULL COMMENT '来源尾程订单ID,一张入仓单对应一张入库计划', + container_no VARCHAR(64) NOT NULL COMMENT '集装箱号', + overseas_warehouse_id BIGINT NOT NULL DEFAULT 193000000000200003 COMMENT '平台预分配的海外实际收货共享物理仓ID', + planned_arrival_date DATE NULL COMMENT '人工计划预计到仓日期', + pallet_type VARCHAR(32) NULL COMMENT '人工计划托盘类型', + assigned_flag TINYINT NOT NULL DEFAULT 0 COMMENT '人工计划是否已分配:0否1是', + merge_required TINYINT NOT NULL DEFAULT 0 COMMENT '人工计划是否要求合托:0否1是', + urgent_flag TINYINT NOT NULL DEFAULT 0 COMMENT '人工计划是否加急:0否1是', + creation_mode VARCHAR(16) NOT NULL DEFAULT 'AUTO' COMMENT '计划生成方式:AUTO柜级自动、MANUAL单票人工', + plan_remark VARCHAR(1000) NULL COMMENT '入仓计划备注', + status VARCHAR(32) NOT NULL DEFAULT 'PLANNED' COMMENT '入库计划状态', + line_count INT NOT NULL DEFAULT 0 COMMENT '入仓码明细数量', + pallet_count INT NOT NULL DEFAULT 0 COMMENT '系统自动分配托盘数量', + total_volume_cbm DECIMAL(18,4) 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 '更新时间', + version INT NOT NULL DEFAULT 0 COMMENT '入库计划乐观锁版本', + PRIMARY KEY (id), + UNIQUE KEY uk_ll_inbound_plan_no (plan_no), + UNIQUE KEY uk_ll_inbound_plan_source_order (customer_id, source_order_id), + KEY idx_ll_inbound_plan_container (customer_id, container_no) +) ENGINE=InnoDB COMMENT='尾程入库计划表:每张来源入仓单生成一张入库计划,同柜计划共享集装箱号'; + +CREATE TABLE IF NOT EXISTS ll_container_plan_conversion_command ( + id BIGINT NOT NULL COMMENT '柜级转换命令ID', + source_container_booking_id VARCHAR(128) NOT NULL COMMENT '稳定来源柜预订ID', + idempotency_key VARCHAR(128) NOT NULL COMMENT '调用方幂等键', + request_hash CHAR(64) NOT NULL COMMENT '确认请求SHA-256摘要', + snapshot_version BIGINT NOT NULL COMMENT '确认的柜级快照版本', + snapshot_hash CHAR(64) NOT NULL COMMENT '确认的柜级快照SHA-256', + warehouse_id BIGINT NOT NULL COMMENT '海外接柜仓ID', + batch_key VARCHAR(160) NOT NULL COMMENT '海外仓托盘批次幂等键', + status VARCHAR(32) NOT NULL COMMENT '命令状态:PREPARED/APPLYING/FAILED/LOCAL_COMMITTED/COMPLETED', + lease_token VARCHAR(64) NULL COMMENT '当前执行租约令牌,用于陈旧执行者隔离', + preparation_json JSON NOT NULL COMMENT '稳定计划号与托盘业务键快照', + result_json JSON NULL COMMENT '本地计划提交结果', + last_error VARCHAR(1000) NULL 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_container_conversion_command(source_container_booking_id), + UNIQUE KEY uk_ll_container_conversion_batch(warehouse_id,batch_key), + KEY idx_ll_container_conversion_status(status,updated_at) +) ENGINE=InnoDB COMMENT='柜级入库计划转换Saga与幂等命令'; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_container_plan_conversion_command' AND column_name='lease_token'), 'SELECT 1', 'ALTER TABLE ll_container_plan_conversion_command ADD COLUMN lease_token VARCHAR(64) NULL COMMENT ''当前执行租约令牌,用于陈旧执行者隔离'' AFTER status')); +PREPARE container_conversion_lease_stmt FROM @ddl; EXECUTE container_conversion_lease_stmt; DEALLOCATE PREPARE container_conversion_lease_stmt; + +CREATE TABLE IF NOT EXISTS ll_inbound_plan_line ( + id BIGINT NOT NULL COMMENT '入库计划明细ID(雪花ID)', + customer_id VARCHAR(128) NOT NULL COMMENT '客户账号标识', + plan_id BIGINT NOT NULL COMMENT '入库计划ID', + order_id BIGINT NOT NULL COMMENT '尾程订单ID', + inbound_code VARCHAR(128) NOT NULL COMMENT '头程入仓码快照', + destination_type VARCHAR(32) NOT NULL DEFAULT 'OVERSEAS' COMMENT '目的类型:FBA按箱数接收、OVERSEAS逐箱扫码', + destination_warehouse_id VARCHAR(128) NULL COMMENT '目的仓ID快照', + destination_warehouse_code VARCHAR(128) NULL COMMENT '目的仓编码快照', + destination_warehouse_name VARCHAR(255) NULL COMMENT '目的仓名称快照', + volume_cbm DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT '入仓码体积(立方米)', + planned_pallet_count INT NOT NULL DEFAULT 0 COMMENT '该明细分配的托盘数量', + PRIMARY KEY (id), + UNIQUE KEY uk_ll_inbound_line_order (plan_id, order_id), + KEY idx_ll_inbound_line_plan (plan_id), + KEY idx_ll_inbound_line_customer_plan (customer_id, plan_id) +) ENGINE=InnoDB COMMENT='尾程入库计划明细表:按入仓码和尾程订单维度记录'; + +CREATE TABLE IF NOT EXISTS ll_inbound_plan_carton ( + id BIGINT NOT NULL COMMENT '箱唛作业主键', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', + order_id BIGINT NULL COMMENT '生成尾程订单时绑定的尾程订单ID', source_inbound_detail_id VARCHAR(128) NOT NULL COMMENT '来源入库明细ID', + sku_code VARCHAR(128) NULL COMMENT 'SKU编码快照', carton_mark VARCHAR(128) NOT NULL COMMENT '国内冻结箱唛', + destination_warehouse_id VARCHAR(128) NULL COMMENT '目的仓ID快照', destination_warehouse_code VARCHAR(128) NULL COMMENT '目的仓编码快照', + destination_warehouse_name VARCHAR(255) NULL COMMENT '目的仓名称快照', plan_id BIGINT NULL COMMENT '海外入库计划ID', + line_id BIGINT NULL COMMENT '入库计划明细ID', plan_pallet_id BIGINT NULL COMMENT '海外计划托盘ID', warehouse_pallet_id BIGINT NULL COMMENT '海外实物托盘ID', + warehouse_location_id BIGINT NULL COMMENT '海外库位ID', pda_task_id BIGINT NULL COMMENT '首次扫码PDA任务ID', + status VARCHAR(32) NOT NULL COMMENT '箱唛状态:UNASSIGNED、ON_PALLET、BULK_RECEIVED、AVAILABLE', version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本', + bound_at DATETIME(3) NULL COMMENT '绑定托盘时间', received_at DATETIME(3) NULL COMMENT '完成入库时间', + updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间', + PRIMARY KEY(id), UNIQUE KEY uk_ll_inbound_carton_mark(customer_id,carton_mark), + KEY idx_ll_inbound_carton_plan(plan_id,status), KEY idx_ll_inbound_carton_pallet(plan_pallet_id,status), + KEY idx_ll_inbound_carton_order(order_id,status) +) ENGINE=InnoDB COMMENT='柜次冻结箱唛及海外入库箱级作业账'; + +CREATE TABLE IF NOT EXISTS ll_inbound_plan_pallet ( + id BIGINT NOT NULL COMMENT '计划托盘ID(雪花ID)', + customer_id VARCHAR(128) NOT NULL COMMENT '客户账号标识', + plan_id BIGINT NOT NULL COMMENT '入库计划ID', + pallet_no VARCHAR(128) NOT NULL COMMENT '海外仓资源服务返回的真实托盘码', + source_pallet_no VARCHAR(128) NULL COMMENT '一件代发不可变来源托盘号快照', + pallet_type VARCHAR(32) NULL COMMENT '人工计划托盘类型', + destination_warehouse_id VARCHAR(128) NULL COMMENT '目的仓ID快照', + destination_warehouse_code VARCHAR(128) NULL COMMENT '目的仓编码快照', + destination_warehouse_name VARCHAR(255) NULL COMMENT '目的仓名称快照', + capacity_cbm DECIMAL(18,4) NOT NULL DEFAULT 2.0000 COMMENT '托盘容量,固定为2立方米', + allocated_volume_cbm DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT '已分配体积(立方米)', + sequence_no INT NOT NULL COMMENT '计划内托盘序号', + PRIMARY KEY (id), + UNIQUE KEY uk_ll_inbound_pallet_no (pallet_no), + UNIQUE KEY uk_ll_inbound_pallet_sequence (plan_id, sequence_no), + KEY idx_ll_inbound_pallet_plan (plan_id), + KEY idx_ll_inbound_pallet_customer_plan (customer_id, plan_id) +) ENGINE=InnoDB COMMENT='尾程入库计划托盘表:按目的仓隔离合托'; + +CREATE TABLE IF NOT EXISTS ll_inbound_plan_pallet_allocation ( + id BIGINT NOT NULL COMMENT '托盘分配记录ID(雪花ID)', + customer_id VARCHAR(128) NOT NULL COMMENT '客户账号标识', + plan_id BIGINT NOT NULL COMMENT '入库计划ID', + line_id BIGINT NOT NULL COMMENT '入库计划明细ID', + pallet_id BIGINT NOT NULL COMMENT '计划托盘ID', + allocated_volume_cbm DECIMAL(18,4) NOT NULL COMMENT '该明细分配到托盘的体积(立方米)', + PRIMARY KEY (id), + UNIQUE KEY uk_ll_inbound_allocation (line_id, pallet_id), + KEY idx_ll_inbound_allocation_plan (plan_id), + KEY idx_ll_inbound_allocation_customer_plan (customer_id, plan_id), + KEY idx_ll_inbound_allocation_pallet (pallet_id) +) ENGINE=InnoDB COMMENT='尾程入库计划托盘分配表:记录入仓码明细与计划托盘的分配关系'; + +-- 修复既有入库计划表的注释;历史“每柜一张计划”记录不在这里猜测拆分。 +ALTER TABLE ll_inbound_plan COMMENT='尾程入库计划表:每张来源入仓单生成一张入库计划,同柜计划共享集装箱号'; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inbound_plan' AND column_name='source_order_id'), 'SELECT 1', 'ALTER TABLE ll_inbound_plan ADD COLUMN source_order_id BIGINT NOT NULL DEFAULT 0 COMMENT ''来源尾程订单ID,一张入仓单对应一张入库计划'' AFTER customer_id')); +PREPARE inbound_plan_source_order_stmt FROM @ddl; EXECUTE inbound_plan_source_order_stmt; DEALLOCATE PREPARE inbound_plan_source_order_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inbound_plan' AND column_name='planned_arrival_date'), 'SELECT 1', 'ALTER TABLE ll_inbound_plan ADD COLUMN planned_arrival_date DATE NULL COMMENT ''人工计划预计到仓日期'' AFTER overseas_warehouse_id')); +PREPARE inbound_plan_manual_stmt FROM @ddl; EXECUTE inbound_plan_manual_stmt; DEALLOCATE PREPARE inbound_plan_manual_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inbound_plan' AND column_name='pallet_type'), 'SELECT 1', 'ALTER TABLE ll_inbound_plan ADD COLUMN pallet_type VARCHAR(32) NULL COMMENT ''人工计划托盘类型'' AFTER planned_arrival_date')); +PREPARE inbound_plan_manual_stmt FROM @ddl; EXECUTE inbound_plan_manual_stmt; DEALLOCATE PREPARE inbound_plan_manual_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inbound_plan' AND column_name='assigned_flag'), 'SELECT 1', 'ALTER TABLE ll_inbound_plan ADD COLUMN assigned_flag TINYINT NOT NULL DEFAULT 0 COMMENT ''人工计划是否已分配:0否1是'' AFTER pallet_type')); +PREPARE inbound_plan_manual_stmt FROM @ddl; EXECUTE inbound_plan_manual_stmt; DEALLOCATE PREPARE inbound_plan_manual_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inbound_plan' AND column_name='merge_required'), 'SELECT 1', 'ALTER TABLE ll_inbound_plan ADD COLUMN merge_required TINYINT NOT NULL DEFAULT 0 COMMENT ''人工计划是否要求合托:0否1是'' AFTER assigned_flag')); +PREPARE inbound_plan_manual_stmt FROM @ddl; EXECUTE inbound_plan_manual_stmt; DEALLOCATE PREPARE inbound_plan_manual_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inbound_plan' AND column_name='urgent_flag'), 'SELECT 1', 'ALTER TABLE ll_inbound_plan ADD COLUMN urgent_flag TINYINT NOT NULL DEFAULT 0 COMMENT ''人工计划是否加急:0否1是'' AFTER merge_required')); +PREPARE inbound_plan_manual_stmt FROM @ddl; EXECUTE inbound_plan_manual_stmt; DEALLOCATE PREPARE inbound_plan_manual_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inbound_plan' AND column_name='creation_mode'), 'SELECT 1', 'ALTER TABLE ll_inbound_plan ADD COLUMN creation_mode VARCHAR(16) NOT NULL DEFAULT ''AUTO'' COMMENT ''计划生成方式:AUTO柜级自动、MANUAL单票人工'' AFTER urgent_flag')); +PREPARE inbound_plan_manual_stmt FROM @ddl; EXECUTE inbound_plan_manual_stmt; DEALLOCATE PREPARE inbound_plan_manual_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inbound_plan' AND column_name='plan_remark'), 'SELECT 1', 'ALTER TABLE ll_inbound_plan ADD COLUMN plan_remark VARCHAR(1000) NULL COMMENT ''入仓计划备注'' AFTER creation_mode')); +PREPARE inbound_plan_manual_stmt FROM @ddl; EXECUTE inbound_plan_manual_stmt; DEALLOCATE PREPARE inbound_plan_manual_stmt; +ALTER TABLE ll_inbound_plan + MODIFY COLUMN id BIGINT NOT NULL COMMENT '入库计划ID(雪花ID)', + MODIFY COLUMN plan_no VARCHAR(32) NOT NULL COMMENT '入库单号', + MODIFY COLUMN customer_id VARCHAR(128) NOT NULL COMMENT '租户/客户账号标识', + MODIFY COLUMN source_order_id BIGINT NOT NULL COMMENT '来源尾程订单ID,一张入仓单对应一张入库计划', + MODIFY COLUMN container_no VARCHAR(64) NOT NULL COMMENT '集装箱号', + MODIFY COLUMN status VARCHAR(32) NOT NULL DEFAULT 'PLANNED' COMMENT '入库计划状态', + MODIFY COLUMN line_count INT NOT NULL DEFAULT 0 COMMENT '入仓码明细数量', + MODIFY COLUMN pallet_count INT NOT NULL DEFAULT 0 COMMENT '系统自动分配托盘数量', + MODIFY COLUMN total_volume_cbm DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT '集装箱总体积(立方米)', + MODIFY COLUMN created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + MODIFY COLUMN updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间'; + +ALTER TABLE ll_inbound_plan_line COMMENT='尾程入库计划明细表:按入仓码和尾程订单维度记录'; +ALTER TABLE ll_inbound_plan_line + MODIFY COLUMN id BIGINT NOT NULL COMMENT '入库计划明细ID(雪花ID)', + MODIFY COLUMN customer_id VARCHAR(128) NOT NULL COMMENT '客户账号标识', + MODIFY COLUMN plan_id BIGINT NOT NULL COMMENT '入库计划ID', + MODIFY COLUMN order_id BIGINT NOT NULL COMMENT '尾程订单ID', + MODIFY COLUMN inbound_code VARCHAR(128) NOT NULL COMMENT '头程入仓码快照', + MODIFY COLUMN destination_warehouse_id VARCHAR(128) NULL COMMENT '目的仓ID快照', + MODIFY COLUMN destination_warehouse_code VARCHAR(128) NULL COMMENT '目的仓编码快照', + MODIFY COLUMN destination_warehouse_name VARCHAR(255) NULL COMMENT '目的仓名称快照', + MODIFY COLUMN volume_cbm DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT '入仓码体积(立方米)', + MODIFY COLUMN planned_pallet_count INT NOT NULL DEFAULT 0 COMMENT '该明细分配的托盘数量'; + +ALTER TABLE ll_inbound_plan_pallet COMMENT='尾程入库计划托盘表:按目的仓隔离合托'; +ALTER TABLE ll_inbound_plan_pallet + MODIFY COLUMN id BIGINT NOT NULL COMMENT '计划托盘ID(雪花ID)', + MODIFY COLUMN plan_id BIGINT NOT NULL COMMENT '入库计划ID', + MODIFY COLUMN pallet_no VARCHAR(128) NOT NULL COMMENT '海外仓资源服务返回的真实托盘码', + MODIFY COLUMN destination_warehouse_id VARCHAR(128) NULL COMMENT '目的仓ID快照', + MODIFY COLUMN destination_warehouse_code VARCHAR(128) NULL COMMENT '目的仓编码快照', + MODIFY COLUMN destination_warehouse_name VARCHAR(255) NULL COMMENT '目的仓名称快照', + MODIFY COLUMN capacity_cbm DECIMAL(18,4) NOT NULL DEFAULT 2.0000 COMMENT '托盘容量,固定为2立方米', + MODIFY COLUMN allocated_volume_cbm DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT '已分配体积(立方米)', + MODIFY COLUMN sequence_no INT NOT NULL COMMENT '计划内托盘序号'; + +ALTER TABLE ll_inbound_plan_pallet_allocation COMMENT='尾程入库计划托盘分配表:记录入仓码明细与计划托盘的分配关系'; +ALTER TABLE ll_inbound_plan_pallet_allocation + MODIFY COLUMN id BIGINT NOT NULL COMMENT '托盘分配记录ID(雪花ID)', + MODIFY COLUMN plan_id BIGINT NOT NULL COMMENT '入库计划ID', + MODIFY COLUMN line_id BIGINT NOT NULL COMMENT '入库计划明细ID', + MODIFY COLUMN pallet_id BIGINT NOT NULL COMMENT '计划托盘ID', + MODIFY COLUMN allocated_volume_cbm DECIMAL(18,4) NOT NULL COMMENT '该明细分配到托盘的体积(立方米)'; + +-- Additive inbound picking schema. Guarded statements preserve existing rows on restart. +ALTER TABLE ll_inbound_plan MODIFY COLUMN status VARCHAR(32) NOT NULL DEFAULT 'PENDING_ASSIGNMENT' COMMENT '入库计划状态:PENDING_ASSIGNMENT待分配及后续正式状态'; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inbound_plan_line' AND column_name='required_package_count'), 'SELECT 1', 'ALTER TABLE ll_inbound_plan_line ADD COLUMN required_package_count INT NOT NULL DEFAULT 0 COMMENT ''计划明细应收箱数''')); +PREPARE inbound_schema_stmt FROM @ddl; EXECUTE inbound_schema_stmt; DEALLOCATE PREPARE inbound_schema_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inbound_plan_pallet' AND column_name='warehouse_pallet_id'), 'SELECT 1', 'ALTER TABLE ll_inbound_plan_pallet ADD COLUMN warehouse_pallet_id BIGINT NULL COMMENT ''海外仓实物托盘ID''')); +PREPARE inbound_schema_stmt FROM @ddl; EXECUTE inbound_schema_stmt; DEALLOCATE PREPARE inbound_schema_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inbound_plan_pallet' AND column_name='pallet_type'), 'SELECT 1', 'ALTER TABLE ll_inbound_plan_pallet ADD COLUMN pallet_type VARCHAR(32) NULL COMMENT ''人工计划托盘类型'' AFTER pallet_no')); +PREPARE inbound_schema_stmt FROM @ddl; EXECUTE inbound_schema_stmt; DEALLOCATE PREPARE inbound_schema_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inbound_plan_pallet' AND column_name='warehouse_location_id'), 'SELECT 1', 'ALTER TABLE ll_inbound_plan_pallet ADD COLUMN warehouse_location_id BIGINT NULL COMMENT ''海外仓库位ID''')); +PREPARE inbound_schema_stmt FROM @ddl; EXECUTE inbound_schema_stmt; DEALLOCATE PREPARE inbound_schema_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inbound_plan_pallet' AND column_name='warehouse_resource_lock_id'), 'SELECT 1', 'ALTER TABLE ll_inbound_plan_pallet ADD COLUMN warehouse_resource_lock_id BIGINT NULL COMMENT ''海外仓资源锁ID''')); +PREPARE inbound_schema_stmt FROM @ddl; EXECUTE inbound_schema_stmt; DEALLOCATE PREPARE inbound_schema_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inbound_plan_pallet' AND column_name='assignment_status'), 'SELECT 1', 'ALTER TABLE ll_inbound_plan_pallet ADD COLUMN assignment_status VARCHAR(32) NOT NULL DEFAULT ''UNASSIGNED'' COMMENT ''托盘分配状态:UNASSIGNED未分配及后续正式状态''')); +PREPARE inbound_schema_stmt FROM @ddl; EXECUTE inbound_schema_stmt; DEALLOCATE PREPARE inbound_schema_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inbound_plan_pallet' AND column_name='locked_at'), 'SELECT 1', 'ALTER TABLE ll_inbound_plan_pallet ADD COLUMN locked_at DATETIME(3) NULL COMMENT ''资源锁定时间''')); +PREPARE inbound_schema_stmt FROM @ddl; EXECUTE inbound_schema_stmt; DEALLOCATE PREPARE inbound_schema_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inbound_plan_pallet' AND column_name='actual_volume_cbm'), 'SELECT 1', 'ALTER TABLE ll_inbound_plan_pallet ADD COLUMN actual_volume_cbm DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT ''实收体积(立方米)''')); +PREPARE inbound_schema_stmt FROM @ddl; EXECUTE inbound_schema_stmt; DEALLOCATE PREPARE inbound_schema_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inbound_plan_pallet' AND column_name='actual_package_count'), 'SELECT 1', 'ALTER TABLE ll_inbound_plan_pallet ADD COLUMN actual_package_count INT NOT NULL DEFAULT 0 COMMENT ''实收箱数''')); +PREPARE inbound_schema_stmt FROM @ddl; EXECUTE inbound_schema_stmt; DEALLOCATE PREPARE inbound_schema_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inbound_plan_pallet_allocation' AND column_name='planned_package_count'), 'SELECT 1', 'ALTER TABLE ll_inbound_plan_pallet_allocation ADD COLUMN planned_package_count INT NOT NULL DEFAULT 0 COMMENT ''计划分配箱数''')); +PREPARE inbound_schema_stmt FROM @ddl; EXECUTE inbound_schema_stmt; DEALLOCATE PREPARE inbound_schema_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inbound_plan_pallet_allocation' AND column_name='counted_package_count'), 'SELECT 1', 'ALTER TABLE ll_inbound_plan_pallet_allocation ADD COLUMN counted_package_count INT NOT NULL DEFAULT 0 COMMENT ''清点箱数''')); +PREPARE inbound_schema_stmt FROM @ddl; EXECUTE inbound_schema_stmt; DEALLOCATE PREPARE inbound_schema_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inbound_plan_pallet_allocation' AND column_name='picked_package_count'), 'SELECT 1', 'ALTER TABLE ll_inbound_plan_pallet_allocation ADD COLUMN picked_package_count INT NOT NULL DEFAULT 0 COMMENT ''已拣箱数''')); +PREPARE inbound_schema_stmt FROM @ddl; EXECUTE inbound_schema_stmt; DEALLOCATE PREPARE inbound_schema_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inbound_plan_pallet_allocation' AND column_name='actual_volume_cbm'), 'SELECT 1', 'ALTER TABLE ll_inbound_plan_pallet_allocation ADD COLUMN actual_volume_cbm DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT ''实收体积(立方米)''')); +PREPARE inbound_schema_stmt FROM @ddl; EXECUTE inbound_schema_stmt; DEALLOCATE PREPARE inbound_schema_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inbound_plan_pallet_allocation' AND column_name='status'), 'SELECT 1', 'ALTER TABLE ll_inbound_plan_pallet_allocation ADD COLUMN status VARCHAR(32) NOT NULL DEFAULT ''PENDING_COUNT'' COMMENT ''分配作业状态:PENDING_COUNT待清点及后续正式状态''')); +PREPARE inbound_schema_stmt FROM @ddl; EXECUTE inbound_schema_stmt; DEALLOCATE PREPARE inbound_schema_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inbound_plan_pallet_allocation' AND column_name='version'), 'SELECT 1', 'ALTER TABLE ll_inbound_plan_pallet_allocation ADD COLUMN version INT NOT NULL DEFAULT 0 COMMENT ''托盘分配乐观锁版本''')); +PREPARE inbound_schema_stmt FROM @ddl; EXECUTE inbound_schema_stmt; DEALLOCATE PREPARE inbound_schema_stmt; + +CREATE TABLE IF NOT EXISTS ll_inbound_pda_task ( + id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', plan_id BIGINT NOT NULL COMMENT '入仓计划ID', + task_no VARCHAR(64) NOT NULL COMMENT 'PDA任务号', status VARCHAR(32) NOT NULL DEFAULT 'PENDING' COMMENT '业务状态', + operator_id VARCHAR(128) NULL COMMENT '操作人ID', operator_name VARCHAR(255) NULL COMMENT '操作人姓名', + pushed_at DATETIME(3) NULL COMMENT '任务下发时间', started_at DATETIME(3) NULL COMMENT '开始时间', completed_at DATETIME(3) NULL COMMENT '完成时间', + unload_photo_file_ids JSON NULL COMMENT '卸柜照片文件ID列表', unload_photo_uploaded_at DATETIME(3) NULL COMMENT '卸柜照片上传成功时间', + estimated_empty_return_at DATETIME(3) NULL COMMENT '预计报空时间(卸柜照片上传成功后两天)', + version INT 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_inbound_pda_task_plan (customer_id, plan_id), + UNIQUE KEY uk_ll_inbound_pda_task_no (task_no) +) ENGINE=InnoDB COMMENT='入仓PDA作业任务'; + +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inbound_pda_task' AND column_name='unload_photo_file_ids'), 'SELECT 1', 'ALTER TABLE ll_inbound_pda_task ADD COLUMN unload_photo_file_ids JSON NULL COMMENT ''卸柜照片文件ID列表'' AFTER completed_at')); +PREPARE inbound_pda_photo_stmt FROM @ddl; EXECUTE inbound_pda_photo_stmt; DEALLOCATE PREPARE inbound_pda_photo_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inbound_pda_task' AND column_name='unload_photo_uploaded_at'), 'SELECT 1', 'ALTER TABLE ll_inbound_pda_task ADD COLUMN unload_photo_uploaded_at DATETIME(3) NULL COMMENT ''卸柜照片上传成功时间'' AFTER unload_photo_file_ids')); +PREPARE inbound_pda_photo_stmt FROM @ddl; EXECUTE inbound_pda_photo_stmt; DEALLOCATE PREPARE inbound_pda_photo_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inbound_pda_task' AND column_name='estimated_empty_return_at'), 'SELECT 1', 'ALTER TABLE ll_inbound_pda_task ADD COLUMN estimated_empty_return_at DATETIME(3) NULL COMMENT ''预计报空时间(卸柜照片上传成功后两天)'' AFTER unload_photo_uploaded_at')); +PREPARE inbound_pda_photo_stmt FROM @ddl; EXECUTE inbound_pda_photo_stmt; DEALLOCATE PREPARE inbound_pda_photo_stmt; + +CREATE TABLE IF NOT EXISTS ll_event_outbox ( + id BIGINT NOT NULL COMMENT '事件主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', + aggregate_type VARCHAR(64) NOT NULL COMMENT '聚合类型', aggregate_id VARCHAR(128) NOT NULL COMMENT '聚合标识', + event_type VARCHAR(64) NOT NULL COMMENT '事件类型', event_version INT NOT NULL COMMENT '事件版本', + event_key VARCHAR(255) NOT NULL COMMENT '业务幂等键', payload_json LONGTEXT NOT NULL COMMENT '事件载荷JSON', + status VARCHAR(32) NOT NULL DEFAULT 'PENDING' COMMENT '发布状态', retry_count INT NOT NULL DEFAULT 0 COMMENT '重试次数', + next_retry_at DATETIME(3) NULL COMMENT '下次重试时间', published_at DATETIME(3) NULL COMMENT '发布时间', + locked_at DATETIME(3) NULL COMMENT '投递租约获取时间', locked_by VARCHAR(128) NULL COMMENT '投递租约持有者', + last_error VARCHAR(1000) NULL COMMENT '最后一次发布错误', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + PRIMARY KEY (id), UNIQUE KEY uk_ll_event_outbox_key (event_key), + KEY idx_ll_event_outbox_publish (status, next_retry_at, created_at) +) ENGINE=InnoDB COMMENT='尾程跨区业务事件发件箱'; + +CREATE TABLE IF NOT EXISTS ll_finance_fee_source ( + id BIGINT NOT NULL COMMENT '费用版本记录ID', source_fee_id BIGINT NOT NULL COMMENT '跨版本稳定费用来源ID', + source_version INT NOT NULL COMMENT '费用来源版本,从1递增', customer_id VARCHAR(128) NOT NULL COMMENT '客户ID和隔离键', + customer_name VARCHAR(200) NULL COMMENT '客户名称快照', source_type VARCHAR(64) NOT NULL COMMENT '海外费用来源类型', + source_no VARCHAR(128) NOT NULL COMMENT '来源业务编号或入仓码快照', business_type VARCHAR(32) NOT NULL COMMENT '来源业务类型', + business_id BIGINT NULL COMMENT '来源业务记录ID', business_no VARCHAR(128) NOT NULL COMMENT '来源业务编号', + order_id BIGINT NULL COMMENT '关联订单ID', inbound_id BIGINT NULL COMMENT '关联海外入仓计划或入仓记录ID', + inbound_code VARCHAR(64) NULL COMMENT '关联入仓码;并非所有费用必填', container_id BIGINT NULL COMMENT '关联柜次ID', + container_booking_id BIGINT NULL COMMENT '来源订舱柜ID', container_no VARCHAR(64) NULL COMMENT '真实集装箱号快照', + warehouse_id BIGINT NULL COMMENT '关联海外仓ID', fee_item_code VARCHAR(64) NOT NULL COMMENT '费用项目编码快照', + fee_item_name VARCHAR(200) NOT NULL COMMENT '费用项目名称快照', direction VARCHAR(16) NOT NULL COMMENT 'RECEIVABLE应收或PAYABLE应付', + supplier_id BIGINT NULL COMMENT '应付供应商ID', supplier_name VARCHAR(200) NULL COMMENT '应付供应商名称快照', + supplier_category VARCHAR(32) NULL COMMENT '应付供应商类别约束', settlement_mode VARCHAR(16) NULL COMMENT 'ON_ACCOUNT账期应付或DIRECT_PAID业务已付', + paid_at DATETIME(3) NULL COMMENT '直接已付时间', payment_method VARCHAR(32) NULL COMMENT '直接已付方式', + payment_reference_no VARCHAR(128) NULL COMMENT '直接已付流水号', payment_voucher_file_ids JSON NULL COMMENT '直接已付凭证文件ID快照', + business_date DATE NOT NULL COMMENT '费用业务日期', currency CHAR(3) NOT NULL DEFAULT 'CNY' COMMENT '财务结算币种,固定CNY', + amount DECIMAL(20,6) NOT NULL COMMENT '人民币结算金额', original_currency CHAR(3) NOT NULL COMMENT '业务原币种,支持CNY或USD', + original_amount DECIMAL(20,6) NOT NULL COMMENT '业务原币金额', exchange_rate DECIMAL(20,6) NOT NULL COMMENT '原币折算人民币汇率,人民币固定为1', + exchange_rate_month CHAR(7) NOT NULL COMMENT '汇率月份yyyy-MM', chargeable_quantity DECIMAL(20,6) NULL COMMENT '计费数量,如箱数、托数、体积或天数', + quantity_unit VARCHAR(32) NULL COMMENT '计费数量单位', unit_price DECIMAL(20,6) NULL COMMENT '原币计费单价', + period_start DATE NULL COMMENT '仓租等期间费用开始日期', period_end DATE NULL COMMENT '仓租等期间费用结束日期', + review_status VARCHAR(16) NOT NULL COMMENT 'DRAFT、PENDING、APPROVED或REJECTED', + publication_status VARCHAR(16) NOT NULL COMMENT 'UNPUBLISHED或PUBLISHED', source_status VARCHAR(16) NOT NULL COMMENT 'ACTIVE、SUPERSEDED或VOIDED', + review_reason VARCHAR(500) NULL COMMENT '审核或作废原因', reviewer_id VARCHAR(128) NULL COMMENT '审核人ID', reviewed_at DATETIME(3) NULL COMMENT '审核时间', + created_by VARCHAR(128) NOT NULL COMMENT '创建人ID', updated_by VARCHAR(128) NOT NULL COMMENT '最后更新人ID', + created_at DATETIME(3) NOT NULL COMMENT '创建时间', updated_at DATETIME(3) NOT NULL COMMENT '最后更新时间', + PRIMARY KEY(id), UNIQUE KEY uk_ll_finance_fee_version(source_fee_id,source_version), + KEY idx_ll_finance_fee_customer(customer_id,source_status,created_at), KEY idx_ll_finance_fee_business(business_type,business_no,created_at), + KEY idx_ll_finance_fee_container_customer(container_booking_id,customer_id,source_status,created_at), + CONSTRAINT chk_ll_finance_fee_amount CHECK(amount>0), CONSTRAINT chk_ll_finance_fee_currency CHECK(currency='CNY'), + CONSTRAINT chk_ll_finance_fee_original_currency CHECK(original_currency IN('CNY','USD')), + CONSTRAINT chk_ll_finance_fee_period CHECK((period_start IS NULL AND period_end IS NULL) OR (period_start IS NOT NULL AND period_end IS NOT NULL AND period_start<=period_end)), + CONSTRAINT chk_ll_finance_fee_direction CHECK(direction IN('RECEIVABLE','PAYABLE')), + CONSTRAINT chk_ll_finance_fee_review CHECK(review_status IN('DRAFT','PENDING','APPROVED','REJECTED')), + CONSTRAINT chk_ll_finance_fee_publication CHECK(publication_status IN('UNPUBLISHED','PUBLISHED')), + CONSTRAINT chk_ll_finance_fee_status CHECK(source_status IN('ACTIVE','SUPERSEDED','VOIDED')) +) ENGINE=InnoDB COMMENT='海外业务不可变费用来源版本;发布后经跨区事件进入Finance'; + +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_finance_fee_source' AND index_name='idx_ll_finance_fee_container_customer'), 'SELECT 1', 'ALTER TABLE ll_finance_fee_source ADD KEY idx_ll_finance_fee_container_customer(container_booking_id,customer_id,source_status,created_at)')); +PREPARE ll_finance_fee_ddl FROM @ddl; EXECUTE ll_finance_fee_ddl; DEALLOCATE PREPARE ll_finance_fee_ddl; + +CREATE TABLE IF NOT EXISTS ll_finance_fee_command ( + id BIGINT NOT NULL COMMENT '幂等命令ID', operator_id VARCHAR(128) NOT NULL COMMENT '操作人ID', + action VARCHAR(16) NOT NULL COMMENT 'CREATE、SUBMIT、REVIEW、ADJUST或VOID', idempotency_key VARCHAR(128) NOT NULL COMMENT '操作幂等键', + request_hash CHAR(64) NOT NULL COMMENT '请求内容SHA-256', source_fee_id BIGINT NOT NULL COMMENT '稳定费用来源ID', + source_version INT NOT NULL COMMENT '命令结果版本', created_at DATETIME(3) NOT NULL COMMENT '命令创建时间', + PRIMARY KEY(id), UNIQUE KEY uk_ll_finance_fee_command(operator_id,action,idempotency_key), + KEY idx_ll_finance_fee_command_source(source_fee_id,source_version) +) ENGINE=InnoDB COMMENT='海外费用来源写操作幂等审计'; + +CREATE TABLE IF NOT EXISTS ll_overseas_quote ( + id BIGINT NOT NULL COMMENT '报价ID', name VARCHAR(128) NOT NULL COMMENT '报价名称', + warehouse_id BIGINT NOT NULL COMMENT '国内平台海外合作仓ID', warehouse_code VARCHAR(64) NOT NULL COMMENT '合作仓编码快照', + warehouse_name VARCHAR(128) NOT NULL COMMENT '合作仓名称快照', country_code VARCHAR(2) NOT NULL COMMENT '国家二字码快照', + provider_name VARCHAR(128) NOT NULL COMMENT '报价方', currency CHAR(3) NOT NULL COMMENT '币种', + effective_start DATE NOT NULL COMMENT '生效开始日期', effective_end DATE NOT NULL COMMENT '生效结束日期', + version_code VARCHAR(64) NOT NULL COMMENT '业务版本号', status VARCHAR(16) NOT NULL DEFAULT 'INACTIVE' COMMENT '状态:ACTIVE/INACTIVE', + remark VARCHAR(1000) NULL COMMENT '备注', record_version BIGINT NOT NULL DEFAULT 0 COMMENT '乐观锁版本', + created_by VARCHAR(128) NOT NULL COMMENT '创建人', updated_by VARCHAR(128) NOT NULL COMMENT '更新人', + created_at DATETIME(3) NOT NULL COMMENT '创建时间', updated_at DATETIME(3) NOT NULL COMMENT '更新时间', + PRIMARY KEY (id), UNIQUE KEY uk_ll_quote_warehouse_provider_version (warehouse_id,provider_name,version_code), + KEY idx_ll_quote_page (status,updated_at,id), KEY idx_ll_quote_effective (warehouse_id,provider_name,currency,effective_start,effective_end) +) ENGINE=InnoDB COMMENT='海外仓报价主表'; + +CREATE TABLE IF NOT EXISTS ll_overseas_quote_fee ( + id BIGINT NOT NULL COMMENT '费用明细ID', quote_id BIGINT NOT NULL COMMENT '报价ID', sequence_no INT NOT NULL COMMENT '显示顺序', + item VARCHAR(128) NOT NULL COMMENT '费用项目', billing_unit VARCHAR(64) NOT NULL COMMENT '计费单位', + unit_price DECIMAL(18,4) NOT NULL COMMENT '单价', minimum_charge DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT '最低收费', + remark VARCHAR(500) NULL COMMENT '备注', PRIMARY KEY (id), UNIQUE KEY uk_ll_quote_fee_sequence (quote_id,sequence_no), + KEY idx_ll_quote_fee_quote (quote_id) +) ENGINE=InnoDB COMMENT='海外仓报价仓库费用'; + +CREATE TABLE IF NOT EXISTS ll_overseas_quote_transit_price ( + id BIGINT NOT NULL COMMENT '中转价格ID', quote_id BIGINT NOT NULL COMMENT '报价ID', sequence_no INT NOT NULL COMMENT '显示顺序', + fba_warehouse_code VARCHAR(64) NOT NULL COMMENT 'FBA仓编码', delivery_method VARCHAR(64) NOT NULL COMMENT '报价运输方式', + estimated_miles DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT '预估英里数', price DECIMAL(18,4) NOT NULL COMMENT '报价金额', + minimum_charge DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT '最低收费', remark VARCHAR(500) NULL COMMENT '备注', + PRIMARY KEY (id), UNIQUE KEY uk_ll_quote_transit_sequence (quote_id,sequence_no), + KEY idx_ll_quote_transit_quote (quote_id) +) ENGINE=InnoDB COMMENT='海外仓报价中转价格明细'; + +CREATE TABLE IF NOT EXISTS ll_monthly_exchange_rate ( + id BIGINT NOT NULL COMMENT '月度汇率ID', rate_month CHAR(7) NOT NULL COMMENT '汇率月份,格式yyyy-MM', + base_currency CHAR(3) NOT NULL DEFAULT 'USD' COMMENT '基准币种', quote_currency CHAR(3) NOT NULL DEFAULT 'CNY' COMMENT '报价币种', + usd_to_cny_average DECIMAL(18,8) NOT NULL COMMENT '美元兑人民币月平均汇率', status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE' COMMENT '状态:ACTIVE/INACTIVE', + remark VARCHAR(1000) NULL COMMENT '备注', record_version BIGINT NOT NULL DEFAULT 0 COMMENT '乐观锁版本', + created_by VARCHAR(128) NOT NULL COMMENT '创建人', updated_by VARCHAR(128) NOT NULL COMMENT '更新人', + created_at DATETIME(3) NOT NULL COMMENT '创建时间', updated_at DATETIME(3) NOT NULL COMMENT '更新时间', + PRIMARY KEY (id), UNIQUE KEY uk_ll_exchange_rate_month (rate_month), + KEY idx_ll_exchange_rate_page (status,rate_month) +) ENGINE=InnoDB COMMENT='美元兑人民币月度平均汇率'; + +CREATE TABLE IF NOT EXISTS ll_monthly_exchange_rate_history ( + id BIGINT NOT NULL COMMENT '汇率变更历史ID', exchange_rate_id BIGINT NOT NULL COMMENT '月度汇率ID', + before_rate DECIMAL(18,8) NULL COMMENT '变更前汇率', after_rate DECIMAL(18,8) NOT NULL COMMENT '变更后汇率', + before_status VARCHAR(16) NULL COMMENT '变更前状态', after_status VARCHAR(16) NOT NULL COMMENT '变更后状态', + operator_id VARCHAR(128) NOT NULL COMMENT '操作人', remark VARCHAR(1000) NULL COMMENT '变更说明', + created_at DATETIME(3) NOT NULL COMMENT '创建时间', PRIMARY KEY (id), + KEY idx_ll_exchange_rate_history (exchange_rate_id,created_at) +) ENGINE=InnoDB COMMENT='月度汇率变更历史'; + +CREATE TABLE IF NOT EXISTS ll_vehicle ( + id BIGINT NOT NULL COMMENT '车辆档案ID', plate_number VARCHAR(64) NOT NULL COMMENT '车牌号', + model VARCHAR(128) NOT NULL COMMENT '车型', fuel_consumption_per100_km DECIMAL(10,2) NOT NULL COMMENT '百公里油耗,单位升', + mileage_km BIGINT NOT NULL COMMENT '当前公里码,单位公里', driver_or_dispatch_company VARCHAR(128) NULL COMMENT '司机姓名或外派单位', + phone VARCHAR(64) NULL COMMENT '司机或外派单位联系电话', region VARCHAR(16) NOT NULL COMMENT '区域:DOMESTIC/OVERSEAS', + ownership VARCHAR(16) NOT NULL COMMENT '车辆归属:OWNED/DISPATCHED', + status VARCHAR(32) NOT NULL COMMENT '状态:IN_SERVICE/DISPATCHED/MAINTENANCE/INACTIVE/RETURNED', + remark VARCHAR(1000) NULL COMMENT '备注', record_version BIGINT NOT NULL DEFAULT 0 COMMENT '乐观锁版本', + created_by VARCHAR(128) NOT NULL COMMENT '创建人', updated_by VARCHAR(128) NOT NULL COMMENT '更新人', + created_at DATETIME(3) NOT NULL COMMENT '创建时间', updated_at DATETIME(3) NOT NULL COMMENT '更新时间', + PRIMARY KEY (id), UNIQUE KEY uk_ll_vehicle_plate (plate_number), + KEY idx_ll_vehicle_page (ownership,region,status,updated_at,id) +) ENGINE=InnoDB COMMENT='平台车辆主数据'; + +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_event_outbox' AND column_name='locked_at'), 'SELECT 1', 'ALTER TABLE ll_event_outbox ADD COLUMN locked_at DATETIME(3) NULL COMMENT ''投递租约获取时间'' AFTER published_at')); +PREPARE ll_outbox_lease_stmt FROM @ddl; EXECUTE ll_outbox_lease_stmt; DEALLOCATE PREPARE ll_outbox_lease_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_event_outbox' AND column_name='locked_by'), 'SELECT 1', 'ALTER TABLE ll_event_outbox ADD COLUMN locked_by VARCHAR(128) NULL COMMENT ''投递租约持有者'' AFTER locked_at')); +PREPARE ll_outbox_lease_stmt FROM @ddl; EXECUTE ll_outbox_lease_stmt; DEALLOCATE PREPARE ll_outbox_lease_stmt; + +CREATE TABLE IF NOT EXISTS ll_inbound_pda_operation ( + id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', task_id BIGINT NOT NULL COMMENT 'PDA任务ID', + allocation_id BIGINT NULL COMMENT '分配记录ID', action VARCHAR(32) NOT NULL COMMENT '操作动作', idempotency_key VARCHAR(255) NOT NULL COMMENT '业务幂等键', + request_hash VARCHAR(128) NOT NULL COMMENT '请求内容哈希', request_json LONGTEXT NOT NULL COMMENT '请求内容JSON', result_json LONGTEXT NOT NULL COMMENT '处理结果JSON', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + PRIMARY KEY (id), UNIQUE KEY uk_ll_inbound_pda_operation_key (customer_id, idempotency_key), + KEY idx_ll_inbound_pda_operation_task (task_id), KEY idx_ll_inbound_pda_operation_allocation (allocation_id) +) ENGINE=InnoDB COMMENT='入仓PDA操作幂等记录'; + +CREATE TABLE IF NOT EXISTS ll_inbound_pallet_merge ( + id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', task_id BIGINT NOT NULL COMMENT 'PDA任务ID', + merge_group_no VARCHAR(64) NOT NULL COMMENT '合托分组号', source_pallet_id BIGINT NOT NULL COMMENT '来源托盘ID', target_pallet_id BIGINT NOT NULL COMMENT '目标托盘ID', + source_allocation_id BIGINT NOT NULL COMMENT '来源分配记录ID', target_allocation_id BIGINT NOT NULL COMMENT '目标分配记录ID', + source_line_id BIGINT NOT NULL COMMENT '来源计划明细ID', target_line_id BIGINT NOT NULL COMMENT '目标计划明细ID', + source_order_id BIGINT NOT NULL COMMENT '来源订单ID', target_order_id BIGINT NOT NULL COMMENT '目标订单ID', + moved_package_count INT NOT NULL COMMENT '移动箱数', moved_volume_cbm DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT '移动体积(立方米)', + source_pallet_scrapped TINYINT NOT NULL DEFAULT 0 COMMENT '来源托盘是否已报废', status VARCHAR(32) NOT NULL COMMENT '业务状态', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', PRIMARY KEY (id), + KEY idx_ll_inbound_merge_task (customer_id, task_id), KEY idx_ll_inbound_merge_group (merge_group_no) +) ENGINE=InnoDB COMMENT='入仓托盘合托记录'; + +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inbound_plan_line' AND column_name='fba_warehouse_code'), 'SELECT 1', 'ALTER TABLE ll_inbound_plan_line ADD COLUMN fba_warehouse_code VARCHAR(128) NULL AFTER inbound_code')); +PREPARE inbound_schema_stmt FROM @ddl; EXECUTE inbound_schema_stmt; DEALLOCATE PREPARE inbound_schema_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inbound_plan_line' AND column_name='customer_id'), 'SELECT 1', 'ALTER TABLE ll_inbound_plan_line ADD COLUMN customer_id VARCHAR(128) NOT NULL DEFAULT '''' COMMENT ''客户账号标识'' AFTER id')); +PREPARE receiving_container_line_stmt FROM @ddl; EXECUTE receiving_container_line_stmt; DEALLOCATE PREPARE receiving_container_line_stmt; +UPDATE ll_inbound_plan_line l JOIN ll_inbound_plan p ON p.id=l.plan_id SET l.customer_id=p.customer_id WHERE l.customer_id IS NULL OR l.customer_id=''; +ALTER TABLE ll_inbound_plan_line MODIFY COLUMN customer_id VARCHAR(128) NOT NULL COMMENT '客户账号标识'; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_inbound_plan_line' AND index_name='idx_ll_inbound_line_customer_plan'), 'SELECT 1', 'ALTER TABLE ll_inbound_plan_line ADD KEY idx_ll_inbound_line_customer_plan(customer_id,plan_id)')); +PREPARE inbound_customer_scope_stmt FROM @ddl; EXECUTE inbound_customer_scope_stmt; DEALLOCATE PREPARE inbound_customer_scope_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inbound_plan_pallet' AND column_name='customer_id'), 'SELECT 1', 'ALTER TABLE ll_inbound_plan_pallet ADD COLUMN customer_id VARCHAR(128) NOT NULL DEFAULT '''' COMMENT ''客户账号标识'' AFTER id')); +PREPARE inbound_customer_scope_stmt FROM @ddl; EXECUTE inbound_customer_scope_stmt; DEALLOCATE PREPARE inbound_customer_scope_stmt; +UPDATE ll_inbound_plan_pallet v JOIN ll_inbound_plan p ON p.id=v.plan_id SET v.customer_id=p.customer_id WHERE v.customer_id IS NULL OR v.customer_id=''; +ALTER TABLE ll_inbound_plan_pallet MODIFY COLUMN customer_id VARCHAR(128) NOT NULL COMMENT '客户账号标识'; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_inbound_plan_pallet' AND index_name='idx_ll_inbound_pallet_customer_plan'), 'SELECT 1', 'ALTER TABLE ll_inbound_plan_pallet ADD KEY idx_ll_inbound_pallet_customer_plan(customer_id,plan_id)')); +PREPARE inbound_customer_scope_stmt FROM @ddl; EXECUTE inbound_customer_scope_stmt; DEALLOCATE PREPARE inbound_customer_scope_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inbound_plan_pallet_allocation' AND column_name='customer_id'), 'SELECT 1', 'ALTER TABLE ll_inbound_plan_pallet_allocation ADD COLUMN customer_id VARCHAR(128) NOT NULL DEFAULT '''' COMMENT ''客户账号标识'' AFTER id')); +PREPARE inbound_customer_scope_stmt FROM @ddl; EXECUTE inbound_customer_scope_stmt; DEALLOCATE PREPARE inbound_customer_scope_stmt; +UPDATE ll_inbound_plan_pallet_allocation a JOIN ll_inbound_plan p ON p.id=a.plan_id SET a.customer_id=p.customer_id WHERE a.customer_id IS NULL OR a.customer_id=''; +ALTER TABLE ll_inbound_plan_pallet_allocation MODIFY COLUMN customer_id VARCHAR(128) NOT NULL COMMENT '客户账号标识'; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_inbound_plan_pallet_allocation' AND index_name='idx_ll_inbound_allocation_customer_plan'), 'SELECT 1', 'ALTER TABLE ll_inbound_plan_pallet_allocation ADD KEY idx_ll_inbound_allocation_customer_plan(customer_id,plan_id)')); +PREPARE inbound_customer_scope_stmt FROM @ddl; EXECUTE inbound_customer_scope_stmt; DEALLOCATE PREPARE inbound_customer_scope_stmt; +DROP TEMPORARY TABLE IF EXISTS inbound_plan_customer_scope_assertion; +CREATE TEMPORARY TABLE inbound_plan_customer_scope_assertion ( + is_valid TINYINT NOT NULL, + CONSTRAINT chk_inbound_plan_customer_scope_ready CHECK (is_valid=1) +); +INSERT INTO inbound_plan_customer_scope_assertion(is_valid) +SELECT 0 +WHERE EXISTS (SELECT 1 FROM ll_inbound_plan_line l LEFT JOIN ll_inbound_plan p ON p.id=l.plan_id WHERE l.customer_id IS NULL OR l.customer_id='' OR p.id IS NULL OR l.customer_id<>p.customer_id) + OR EXISTS (SELECT 1 FROM ll_inbound_plan_pallet v LEFT JOIN ll_inbound_plan p ON p.id=v.plan_id WHERE v.customer_id IS NULL OR v.customer_id='' OR p.id IS NULL OR v.customer_id<>p.customer_id) + OR EXISTS (SELECT 1 FROM ll_inbound_plan_pallet_allocation a LEFT JOIN ll_inbound_plan p ON p.id=a.plan_id WHERE a.customer_id IS NULL OR a.customer_id='' OR p.id IS NULL OR a.customer_id<>p.customer_id); +DROP TEMPORARY TABLE inbound_plan_customer_scope_assertion; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inbound_plan_line' AND column_name='destination_type'), 'ALTER TABLE ll_inbound_plan_line MODIFY COLUMN destination_type VARCHAR(32) NOT NULL DEFAULT ''OVERSEAS'' COMMENT ''目的类型:FBA按箱数接收、OVERSEAS逐箱扫码''', 'ALTER TABLE ll_inbound_plan_line ADD COLUMN destination_type VARCHAR(32) NOT NULL DEFAULT ''OVERSEAS'' COMMENT ''目的类型:FBA按箱数接收、OVERSEAS逐箱扫码'' AFTER inbound_code')); +PREPARE receiving_container_line_stmt FROM @ddl; EXECUTE receiving_container_line_stmt; DEALLOCATE PREPARE receiving_container_line_stmt; +UPDATE ll_inbound_plan_line l JOIN ll_order o ON o.id=l.order_id +SET l.fba_warehouse_code=o.fba_warehouse_code, + l.destination_type=COALESCE(NULLIF(o.destination_type,''),CASE WHEN NULLIF(o.fba_warehouse_code,'') IS NULL THEN 'OVERSEAS' ELSE 'FBA' END) +WHERE (l.fba_warehouse_code IS NULL AND o.fba_warehouse_code IS NOT NULL) + OR l.destination_type IS NULL OR l.destination_type='' OR l.destination_type='OVERSEAS'; + +CREATE TABLE IF NOT EXISTS ll_receiving_container_session ( + id BIGINT NOT NULL COMMENT '柜号签收会话ID', + source_container_booking_id VARCHAR(128) NOT NULL COMMENT '稳定来源柜预订ID', container_fact_hash VARCHAR(64) NOT NULL COMMENT '领取时整柜订单与计划事实摘要', + container_no VARCHAR(64) NOT NULL COMMENT '集装箱号', overseas_warehouse_id BIGINT NOT NULL COMMENT '平台预分配并由操作员Token匹配的海外接柜仓ID', overseas_warehouse_code VARCHAR(128) NOT NULL COMMENT '海外接柜仓业务编码快照', overseas_warehouse_name VARCHAR(255) NOT NULL COMMENT '仓库服务校验后的海外接柜仓名称快照', operator_key VARCHAR(128) NOT NULL COMMENT '操作账号唯一标识', + operator_name VARCHAR(255) NULL COMMENT '操作账号名称', active_operator_key VARCHAR(255) NULL COMMENT '未完成会话平台操作账号全局唯一锁键', + active_container_key VARCHAR(255) NULL COMMENT '未完成会话柜号全局唯一锁键', status VARCHAR(32) NOT NULL DEFAULT 'WAIT_SIGN' COMMENT '会话状态:WAIT_SIGN、SIGNED、RECEIVING、COMPLETED', + sign_photos JSON NULL COMMENT '签收卸柜照片角色与文件ID列表', prepared_at DATETIME(3) NULL COMMENT '选择柜号时间', + signed_at DATETIME(3) NULL COMMENT '签收时间', receiving_at DATETIME(3) NULL COMMENT '首次收货时间', + completed_at DATETIME(3) NULL 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 '更新时间', version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本', + PRIMARY KEY(id), UNIQUE KEY uk_ll_receiving_active_operator(active_operator_key), UNIQUE KEY uk_ll_receiving_active_container(active_container_key), + KEY idx_ll_receiving_container(container_no,status), KEY idx_ll_receiving_operator(operator_key,created_at) +) ENGINE=InnoDB COMMENT='海外PDA柜号签收收货会话'; + +CREATE TABLE IF NOT EXISTS ll_receiving_sku_receipt ( + id BIGINT NOT NULL COMMENT 'FBA SKU实收进度ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户账号标识', + session_id BIGINT NOT NULL COMMENT '柜号签收会话ID', plan_id BIGINT NOT NULL COMMENT '入仓计划ID', + line_id BIGINT NOT NULL COMMENT '入仓计划明细ID', pallet_id BIGINT NOT NULL COMMENT '计划托盘ID', + order_id BIGINT NOT NULL COMMENT '尾程订单ID', inbound_code VARCHAR(128) NOT NULL COMMENT '入仓码快照', + sku_code VARCHAR(128) NOT NULL COMMENT 'SKU编码快照', product_name VARCHAR(255) NULL COMMENT '产品名称快照', + received_packages INT NOT NULL COMMENT 'SKU实收箱数', 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 '更新时间', + version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本', PRIMARY KEY(id), + UNIQUE KEY uk_ll_receiving_sku_pallet (customer_id,line_id,pallet_id,sku_code), + KEY idx_ll_receiving_sku_session (customer_id,session_id,line_id), + KEY idx_ll_receiving_sku_plan (customer_id,plan_id,line_id) +) ENGINE=InnoDB COMMENT='海外PDA柜号FBA SKU实收进度'; + +CREATE TABLE IF NOT EXISTS ll_receiving_container_operation ( + id BIGINT NOT NULL COMMENT '柜号收货操作ID', session_id BIGINT NOT NULL COMMENT '柜号签收会话ID', + operator_key VARCHAR(128) NOT NULL COMMENT '操作账号唯一标识', action VARCHAR(32) NOT NULL COMMENT '操作动作', idempotency_key VARCHAR(255) NOT NULL COMMENT '客户端幂等键', + request_hash VARCHAR(128) NOT NULL COMMENT '请求内容哈希', request_json LONGTEXT NOT NULL COMMENT '请求内容快照', result_json LONGTEXT NOT NULL COMMENT '响应内容快照', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', PRIMARY KEY(id), + UNIQUE KEY uk_ll_receiving_operation(operator_key,idempotency_key), KEY idx_ll_receiving_operation_session(session_id,created_at) +) ENGINE=InnoDB COMMENT='海外PDA柜号收货幂等审计记录'; + +CREATE TABLE IF NOT EXISTS ll_pda_operation_history ( + id BIGINT NOT NULL COMMENT 'PDA作业历史ID', overseas_warehouse_id BIGINT NOT NULL COMMENT 'Token冻结的海外实际仓ID', + customer_id VARCHAR(128) NULL COMMENT '作业涉及的客户标识', operator_key VARCHAR(128) NOT NULL COMMENT 'PDA操作账号标识', + operation_type VARCHAR(64) NOT NULL COMMENT '作业类型', business_key VARCHAR(255) NOT NULL COMMENT '作业业务键', + status VARCHAR(32) NOT NULL COMMENT '作业结果状态', idempotency_key VARCHAR(255) NULL COMMENT '写操作幂等键', + request_hash VARCHAR(128) NULL COMMENT '写请求内容哈希', summary VARCHAR(1000) NULL COMMENT '作业结果摘要', + occurred_at DATETIME(3) NOT NULL COMMENT '作业发生时间', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + PRIMARY KEY(id), UNIQUE KEY uk_ll_pda_history_idempotency(operator_key,idempotency_key), + KEY idx_ll_pda_history_operator(overseas_warehouse_id,operator_key,occurred_at), + KEY idx_ll_pda_history_business(overseas_warehouse_id,operation_type,business_key) +) ENGINE=InnoDB COMMENT='海外PDA真实作业历史'; + +CREATE TABLE IF NOT EXISTS ll_pda_pallet_operation ( + id BIGINT NOT NULL COMMENT 'PDA托盘作业ID', overseas_warehouse_id BIGINT NOT NULL COMMENT 'Token冻结的海外实际仓ID', + operator_key VARCHAR(128) NOT NULL COMMENT 'PDA操作账号标识', idempotency_key VARCHAR(128) NOT NULL COMMENT '设备幂等键', + operation_type VARCHAR(64) NOT NULL COMMENT '托盘作业类型', request_hash VARCHAR(128) NOT NULL COMMENT '请求内容摘要', + status VARCHAR(32) NOT NULL COMMENT '处理状态:PROCESSING、COMPLETED、FAILED_COMPENSATED、COMPENSATION_REQUIRED', + result_json LONGTEXT NULL COMMENT '成功响应快照', remote_transitions_json LONGTEXT NULL COMMENT '实物托盘状态变更回执', + last_error VARCHAR(2000) NULL 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_pda_pallet_idempotency(operator_key,idempotency_key), + KEY idx_ll_pda_pallet_warehouse(overseas_warehouse_id,status,updated_at) +) ENGINE=InnoDB COMMENT='海外PDA托盘作业幂等、补偿与结果记录'; + +CREATE TABLE IF NOT EXISTS ll_pda_stocktake_task ( + id BIGINT NOT NULL COMMENT '盘点任务ID', task_no VARCHAR(64) NOT NULL COMMENT '盘点任务号', + overseas_warehouse_id BIGINT NOT NULL COMMENT 'Token冻结的海外实际仓ID', scope_type VARCHAR(16) NOT NULL COMMENT '盘点范围类型LOCATION或PALLET', + scope_resource_id BIGINT NOT NULL COMMENT '盘点库位或托盘资源ID', scope_code VARCHAR(128) NOT NULL COMMENT '盘点库位码或托盘码快照', + status VARCHAR(32) NOT NULL COMMENT '盘点任务状态', position_count INT NOT NULL COMMENT '库存位置总数', + counted_count INT NOT NULL DEFAULT 0 COMMENT '已盘库存位置数', difference_count INT NOT NULL DEFAULT 0 COMMENT '存在差异的库存位置数', + created_by VARCHAR(128) NOT NULL COMMENT '创建操作账号标识', submitted_by VARCHAR(128) NULL COMMENT '提交操作账号标识', + submit_reason VARCHAR(500) NULL COMMENT '盘点差异提交原因', idempotency_key VARCHAR(128) NOT NULL COMMENT '创建幂等键', + request_hash VARCHAR(128) NOT NULL COMMENT '创建请求哈希', version BIGINT NOT NULL DEFAULT 0 COMMENT '乐观锁版本', + created_at DATETIME(3) NOT NULL COMMENT '创建时间', updated_at DATETIME(3) NOT NULL COMMENT '更新时间', + submitted_at DATETIME(3) NULL COMMENT '提交完成时间', PRIMARY KEY(id), UNIQUE KEY uk_ll_pda_stocktake_no(task_no), + UNIQUE KEY uk_ll_pda_stocktake_create(overseas_warehouse_id,created_by,idempotency_key), + KEY idx_ll_pda_stocktake_list(overseas_warehouse_id,status,created_at) +) ENGINE=InnoDB COMMENT='海外PDA库存盘点任务'; + +CREATE TABLE IF NOT EXISTS ll_pda_stocktake_item ( + id BIGINT NOT NULL COMMENT '盘点明细ID', task_id BIGINT NOT NULL COMMENT '盘点任务ID', customer_id VARCHAR(128) NOT NULL COMMENT '库存所属客户标识', + balance_id BIGINT NOT NULL COMMENT '库存余额ID', position_id BIGINT NOT NULL COMMENT '库存位置ID', sku_code VARCHAR(128) NULL COMMENT 'SKU快照', + product_name VARCHAR(255) NULL COMMENT '品名快照', inbound_code VARCHAR(128) NULL COMMENT '入仓码快照', pallet_id BIGINT NOT NULL COMMENT '实物托盘ID', + pallet_code VARCHAR(128) NOT NULL COMMENT '实物托盘码快照', location_id BIGINT NOT NULL COMMENT '库位ID快照', location_code VARCHAR(128) NOT NULL COMMENT '库位码快照', + book_packages BIGINT NOT NULL COMMENT '任务创建时账面总箱数', actual_packages BIGINT NULL COMMENT '现场实盘总箱数', + difference_packages BIGINT NULL COMMENT '实盘与账面箱数差异', status VARCHAR(32) NOT NULL COMMENT '盘点明细状态', + position_version BIGINT NOT NULL COMMENT '任务创建时库存位置版本', counted_by VARCHAR(128) NULL COMMENT '盘点操作账号标识', + version BIGINT NOT NULL DEFAULT 0 COMMENT '明细乐观锁版本', counted_at DATETIME(3) NULL COMMENT '盘点时间', + created_at DATETIME(3) NOT NULL COMMENT '创建时间', updated_at DATETIME(3) NOT NULL COMMENT '更新时间', + PRIMARY KEY(id), UNIQUE KEY uk_ll_pda_stocktake_item(task_id,position_id), KEY idx_ll_pda_stocktake_item_task(task_id,status,id) +) ENGINE=InnoDB COMMENT='海外PDA库存盘点位置明细'; + +CREATE TABLE IF NOT EXISTS ll_pda_stocktake_action ( + id BIGINT NOT NULL COMMENT '盘点动作ID', overseas_warehouse_id BIGINT NOT NULL COMMENT 'Token冻结的海外实际仓ID', + operator_key VARCHAR(128) NOT NULL COMMENT 'PDA操作账号标识', idempotency_key VARCHAR(128) NOT NULL COMMENT '动作幂等键', + action_type VARCHAR(32) NOT NULL COMMENT '盘点动作类型', task_id BIGINT NULL COMMENT '盘点任务ID', request_hash VARCHAR(128) NOT NULL COMMENT '动作请求哈希', + status VARCHAR(32) NOT NULL COMMENT '动作处理状态', result_json LONGTEXT NULL COMMENT '成功响应JSON', + created_at DATETIME(3) NOT NULL COMMENT '创建时间', updated_at DATETIME(3) NOT NULL COMMENT '更新时间', PRIMARY KEY(id), + UNIQUE KEY uk_ll_pda_stocktake_action(overseas_warehouse_id,operator_key,idempotency_key), KEY idx_ll_pda_stocktake_action_task(task_id,created_at) +) ENGINE=InnoDB COMMENT='海外PDA盘点动作幂等记录'; + +CREATE TABLE IF NOT EXISTS ll_pda_inventory_move ( + id BIGINT NOT NULL COMMENT '移库操作ID', move_no VARCHAR(64) NOT NULL COMMENT '移库操作单号', + overseas_warehouse_id BIGINT NOT NULL COMMENT 'Token冻结的海外实际仓ID', pallet_id BIGINT NOT NULL COMMENT '整托移库的实物托盘ID', + pallet_code VARCHAR(128) NOT NULL COMMENT '实物托盘码快照', source_location_id BIGINT NOT NULL COMMENT '来源库位ID', + source_location_code VARCHAR(128) NOT NULL COMMENT '来源库位码快照', target_location_id BIGINT NOT NULL COMMENT '目标库位ID', + target_location_code VARCHAR(128) NOT NULL COMMENT '目标库位码快照', customer_count INT NOT NULL COMMENT '托盘涉及客户数', + position_count INT NOT NULL COMMENT '迁移库存位置数', package_count BIGINT NOT NULL COMMENT '迁移总箱数', + pallet_version_before INT NOT NULL COMMENT '迁移前托盘版本', pallet_version_after INT NULL COMMENT '迁移后托盘版本', + operator_key VARCHAR(128) NOT NULL COMMENT 'PDA操作账号标识', reason VARCHAR(500) NOT NULL COMMENT '移库原因', + idempotency_key VARCHAR(128) NOT NULL COMMENT '移库幂等键', request_hash VARCHAR(128) NOT NULL COMMENT '移库请求哈希', + status VARCHAR(32) NOT NULL COMMENT '移库处理状态', result_json LONGTEXT NULL COMMENT '成功响应JSON', + created_at DATETIME(3) NOT NULL COMMENT '创建时间', completed_at DATETIME(3) NULL COMMENT '完成时间', PRIMARY KEY(id), + UNIQUE KEY uk_ll_pda_inventory_move_no(move_no), + UNIQUE KEY uk_ll_pda_inventory_move_key(overseas_warehouse_id,operator_key,idempotency_key), + KEY idx_ll_pda_inventory_move_pallet(overseas_warehouse_id,pallet_id,created_at) +) ENGINE=InnoDB COMMENT='海外PDA同仓整托移库记录'; + +CREATE TABLE IF NOT EXISTS ll_inbound_fba_appointment ( + id BIGINT NOT NULL COMMENT '主键ID', + customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', + appointment_no VARCHAR(64) NOT NULL COMMENT '预约单号', + appointment_at DATETIME(3) NOT NULL COMMENT '预约时间', + expected_loading_at DATETIME(3) NOT NULL COMMENT '预计装车时间', + appointment_status VARCHAR(32) NOT NULL DEFAULT 'PENDING' COMMENT '预约状态', + appointment_remark VARCHAR(1000) NULL COMMENT '预约备注', + fba_warehouse_code VARCHAR(128) NOT NULL COMMENT 'FBA仓库编码', + selected_count INT NOT NULL DEFAULT 0 COMMENT '已选明细数量', + created_by VARCHAR(128) NULL COMMENT '创建人ID', + 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_fba_appointment_no (customer_id, appointment_no), + KEY idx_ll_fba_appointment_list (customer_id, appointment_status, fba_warehouse_code, created_at) +) ENGINE=InnoDB COMMENT='FBA入仓预约单'; + +CREATE TABLE IF NOT EXISTS ll_inbound_fba_appointment_item ( + id BIGINT NOT NULL COMMENT '主键ID', + appointment_id BIGINT NOT NULL COMMENT '预约单ID', + plan_id BIGINT NOT NULL COMMENT '入仓计划ID', + line_id BIGINT NOT NULL COMMENT '入仓计划明细ID', + order_id BIGINT NOT NULL COMMENT '订单ID', + inbound_code VARCHAR(128) NOT NULL COMMENT '入仓单号快照', + fba_warehouse_code VARCHAR(128) NOT NULL COMMENT 'FBA仓库编码', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + PRIMARY KEY (id), + UNIQUE KEY uk_ll_fba_appointment_item_line (appointment_id, line_id), + KEY idx_ll_fba_appointment_item_plan (plan_id), + KEY idx_ll_fba_appointment_item_order (order_id) +) ENGINE=InnoDB COMMENT='FBA入仓预约明细'; + +ALTER TABLE ll_loading_order_status_history + MODIFY COLUMN id BIGINT NOT NULL AUTO_INCREMENT COMMENT '状态历史主键', + MODIFY COLUMN order_id BIGINT NOT NULL COMMENT '尾程订单 ID', + MODIFY COLUMN from_status VARCHAR(32) NULL COMMENT '变更前状态', + MODIFY COLUMN to_status VARCHAR(32) NOT NULL COMMENT '变更后状态', + MODIFY COLUMN event_id VARCHAR(128) NULL COMMENT '触发事件 ID', + MODIFY COLUMN remark VARCHAR(500) NULL COMMENT '状态变更备注', + MODIFY COLUMN created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '记录时间'; + +CREATE TABLE IF NOT EXISTS ll_inventory_balance ( + id BIGINT NOT NULL COMMENT '主键ID', + customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', customer_name VARCHAR(255) NOT NULL COMMENT '客户名称不可变快照', + overseas_warehouse_id VARCHAR(128) NOT NULL COMMENT '海外仓ID', + overseas_warehouse_code VARCHAR(128) NULL COMMENT '海外仓编码快照', + overseas_warehouse_name VARCHAR(255) NULL COMMENT '海外仓名称快照', + destination_warehouse_id VARCHAR(128) NULL COMMENT '目的仓ID', + destination_warehouse_code VARCHAR(128) NOT NULL DEFAULT '' COMMENT '目的仓编码快照', + destination_warehouse_name VARCHAR(255) NULL COMMENT '目的仓名称快照', + inbound_order_id BIGINT NOT NULL COMMENT '入仓单ID', + inbound_order_no VARCHAR(128) NULL COMMENT '入仓单号快照', + inbound_code VARCHAR(128) NOT NULL COMMENT '入仓单号快照', + sku_code VARCHAR(128) NOT NULL COMMENT 'SKU快照;多SKU汇总时为MIXED,不参与当前库存唯一维度', + product_name_zh VARCHAR(255) NULL COMMENT '中文品名快照', + product_name_en VARCHAR(255) NULL COMMENT '英文品名快照', + available_packages BIGINT NOT NULL DEFAULT 0 COMMENT '可用箱数', hold_packages BIGINT NOT NULL DEFAULT 0 COMMENT '冻结箱数', + allocated_packages BIGINT NOT NULL DEFAULT 0 COMMENT '已分配箱数', picked_packages BIGINT NOT NULL DEFAULT 0 COMMENT '已拣箱数', + in_transit_packages BIGINT NOT NULL DEFAULT 0 COMMENT '在途箱数', receiving_packages BIGINT NOT NULL DEFAULT 0 COMMENT '收货处理中箱数', + procurement_packages BIGINT NOT NULL DEFAULT 0 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 '更新时间', + PRIMARY KEY (id), + UNIQUE KEY uk_ll_inventory_dimension (customer_id,overseas_warehouse_id,destination_warehouse_code,inbound_order_id,inbound_code), + KEY idx_ll_inventory_list (customer_id,overseas_warehouse_id,updated_at), + KEY idx_ll_inventory_inbound (customer_id,inbound_code), + KEY idx_ll_inventory_sku (customer_id,sku_code,updated_at) +) ENGINE=InnoDB COMMENT='客户入仓单与海外目的仓维度实时库存余额'; + +CREATE TABLE IF NOT EXISTS ll_inventory_position ( + id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', balance_id BIGINT NOT NULL COMMENT '库存余额记录ID', + overseas_warehouse_id VARCHAR(128) NOT NULL COMMENT '海外仓ID', destination_warehouse_code VARCHAR(128) NOT NULL DEFAULT '' COMMENT '目的仓编码快照', + pallet_id BIGINT NOT NULL COMMENT '托盘ID', pallet_no VARCHAR(64) NOT NULL COMMENT '托盘号', + batch_no VARCHAR(128) NOT NULL DEFAULT '' COMMENT '库存批次号', warehouse_location_id BIGINT NULL COMMENT '仓库库位ID', + warehouse_location_code VARCHAR(128) NOT NULL COMMENT '仓库库位编码', + available_packages BIGINT NOT NULL DEFAULT 0 COMMENT '可用箱数', hold_packages BIGINT NOT NULL DEFAULT 0 COMMENT '冻结箱数', + allocated_packages BIGINT NOT NULL DEFAULT 0 COMMENT '已分配箱数', picked_packages BIGINT NOT NULL DEFAULT 0 COMMENT '已拣箱数', + receiving_packages BIGINT NOT NULL DEFAULT 0 COMMENT '收货处理中箱数', + status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE' 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 '更新时间', + PRIMARY KEY (id), UNIQUE KEY uk_ll_inventory_position (balance_id,pallet_id,batch_no), + KEY idx_ll_inventory_position_pallet (customer_id,pallet_id), + KEY idx_ll_inventory_position_location (customer_id,overseas_warehouse_id,warehouse_location_code) +) ENGINE=InnoDB COMMENT='托盘库位批次库存明细'; + +CREATE TABLE IF NOT EXISTS ll_inventory_ledger ( + id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', customer_name VARCHAR(255) NOT NULL COMMENT '客户名称不可变快照', + balance_id BIGINT NOT NULL COMMENT '库存余额记录ID', position_id BIGINT NULL COMMENT '库存仓位记录ID', overseas_warehouse_id VARCHAR(128) NOT NULL COMMENT '海外仓ID', + destination_warehouse_code VARCHAR(128) NOT NULL DEFAULT '' COMMENT '目的仓编码快照', inbound_order_id BIGINT NOT NULL COMMENT '入仓单ID', + pallet_id BIGINT NULL COMMENT '托盘ID', pallet_no VARCHAR(64) NULL COMMENT '托盘号', batch_no VARCHAR(128) NULL COMMENT '库存批次号', + warehouse_location_code VARCHAR(128) NULL COMMENT '仓库库位编码', sku_code VARCHAR(128) NULL COMMENT 'SKU编码', carton_mark VARCHAR(128) NULL COMMENT '箱唛', carton_packages BIGINT NULL COMMENT '箱内箱数', + business_type VARCHAR(32) NOT NULL COMMENT '业务类型', + business_order_id BIGINT NULL COMMENT '业务单据ID', business_order_no VARCHAR(128) NULL COMMENT '业务单号', inbound_code VARCHAR(128) NULL COMMENT '入仓单号快照', + inbound_order_no VARCHAR(128) NULL COMMENT '入仓单号快照', outbound_order_no VARCHAR(128) NULL COMMENT '出库单号', + source_event_id VARCHAR(128) NULL COMMENT '来源事件ID', idempotency_key VARCHAR(255) NOT NULL COMMENT '业务幂等键', + status_from VARCHAR(32) NULL COMMENT '操作前状态', status_to VARCHAR(32) NULL COMMENT '操作后状态', + before_packages BIGINT NOT NULL COMMENT '变动前箱数', delta_packages BIGINT NOT NULL COMMENT '箱数变动量', after_packages BIGINT NOT NULL COMMENT '变动后箱数', + operator_id VARCHAR(128) NULL COMMENT '操作人ID', remark VARCHAR(1000) NULL COMMENT '备注', + occurred_at DATETIME(3) NOT NULL COMMENT '业务发生时间', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + PRIMARY KEY (id), UNIQUE KEY uk_ll_inventory_ledger_idempotency (customer_id,idempotency_key), + KEY idx_ll_inventory_ledger_query (customer_id,overseas_warehouse_id,occurred_at), + KEY idx_ll_inventory_ledger_inbound (customer_id,inbound_code,occurred_at), + KEY idx_ll_inventory_ledger_business (customer_id,business_type,business_order_no) +) ENGINE=InnoDB COMMENT='不可变库存变动流水'; + +CREATE TABLE IF NOT EXISTS ll_inventory_operation ( + id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', + idempotency_key VARCHAR(255) NOT NULL COMMENT '业务幂等键', operation_type VARCHAR(32) NOT NULL COMMENT '操作类型', + request_hash VARCHAR(128) NOT NULL COMMENT '请求内容哈希', status VARCHAR(32) NOT NULL DEFAULT 'PROCESSING' COMMENT '库存命令处理状态', + result_json LONGTEXT NULL COMMENT '处理结果JSON', last_error VARCHAR(2000) NULL 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_inventory_operation_key (customer_id,idempotency_key), + KEY idx_ll_inventory_operation_status (customer_id,status,updated_at) +) ENGINE=InnoDB COMMENT='库存命令幂等记录'; + +CREATE TABLE IF NOT EXISTS ll_inbound_stock_receipt ( + id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', + plan_id BIGINT NOT NULL COMMENT '入仓计划ID', task_id BIGINT NOT NULL COMMENT 'PDA任务ID', allocation_id BIGINT NOT NULL COMMENT '分配记录ID', + line_id BIGINT NOT NULL COMMENT '入仓计划明细ID', order_id BIGINT NOT NULL COMMENT '订单ID', inbound_code VARCHAR(128) NOT NULL COMMENT '入仓单号快照', + sku_code VARCHAR(128) NOT NULL COMMENT 'SKU编码快照', product_name_zh VARCHAR(255) NULL COMMENT '中文品名快照', + product_name_en VARCHAR(255) NULL COMMENT '英文品名快照', + warehouse_pallet_id BIGINT NOT NULL COMMENT '仓库实物托盘ID', pallet_no VARCHAR(64) NOT NULL COMMENT '海外仓实物托盘码', + source_pallet_no VARCHAR(128) NULL COMMENT '一件代发不可变来源托盘号快照', + warehouse_location_id BIGINT NULL COMMENT '仓库库位ID', warehouse_location_code VARCHAR(128) NULL COMMENT '仓库库位编码', + batch_no VARCHAR(128) NOT NULL COMMENT '库存批次号', received_packages BIGINT NOT NULL COMMENT '实收箱数', + inventory_balance_id BIGINT NOT NULL COMMENT '库存余额记录ID', inventory_position_id BIGINT NOT NULL COMMENT '库存仓位记录ID', + inventory_operation_key VARCHAR(255) NOT NULL COMMENT '库存操作幂等键', received_at DATETIME(3) NOT NULL COMMENT '实收时间', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + PRIMARY KEY (id), UNIQUE KEY uk_ll_stock_receipt_task_allocation_sku (customer_id,task_id,allocation_id,sku_code), + KEY idx_ll_stock_receipt_plan (customer_id,plan_id,line_id), KEY idx_ll_stock_receipt_inventory (inventory_balance_id,inventory_position_id) +) ENGINE=InnoDB COMMENT='入仓PDA库存实收快照'; + +CREATE TABLE IF NOT EXISTS ll_dispatch_order ( + id BIGINT NOT NULL COMMENT '主键ID', + customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', + dispatch_no VARCHAR(64) NOT NULL COMMENT '派车单号', + overseas_warehouse_id VARCHAR(128) NULL COMMENT '海外仓ID', + overseas_warehouse_name VARCHAR(255) NULL COMMENT '海外仓名称快照', + vehicle_plate VARCHAR(64) NULL COMMENT '车牌号', + driver_name VARCHAR(128) NULL COMMENT '司机姓名', + driver_phone VARCHAR(64) NULL COMMENT '司机电话', + carrier_name VARCHAR(255) NULL COMMENT '承运商名称', + route VARCHAR(255) NULL COMMENT '运输路线', + depart_at DATETIME(3) NULL COMMENT '发车时间', + fba_appointment_no VARCHAR(64) NULL COMMENT 'FBA预约号', + fba_appointment_status VARCHAR(32) NOT NULL DEFAULT 'PENDING' COMMENT 'FBA预约状态', + vehicle_appointment_status VARCHAR(32) NOT NULL DEFAULT 'PENDING' COMMENT '车辆预约状态', + estimated_arrival_at DATETIME(3) NULL COMMENT '预计到达时间', + status VARCHAR(32) NOT NULL DEFAULT 'PENDING_DEPARTURE' COMMENT '业务状态', + item_count INT NOT NULL DEFAULT 0 COMMENT '明细数量', + pallet_count INT NOT NULL DEFAULT 0 COMMENT '托盘数', + package_count INT NOT NULL DEFAULT 0 COMMENT '箱数', + picked_package_count INT NOT NULL DEFAULT 0 COMMENT 'PDA累计已拣箱数', + shipped_package_count INT NOT NULL DEFAULT 0 COMMENT '累计已出库箱数', + partial_completion_reason VARCHAR(1000) NULL COMMENT '部分完成原因', + created_by VARCHAR(128) NULL COMMENT '创建人ID', + 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_dispatch_order_no (dispatch_no), + KEY idx_ll_dispatch_order_list (customer_id,status,depart_at), + KEY idx_ll_dispatch_order_vehicle (customer_id,vehicle_plate) +) ENGINE=InnoDB COMMENT='尾程派车单'; + +CREATE TABLE IF NOT EXISTS ll_dispatch_item ( + id BIGINT NOT NULL COMMENT '主键ID', + dispatch_order_id BIGINT NOT NULL COMMENT '派车单ID', + customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', + plan_id BIGINT NULL COMMENT '入仓计划ID', + plan_pallet_id BIGINT NULL COMMENT '计划托盘ID', + warehouse_pallet_id BIGINT NULL COMMENT '仓库实物托盘ID', + order_id BIGINT NULL COMMENT '订单ID', + order_no VARCHAR(64) NULL COMMENT '订单号', + order_refs VARCHAR(2000) NULL COMMENT '关联订单信息', + inbound_code VARCHAR(128) NULL COMMENT '入仓单号快照', + pallet_no VARCHAR(128) NOT NULL COMMENT '托盘号', + overseas_warehouse_id VARCHAR(128) NULL COMMENT '海外仓ID', + overseas_warehouse_name VARCHAR(255) NULL COMMENT '海外仓名称快照', + destination_warehouse_id VARCHAR(128) NULL COMMENT '目的仓ID', + destination_warehouse_code VARCHAR(128) NULL COMMENT '目的仓编码快照', + destination_warehouse_name VARCHAR(255) NULL COMMENT '目的仓名称快照', + fba_warehouse_code VARCHAR(128) NULL COMMENT 'FBA仓库编码', + package_count INT NOT NULL DEFAULT 0 COMMENT '箱数', + volume_cbm DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT '体积(立方米)', + source_stage VARCHAR(32) NOT NULL COMMENT '数据来源阶段', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + PRIMARY KEY (id), + UNIQUE KEY uk_ll_dispatch_item_source (dispatch_order_id,plan_pallet_id,warehouse_pallet_id), + KEY idx_ll_dispatch_item_order (customer_id,order_id), + KEY idx_ll_dispatch_item_inbound (customer_id,inbound_code), + KEY idx_ll_dispatch_item_pallet (customer_id,plan_pallet_id,warehouse_pallet_id) +) ENGINE=InnoDB COMMENT='派车货物冻结明细'; + + CREATE TABLE IF NOT EXISTS ll_dispatch_pallet_lock ( + id BIGINT NOT NULL COMMENT '主键ID', + customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', + source_pallet_key VARCHAR(160) NOT NULL COMMENT '来源托盘业务键', + active_key VARCHAR(160) NULL COMMENT '生效中的资源排他键', + plan_id BIGINT NULL COMMENT '入仓计划ID', + plan_pallet_id BIGINT NULL COMMENT '计划托盘ID', + warehouse_pallet_id BIGINT NULL COMMENT '仓库实物托盘ID', + dispatch_order_id BIGINT NULL COMMENT '正式派车单ID,草稿锁阶段为空', + draft_container_id BIGINT NULL COMMENT '智能排车草稿装车托盘ID', + draft_token VARCHAR(64) NULL COMMENT '智能排车草稿随机标识', + operator_id VARCHAR(128) NULL COMMENT '草稿锁操作人ID', + status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE' COMMENT '业务状态', + locked_at DATETIME(3) NOT NULL COMMENT '锁定时间', + expires_at DATETIME(3) NULL COMMENT '草稿锁失效时间,正式派车锁为空', + released_at DATETIME(3) NULL 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_dispatch_active_pallet (customer_id,active_key), + KEY idx_ll_dispatch_pallet_lock_order (customer_id,dispatch_order_id,status), + KEY idx_ll_dispatch_pallet_lock_source (customer_id,source_pallet_key,status) + ) ENGINE=InnoDB COMMENT='派车托盘排他锁'; + + CREATE TABLE IF NOT EXISTS ll_dispatch_draft ( + id BIGINT NOT NULL COMMENT '主键ID', draft_token VARCHAR(64) NOT NULL COMMENT '智能排车草稿随机标识', + customer_id VARCHAR(128) NULL COMMENT '客户租户ID', operator_id VARCHAR(128) NULL COMMENT '草稿操作人ID', + plan_ids_json VARCHAR(4000) NULL COMMENT '所选入仓计划ID列表JSON', containers_json LONGTEXT NULL COMMENT '草稿装车布局JSON快照', + status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE' COMMENT '草稿状态', expires_at DATETIME(3) NULL COMMENT '草稿失效时间', + released_at DATETIME(3) NULL 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_dispatch_draft_token(draft_token), KEY idx_ll_dispatch_draft_operator(operator_id,status) + ) ENGINE=InnoDB COMMENT='智能排车草稿'; + + CREATE TABLE IF NOT EXISTS ll_dispatch_draft_container ( + id BIGINT NOT NULL COMMENT '主键ID', draft_id BIGINT NOT NULL COMMENT '智能排车草稿ID', + draft_token VARCHAR(64) NOT NULL COMMENT '智能排车草稿随机标识', sequence_no INT NOT NULL COMMENT '草稿车厢顺序号', + capacity INT NOT NULL 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), + KEY idx_ll_dispatch_draft_container_token(draft_token) + ) ENGINE=InnoDB COMMENT='智能排车草稿装车托盘'; + + SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_dispatch_pallet_lock' AND column_name='draft_container_id'), 'SELECT 1', 'ALTER TABLE ll_dispatch_pallet_lock ADD COLUMN draft_container_id BIGINT NULL COMMENT ''智能排车草稿装车托盘ID'' AFTER dispatch_order_id')); + PREPARE dispatch_draft_container_column_stmt FROM @ddl; EXECUTE dispatch_draft_container_column_stmt; DEALLOCATE PREPARE dispatch_draft_container_column_stmt; + +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_dispatch_pallet_lock' AND column_name='draft_token'), 'SELECT 1', 'ALTER TABLE ll_dispatch_pallet_lock ADD COLUMN draft_token VARCHAR(64) NULL COMMENT ''智能排车草稿随机标识'' AFTER dispatch_order_id')); +PREPARE dispatch_schema_stmt FROM @ddl; EXECUTE dispatch_schema_stmt; DEALLOCATE PREPARE dispatch_schema_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_dispatch_pallet_lock' AND column_name='operator_id'), 'SELECT 1', 'ALTER TABLE ll_dispatch_pallet_lock ADD COLUMN operator_id VARCHAR(128) NULL COMMENT ''草稿锁操作人ID'' AFTER draft_token')); +PREPARE dispatch_schema_stmt FROM @ddl; EXECUTE dispatch_schema_stmt; DEALLOCATE PREPARE dispatch_schema_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_dispatch_pallet_lock' AND column_name='expires_at'), 'SELECT 1', 'ALTER TABLE ll_dispatch_pallet_lock ADD COLUMN expires_at DATETIME(3) NULL COMMENT ''草稿锁失效时间,正式派车锁为空'' AFTER locked_at')); +PREPARE dispatch_schema_stmt FROM @ddl; EXECUTE dispatch_schema_stmt; DEALLOCATE PREPARE dispatch_schema_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_dispatch_pallet_lock' AND column_name='dispatch_order_id' AND is_nullable='YES'), 'SELECT 1', 'ALTER TABLE ll_dispatch_pallet_lock MODIFY COLUMN dispatch_order_id BIGINT NULL COMMENT ''正式派车单ID,草稿锁阶段为空''')); +PREPARE dispatch_schema_stmt FROM @ddl; EXECUTE dispatch_schema_stmt; DEALLOCATE PREPARE dispatch_schema_stmt; + +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_dispatch_item' AND column_name='order_refs'), 'SELECT 1', 'ALTER TABLE ll_dispatch_item ADD COLUMN order_refs VARCHAR(2000) NULL AFTER order_no')); +PREPARE dispatch_schema_stmt FROM @ddl; EXECUTE dispatch_schema_stmt; DEALLOCATE PREPARE dispatch_schema_stmt; + +CREATE TABLE IF NOT EXISTS ll_dispatch_pda_task ( + id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', dispatch_order_id BIGINT NOT NULL COMMENT '派车单ID', + task_no VARCHAR(64) NOT NULL COMMENT 'PDA任务号', status VARCHAR(32) NOT NULL COMMENT '业务状态', operator_id VARCHAR(128) NULL COMMENT '操作人ID', + warehouse_id VARCHAR(128) NOT NULL COMMENT 'Token绑定的海外仓库ID', + operator_name VARCHAR(128) NULL COMMENT '操作人姓名', pushed_at DATETIME(3) NOT NULL COMMENT '任务下发时间', completed_at DATETIME(3) NULL 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_dispatch_pda_task_dispatch (customer_id,dispatch_order_id), + UNIQUE KEY uk_ll_dispatch_pda_task_no (task_no) +) ENGINE=InnoDB COMMENT='派车PDA拣货任务'; + +CREATE TABLE IF NOT EXISTS ll_dispatch_pda_pick ( + id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', task_id BIGINT NOT NULL COMMENT 'PDA任务ID', + dispatch_item_id BIGINT NOT NULL COMMENT '派车明细ID', inventory_position_id BIGINT NOT NULL COMMENT '库存仓位记录ID', + packages BIGINT NOT NULL COMMENT '本次操作箱数', product_quantity BIGINT NOT NULL DEFAULT 0 COMMENT '产品数量', idempotency_key VARCHAR(255) NOT NULL COMMENT '业务幂等键', + picked_at DATETIME(3) NOT NULL COMMENT '拣货时间', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + PRIMARY KEY (id), UNIQUE KEY uk_ll_dispatch_pda_pick_key (customer_id,idempotency_key), + KEY idx_ll_dispatch_pda_pick_item (customer_id,task_id,dispatch_item_id) +) ENGINE=InnoDB COMMENT='派车PDA拣货记录'; + +CREATE TABLE IF NOT EXISTS ll_dispatch_pda_operation ( + id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', task_id BIGINT NULL COMMENT 'PDA任务ID', + operation_type VARCHAR(32) NOT NULL COMMENT 'PDA操作类型', idempotency_key VARCHAR(255) NOT NULL COMMENT '业务幂等键', request_hash CHAR(64) NOT NULL COMMENT '请求内容哈希', + result_json LONGTEXT NULL COMMENT '处理结果JSON', status VARCHAR(32) NOT NULL COMMENT 'PDA操作处理状态', + 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_dispatch_pda_operation_key (customer_id,idempotency_key) +) ENGINE=InnoDB COMMENT='派车PDA操作幂等记录'; + +CREATE TABLE IF NOT EXISTS ll_dispatch_pda_pick_order ( + id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', pick_id BIGINT NOT NULL COMMENT 'PDA拣货记录ID', + receipt_id BIGINT NOT NULL COMMENT '入仓实收记录ID', order_id BIGINT NOT NULL COMMENT '订单ID', packages BIGINT NOT NULL COMMENT '本次操作箱数', + product_quantity BIGINT NOT NULL DEFAULT 0 COMMENT '产品数量', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + PRIMARY KEY (id), KEY idx_ll_dispatch_pick_order_pick (customer_id,pick_id), + KEY idx_ll_dispatch_pick_order_receipt (customer_id,receipt_id) +) ENGINE=InnoDB COMMENT='PDA拣货订单归属记录'; + +-- Transfer a dispatch to delivery: one outbound header, many pallet rows, one delivery header. +CREATE TABLE IF NOT EXISTS ll_outbound_order ( + id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', + outbound_no VARCHAR(64) NOT NULL COMMENT '出库单号', dispatch_order_id BIGINT NULL COMMENT '派车单ID,一件代发为空', dispatch_no VARCHAR(64) NULL COMMENT '派车单号,一件代发为空', + overseas_warehouse_id VARCHAR(128) NULL COMMENT '海外仓ID', status VARCHAR(32) NOT NULL DEFAULT 'WAIT_OUTBOUND' COMMENT '业务状态', + package_count INT NOT NULL DEFAULT 0 COMMENT '箱数', pallet_count INT NOT NULL DEFAULT 0 COMMENT '托盘数', + idempotency_key VARCHAR(255) NOT NULL COMMENT '业务幂等键', created_by VARCHAR(128) NULL COMMENT '创建人ID', + 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 '更新时间', + outbound_type VARCHAR(32) NOT NULL DEFAULT 'DISPATCH_DELIVERY' COMMENT '出库类型:派送转出库、一件代发', lx_codes VARCHAR(1000) NULL COMMENT '关联一件代发领星入库单代码汇总', + recipient_info VARCHAR(1000) NULL COMMENT '一件代发收件信息', + PRIMARY KEY (id), UNIQUE KEY uk_ll_outbound_no (outbound_no), + UNIQUE KEY uk_ll_outbound_idempotency (customer_id,idempotency_key), UNIQUE KEY uk_ll_outbound_dispatch (customer_id,dispatch_order_id), + KEY idx_ll_outbound_list (customer_id,status,created_at) +) ENGINE=InnoDB COMMENT='尾程出库单'; + +CREATE TABLE IF NOT EXISTS ll_outbound_pallet_item ( + id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', outbound_order_id BIGINT NOT NULL COMMENT '出库单ID', + warehouse_pallet_id BIGINT NULL COMMENT '仓库实物托盘ID', pallet_no VARCHAR(128) NULL COMMENT '托盘号', + destination_warehouse_id VARCHAR(128) NULL COMMENT '目的仓ID', destination_warehouse_code VARCHAR(128) NULL COMMENT '目的仓编码快照', destination_warehouse_name VARCHAR(255) NULL COMMENT '目的仓名称快照', + package_count INT NOT NULL DEFAULT 0 COMMENT '箱数', picked_package_count INT NOT NULL DEFAULT 0 COMMENT '已拣箱数', status VARCHAR(32) NOT NULL DEFAULT 'WAIT_PICK' COMMENT '拣货状态', sku_code VARCHAR(128) NULL COMMENT '一件代发SKU', order_refs VARCHAR(2000) NULL COMMENT '关联订单信息', inbound_code VARCHAR(128) NULL COMMENT '入仓单号快照', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', PRIMARY KEY (id), + UNIQUE KEY uk_ll_outbound_pallet (customer_id,outbound_order_id,warehouse_pallet_id), + KEY idx_ll_outbound_pallet_list (customer_id,outbound_order_id), KEY idx_ll_outbound_pallet_destination (customer_id,destination_warehouse_code) +) ENGINE=InnoDB COMMENT='出库托盘明细'; + +CREATE TABLE IF NOT EXISTS ll_outbound_pda_task ( + id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', outbound_order_id BIGINT NOT NULL COMMENT '出库单ID', + task_no VARCHAR(64) NOT NULL COMMENT 'PDA任务号', status VARCHAR(32) NOT NULL DEFAULT 'PENDING' COMMENT '任务状态:待拣货、拣货中、已完成', + completion_idempotency_key VARCHAR(255) NULL COMMENT '完成命令幂等键', + pushed_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '推送时间', completed_at DATETIME(3) NULL 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_outbound_pda_task_order(customer_id,outbound_order_id), UNIQUE KEY uk_ll_outbound_pda_task_no(task_no), + UNIQUE KEY uk_ll_outbound_pda_complete_key(customer_id,completion_idempotency_key) +) ENGINE=InnoDB COMMENT='一件代发出库PDA拣货任务'; + +CREATE TABLE IF NOT EXISTS ll_outbound_pda_pick ( + id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户数据隔离键', task_id BIGINT NOT NULL COMMENT 'PDA任务ID', outbound_pallet_item_id BIGINT NOT NULL COMMENT '出库托盘明细ID', + inventory_position_id BIGINT NOT NULL COMMENT '库存仓位ID', packages BIGINT NOT NULL COMMENT '拣货箱数', idempotency_key VARCHAR(255) NOT NULL COMMENT '拣货幂等键', + picked_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '拣货时间', PRIMARY KEY(id), UNIQUE KEY uk_ll_outbound_pda_pick_key(customer_id,idempotency_key) +) ENGINE=InnoDB COMMENT='一件代发出库PDA托盘拣货记录'; + +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_outbound_order' AND column_name='outbound_type'), 'SELECT 1', 'ALTER TABLE ll_outbound_order ADD COLUMN outbound_type VARCHAR(32) NOT NULL DEFAULT ''DISPATCH_DELIVERY'' COMMENT ''出库类型'' AFTER status')); +PREPARE outbound_upgrade_stmt FROM @ddl; EXECUTE outbound_upgrade_stmt; DEALLOCATE PREPARE outbound_upgrade_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_outbound_order' AND column_name='lx_codes'), 'SELECT 1', 'ALTER TABLE ll_outbound_order ADD COLUMN lx_codes VARCHAR(1000) NULL COMMENT ''关联一件代发领星入库单代码汇总'' AFTER outbound_type')); +PREPARE outbound_upgrade_stmt FROM @ddl; EXECUTE outbound_upgrade_stmt; DEALLOCATE PREPARE outbound_upgrade_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_outbound_pallet_item' AND column_name='sku_code'), 'SELECT 1', 'ALTER TABLE ll_outbound_pallet_item ADD COLUMN sku_code VARCHAR(128) NULL COMMENT ''一件代发SKU''')); +PREPARE outbound_upgrade_stmt FROM @ddl; EXECUTE outbound_upgrade_stmt; DEALLOCATE PREPARE outbound_upgrade_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_outbound_order' AND column_name='recipient_info'), 'SELECT 1', 'ALTER TABLE ll_outbound_order ADD COLUMN recipient_info VARCHAR(1000) NULL COMMENT ''一件代发收件信息'' AFTER lx_codes')); +PREPARE outbound_upgrade_stmt FROM @ddl; EXECUTE outbound_upgrade_stmt; DEALLOCATE PREPARE outbound_upgrade_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_outbound_pallet_item' AND column_name='picked_package_count'), 'SELECT 1', 'ALTER TABLE ll_outbound_pallet_item ADD COLUMN picked_package_count INT NOT NULL DEFAULT 0 COMMENT ''已拣箱数'' AFTER package_count')); +PREPARE outbound_upgrade_stmt FROM @ddl; EXECUTE outbound_upgrade_stmt; DEALLOCATE PREPARE outbound_upgrade_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_outbound_pallet_item' AND column_name='status'), 'SELECT 1', 'ALTER TABLE ll_outbound_pallet_item ADD COLUMN status VARCHAR(32) NOT NULL DEFAULT ''WAIT_PICK'' COMMENT ''拣货状态'' AFTER picked_package_count')); +PREPARE outbound_upgrade_stmt FROM @ddl; EXECUTE outbound_upgrade_stmt; DEALLOCATE PREPARE outbound_upgrade_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_outbound_order' AND column_name='dispatch_order_id' AND is_nullable='YES'), 'SELECT 1', 'ALTER TABLE ll_outbound_order MODIFY COLUMN dispatch_order_id BIGINT NULL COMMENT ''派车单ID,一件代发为空'', MODIFY COLUMN dispatch_no VARCHAR(64) NULL COMMENT ''派车单号,一件代发为空''')); +PREPARE outbound_upgrade_stmt FROM @ddl; EXECUTE outbound_upgrade_stmt; DEALLOCATE PREPARE outbound_upgrade_stmt; + +-- Customer warehouse transfer: generated tail order plus selected historical order/inbound bindings. +CREATE TABLE IF NOT EXISTS ll_warehouse_transfer_order ( + id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', + transfer_no VARCHAR(64) NOT NULL COMMENT '转仓单号', inbound_order_id BIGINT NOT NULL COMMENT '入仓单ID', inbound_code VARCHAR(128) NOT NULL COMMENT '入仓单号快照', + lastleg_order_id BIGINT NOT NULL COMMENT '尾程订单ID', lastleg_order_no VARCHAR(32) NOT NULL COMMENT '尾程订单号', + operating_warehouse_id VARCHAR(128) NOT NULL COMMENT '作业仓库ID', fba_warehouse_code VARCHAR(128) NOT NULL COMMENT 'FBA仓库编码', + delivery_address VARCHAR(1000) NOT NULL COMMENT '派送地址', package_count INT NOT NULL COMMENT '箱数', delivery_method VARCHAR(64) NOT NULL COMMENT '派送方式', + estimated_arrival_at DATETIME(3) NOT NULL COMMENT '预计到达时间', fba_shipment_no VARCHAR(128) NOT NULL COMMENT 'FBA货件编号', + tracking_no VARCHAR(255) NULL COMMENT '物流跟踪号', fba_box_nos VARCHAR(2000) NULL COMMENT 'FBA箱号集合', remark VARCHAR(1000) NULL COMMENT '备注', + need_relabel TINYINT NOT NULL DEFAULT 0 COMMENT '是否需要换标', relabel_application_id BIGINT NULL COMMENT '换标申请单ID', relabel_no VARCHAR(64) NULL COMMENT '换标申请单号', + status VARCHAR(32) NOT NULL DEFAULT 'PENDING_REVIEW' COMMENT '业务状态', idempotency_key VARCHAR(255) NOT NULL COMMENT '业务幂等键', + allocation_idempotency_key VARCHAR(255) NULL COMMENT '库存分配幂等键', + allocation_request_hash CHAR(64) NULL COMMENT '库存分配请求哈希', + outbound_idempotency_key VARCHAR(255) NULL COMMENT '出库幂等键', + outbound_request_hash CHAR(64) NULL COMMENT '出库请求哈希', + outbound_at DATETIME(3) NULL COMMENT '出库时间', + request_hash CHAR(64) NOT NULL COMMENT '请求内容哈希', created_by VARCHAR(128) NULL COMMENT '创建人ID', + 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_wh_transfer_no (customer_id,transfer_no), + UNIQUE KEY uk_ll_wh_transfer_key (customer_id,idempotency_key), + UNIQUE KEY uk_ll_wh_transfer_tail_order (lastleg_order_id), + KEY idx_ll_wh_transfer_list (customer_id,status,created_at), KEY idx_ll_wh_transfer_inbound (customer_id,inbound_code) +) ENGINE=InnoDB COMMENT='客户转仓单'; + +-- 兼容已存在的旧版转仓单表:CREATE TABLE IF NOT EXISTS 不会补充新增字段。 +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_warehouse_transfer_order' AND column_name='allocation_idempotency_key'), 'SELECT 1', 'ALTER TABLE ll_warehouse_transfer_order ADD COLUMN allocation_idempotency_key VARCHAR(255) NULL COMMENT ''库存分配幂等键'' AFTER status')); +PREPARE transfer_allocation_key_stmt FROM @ddl; EXECUTE transfer_allocation_key_stmt; DEALLOCATE PREPARE transfer_allocation_key_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_warehouse_transfer_order' AND column_name='allocation_request_hash'), 'SELECT 1', 'ALTER TABLE ll_warehouse_transfer_order ADD COLUMN allocation_request_hash CHAR(64) NULL COMMENT ''库存分配请求哈希'' AFTER allocation_idempotency_key')); +PREPARE transfer_allocation_hash_stmt FROM @ddl; EXECUTE transfer_allocation_hash_stmt; DEALLOCATE PREPARE transfer_allocation_hash_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_warehouse_transfer_order' AND column_name='outbound_idempotency_key'), 'SELECT 1', 'ALTER TABLE ll_warehouse_transfer_order ADD COLUMN outbound_idempotency_key VARCHAR(255) NULL COMMENT ''出库幂等键'' AFTER allocation_request_hash')); +PREPARE transfer_outbound_key_stmt FROM @ddl; EXECUTE transfer_outbound_key_stmt; DEALLOCATE PREPARE transfer_outbound_key_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_warehouse_transfer_order' AND column_name='outbound_request_hash'), 'SELECT 1', 'ALTER TABLE ll_warehouse_transfer_order ADD COLUMN outbound_request_hash CHAR(64) NULL COMMENT ''出库请求哈希'' AFTER outbound_idempotency_key')); +PREPARE transfer_outbound_hash_stmt FROM @ddl; EXECUTE transfer_outbound_hash_stmt; DEALLOCATE PREPARE transfer_outbound_hash_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_warehouse_transfer_order' AND column_name='outbound_at'), 'SELECT 1', 'ALTER TABLE ll_warehouse_transfer_order ADD COLUMN outbound_at DATETIME(3) NULL COMMENT ''出库时间'' AFTER outbound_request_hash')); +PREPARE transfer_outbound_at_stmt FROM @ddl; EXECUTE transfer_outbound_at_stmt; DEALLOCATE PREPARE transfer_outbound_at_stmt; + +CREATE TABLE IF NOT EXISTS ll_warehouse_transfer_source_order ( + id BIGINT NOT NULL COMMENT '主键ID', transfer_order_id BIGINT NOT NULL COMMENT '转仓单ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', + source_lastleg_order_id BIGINT NOT NULL COMMENT '来源尾程订单ID', source_lastleg_order_no VARCHAR(32) NOT NULL COMMENT '来源尾程订单号', + inbound_order_id BIGINT NOT NULL COMMENT '入仓单ID', inbound_code VARCHAR(128) NOT NULL COMMENT '入仓单号快照', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', PRIMARY KEY (id), + UNIQUE KEY uk_ll_wh_transfer_source (transfer_order_id,source_lastleg_order_id), + KEY idx_ll_wh_transfer_source_list (customer_id,transfer_order_id) +) ENGINE=InnoDB COMMENT='转仓来源历史订单绑定'; + +CREATE TABLE IF NOT EXISTS ll_warehouse_transfer_item ( + id BIGINT NOT NULL COMMENT '主键ID', transfer_order_id BIGINT NOT NULL COMMENT '转仓单ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', + sku_code VARCHAR(128) NOT NULL COMMENT 'SKU编码', product_name_zh VARCHAR(255) NOT NULL COMMENT '产品中文名称', + package_count INT NOT NULL COMMENT '箱数', shipping_mark VARCHAR(255) NULL COMMENT '运输唛头', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', PRIMARY KEY (id), + KEY idx_ll_wh_transfer_item_list (customer_id,transfer_order_id), KEY idx_ll_wh_transfer_item_sku (customer_id,sku_code) +) ENGINE=InnoDB COMMENT='转仓货物冻结明细'; + +CREATE TABLE IF NOT EXISTS ll_warehouse_transfer_inventory_allocation ( + id BIGINT NOT NULL COMMENT '雪花主键', + transfer_order_id BIGINT NOT NULL COMMENT '转仓单ID', + customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', + inventory_position_id BIGINT NOT NULL COMMENT '来源库存仓位记录ID', + inventory_balance_id BIGINT NOT NULL COMMENT '来源库存余额记录ID', + inbound_order_id BIGINT NOT NULL COMMENT '来源入仓单ID', + inbound_code VARCHAR(128) NOT NULL COMMENT '来源入仓单号快照', + pallet_id BIGINT NOT NULL COMMENT '来源托盘ID', + pallet_no VARCHAR(128) NOT NULL COMMENT '来源托盘号快照', + package_count INT NOT NULL COMMENT '分配箱数', + status VARCHAR(32) NOT NULL DEFAULT 'RESERVED' COMMENT '分配状态', + active_position_key VARCHAR(255) NULL COMMENT '生效中的仓位排他键', + allocated_at DATETIME(3) NOT NULL COMMENT '分配时间', + shipped_at DATETIME(3) NULL COMMENT '出库时间', + received_at DATETIME(3) NULL 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_wh_transfer_allocation_position(transfer_order_id,inventory_position_id), + UNIQUE KEY uk_ll_wh_transfer_active_position(customer_id,active_position_key), + KEY idx_ll_wh_transfer_allocation_list(customer_id,transfer_order_id,status) +) ENGINE=InnoDB COMMENT='转仓单来源库存分配记录'; + +CREATE TABLE IF NOT EXISTS ll_warehouse_transfer_receipt ( + id BIGINT NOT NULL COMMENT '雪花主键', + transfer_order_id BIGINT NOT NULL COMMENT '转仓单ID', + allocation_id BIGINT NOT NULL COMMENT '库存分配记录ID', + customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', + source_inventory_position_id BIGINT NOT NULL COMMENT '来源库存仓位记录ID', + destination_inventory_balance_id BIGINT NOT NULL COMMENT '目标库存余额记录ID', + destination_inventory_position_id BIGINT NOT NULL COMMENT '目标库存仓位记录ID', + package_count INT NOT NULL COMMENT '收货箱数', + idempotency_key VARCHAR(255) NOT NULL COMMENT '收货幂等键', + request_hash CHAR(64) NOT NULL COMMENT '收货请求哈希', + inventory_operation_key VARCHAR(255) NOT NULL COMMENT '库存操作幂等键', + created_by VARCHAR(128) NULL COMMENT '创建人ID', + received_at DATETIME(3) NOT NULL COMMENT '收货时间', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + PRIMARY KEY(id), + UNIQUE KEY uk_ll_wh_transfer_receipt_source(transfer_order_id,source_inventory_position_id), + UNIQUE KEY uk_ll_wh_transfer_receipt_key(customer_id,idempotency_key), + UNIQUE KEY uk_ll_wh_transfer_receipt_operation(customer_id,inventory_operation_key), + KEY idx_ll_wh_transfer_receipt_list(customer_id,transfer_order_id,received_at) +) ENGINE=InnoDB COMMENT='转仓来源仓位的目标库存收货记录'; + +-- Customer relabel application: header, item snapshots, and immutable process history. +CREATE TABLE IF NOT EXISTS ll_relabel_application ( + id BIGINT NOT NULL COMMENT '主键ID', + customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', + relabel_no VARCHAR(64) NOT NULL COMMENT '换标申请单号', + lastleg_order_id BIGINT NOT NULL COMMENT '尾程订单ID', + customer_order_no VARCHAR(128) NOT NULL COMMENT '客户订单号', + overseas_warehouse_id VARCHAR(128) NOT NULL COMMENT '海外仓ID', + label_deadline DATETIME(3) NOT NULL COMMENT '换标截止时间', + contact_name VARCHAR(128) NOT NULL COMMENT '联系人姓名', + contact_phone VARCHAR(64) NOT NULL COMMENT '联系人电话', + remark VARCHAR(1000) NULL COMMENT '备注', + status VARCHAR(32) NOT NULL DEFAULT 'PENDING_PROCESSING' COMMENT '业务状态', + item_count INT NOT NULL DEFAULT 0 COMMENT '明细数量', + total_package_count INT NOT NULL DEFAULT 0 COMMENT '总箱数', + notified_package_count INT NOT NULL DEFAULT 0 COMMENT '已通知箱数', + charge_amount DECIMAL(20,2) NOT NULL DEFAULT 0 COMMENT '换标费用CNY', + charge_currency CHAR(3) NOT NULL DEFAULT 'CNY' COMMENT '费用币种', + wallet_status VARCHAR(32) NOT NULL DEFAULT 'FREEZE_PENDING' COMMENT '资金状态', + wallet_error_code VARCHAR(64) NULL COMMENT '资金错误码', + wallet_error_message VARCHAR(500) NULL COMMENT '资金错误说明', + wallet_command_event_id VARCHAR(64) NULL COMMENT '当前资金指令事件ID', + idempotency_key VARCHAR(255) NOT NULL COMMENT '业务幂等键', + request_hash CHAR(64) NOT NULL COMMENT '请求内容哈希', + created_by VARCHAR(128) NULL COMMENT '创建人ID', + 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_relabel_application_no (customer_id,relabel_no), + UNIQUE KEY uk_ll_relabel_application_idempotency (customer_id,idempotency_key), + UNIQUE KEY uk_ll_relabel_application_order (lastleg_order_id), + KEY idx_ll_relabel_application_list (customer_id,status,created_at), + KEY idx_ll_relabel_application_customer (customer_id,customer_order_no), + KEY idx_ll_relabel_application_warehouse (customer_id,overseas_warehouse_id) +) ENGINE=InnoDB COMMENT='客户换标申请单'; + +CREATE TABLE IF NOT EXISTS ll_relabel_item ( + id BIGINT NOT NULL COMMENT '主键ID', + application_id BIGINT NOT NULL COMMENT '申请单ID', + customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', + sku_code VARCHAR(128) NULL COMMENT 'SKU编码(选填)', + product_image_file_id VARCHAR(128) NULL COMMENT '产品图片文件ID', + product_image_file_name VARCHAR(255) NULL COMMENT '产品图片文件名', + product_image_url VARCHAR(1000) NULL COMMENT '产品图片地址', + product_name_zh VARCHAR(255) NOT NULL COMMENT '产品中文名称', + total_package_count INT NOT NULL COMMENT '总箱数', + notified_package_count INT NOT NULL COMMENT '已通知箱数', + shipment_no VARCHAR(128) NOT NULL COMMENT '货件编号', + tracking_code VARCHAR(255) NULL COMMENT '物流跟踪编码', + new_sku_code VARCHAR(128) NOT NULL COMMENT '换标后SKU编码', + label_requirement VARCHAR(1000) NULL COMMENT '换标要求', + remark VARCHAR(1000) NULL COMMENT '货物明细备注', + relabel_file_id VARCHAR(128) NULL COMMENT '换标文件ID', + relabel_file_name VARCHAR(255) NULL COMMENT '换标文件名', + relabel_file_url VARCHAR(1000) NULL COMMENT '换标文件地址', + carton_mark VARCHAR(255) NULL COMMENT '客户自有箱唛', + fba_carton_mark VARCHAR(255) NULL COMMENT '关联FBA箱唛', + destination_warehouse_id VARCHAR(128) NULL COMMENT '该箱目的海外仓ID', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + PRIMARY KEY (id), + KEY idx_ll_relabel_item_application (customer_id,application_id), + KEY idx_ll_relabel_item_sku (customer_id,sku_code), + KEY idx_ll_relabel_item_destination (customer_id,destination_warehouse_id,application_id) +) ENGINE=InnoDB COMMENT='换标货物冻结明细'; + +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_relabel_item' AND column_name='remark'), 'SELECT 1', 'ALTER TABLE ll_relabel_item ADD COLUMN remark VARCHAR(1000) NULL COMMENT ''货物明细备注'' AFTER label_requirement')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_relabel_item' AND column_name='carton_mark'), 'SELECT 1', 'ALTER TABLE ll_relabel_item ADD COLUMN carton_mark VARCHAR(255) NULL COMMENT ''客户自有箱唛'' AFTER relabel_file_url')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_relabel_item' AND column_name='fba_carton_mark'), 'SELECT 1', 'ALTER TABLE ll_relabel_item ADD COLUMN fba_carton_mark VARCHAR(255) NULL COMMENT ''关联FBA箱唛'' AFTER carton_mark')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_relabel_item' AND column_name='destination_warehouse_id'), 'SELECT 1', 'ALTER TABLE ll_relabel_item ADD COLUMN destination_warehouse_id VARCHAR(128) NULL COMMENT ''该箱目的海外仓ID'' AFTER fba_carton_mark')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_relabel_item' AND index_name='idx_ll_relabel_item_destination'), 'SELECT 1', 'ALTER TABLE ll_relabel_item ADD KEY idx_ll_relabel_item_destination (customer_id,destination_warehouse_id,application_id)')); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt; + +CREATE TABLE IF NOT EXISTS ll_relabel_status_history ( + id BIGINT NOT NULL COMMENT '主键ID', + customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', + application_id BIGINT NOT NULL COMMENT '申请单ID', + from_status VARCHAR(32) NULL COMMENT '变更前状态', + to_status VARCHAR(32) NOT NULL COMMENT '变更后状态', + operator_id VARCHAR(128) NULL COMMENT '操作人ID', + remark VARCHAR(1000) NULL COMMENT '备注', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + PRIMARY KEY (id), + KEY idx_ll_relabel_status_history_application (customer_id,application_id,created_at) +) ENGINE=InnoDB COMMENT='换标状态变更历史'; + +CREATE TABLE IF NOT EXISTS ll_relabel_pda_task ( + id BIGINT NOT NULL COMMENT '换标PDA任务ID', + application_id BIGINT NOT NULL COMMENT '换标申请单ID', + customer_id VARCHAR(128) NOT NULL COMMENT '客户数据隔离键', + relabel_no VARCHAR(64) NOT NULL COMMENT '换标申请单号快照', + overseas_warehouse_id BIGINT NOT NULL COMMENT 'PDA令牌冻结的海外实际作业仓ID', + status VARCHAR(32) NOT NULL COMMENT '任务状态:PROCESSING、WAIT_REVIEW或COMPLETED', + operator_key VARCHAR(128) NOT NULL COMMENT '认领操作账号标识', + operator_name VARCHAR(255) NOT NULL COMMENT '认领操作员名称快照', + reviewer_key VARCHAR(128) NULL COMMENT '复核账号标识', + reviewer_name VARCHAR(255) NULL COMMENT '复核员名称快照', + expected_packages INT NOT NULL COMMENT '申请单通知的应换标箱数', + relabelled_packages INT NOT NULL DEFAULT 0 COMMENT '已正常换标箱数', + skipped_packages INT NOT NULL DEFAULT 0 COMMENT '已说明原因跳过箱数', + exception_packages INT NOT NULL DEFAULT 0 COMMENT '已登记异常箱数', + completion_photo_file_ids JSON NULL COMMENT '换标完成现场照片文件ID列表', + completion_remark VARCHAR(1000) NULL COMMENT '完成提交备注', + review_decision VARCHAR(16) NULL COMMENT '最近复核结论:APPROVE或REWORK', + review_remark VARCHAR(1000) NULL COMMENT '最近复核备注', + claimed_at DATETIME(3) NOT NULL COMMENT '任务认领时间', + submitted_at DATETIME(3) NULL COMMENT '提交复核时间', + reviewed_at DATETIME(3) NULL COMMENT '最近复核时间', + version INT 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_relabel_pda_task_application (customer_id,application_id), + UNIQUE KEY uk_ll_relabel_pda_task_no (relabel_no), + KEY idx_ll_relabel_pda_task_warehouse (overseas_warehouse_id,status,updated_at), + KEY idx_ll_relabel_pda_task_operator (operator_key,status,updated_at) +) ENGINE=InnoDB COMMENT='海外PDA换标现场作业任务'; + +CREATE TABLE IF NOT EXISTS ll_relabel_pda_unit_fact ( + id BIGINT NOT NULL COMMENT '逐件换标事实ID', + task_id BIGINT NOT NULL COMMENT '换标PDA任务ID', + application_id BIGINT NOT NULL COMMENT '换标申请单ID', + item_id BIGINT NOT NULL COMMENT '换标申请明细ID', + customer_id VARCHAR(128) NOT NULL COMMENT '客户数据隔离键', + old_label VARCHAR(255) NULL COMMENT '扫描的实物旧标签或箱标', + new_label VARCHAR(255) NULL COMMENT '扫描的换标后新标签', + result VARCHAR(32) NOT NULL COMMENT '逐件结果:RELABELLED、SKIPPED或EXCEPTION', + exception_code VARCHAR(32) NULL COMMENT '异常编码', + reason VARCHAR(1000) NULL COMMENT '跳过或异常原因', + operator_key VARCHAR(128) NOT NULL COMMENT '操作账号标识', + operator_name VARCHAR(255) NOT NULL COMMENT '操作员名称快照', + occurred_at DATETIME(3) NOT NULL COMMENT '逐件事实发生时间', + PRIMARY KEY (id), + UNIQUE KEY uk_ll_relabel_pda_old_label (old_label), + KEY idx_ll_relabel_pda_unit_task (customer_id,task_id,occurred_at,id), + KEY idx_ll_relabel_pda_unit_item (customer_id,application_id,item_id) +) ENGINE=InnoDB COMMENT='海外PDA逐件换标扫码与异常事实'; + +CREATE TABLE IF NOT EXISTS ll_relabel_pda_operation ( + id BIGINT NOT NULL COMMENT '换标PDA幂等操作ID', + task_id BIGINT NOT NULL COMMENT '换标PDA任务ID', + customer_id VARCHAR(128) NOT NULL COMMENT '客户数据隔离键', + operator_key VARCHAR(128) NOT NULL COMMENT '请求操作账号标识', + action VARCHAR(32) NOT NULL COMMENT '幂等操作动作', + idempotency_key VARCHAR(255) NOT NULL COMMENT '设备生成的幂等键', + request_hash CHAR(64) NOT NULL COMMENT '请求内容哈希', + result_json LONGTEXT NOT NULL COMMENT '成功响应快照JSON', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + PRIMARY KEY (id), + UNIQUE KEY uk_ll_relabel_pda_operation_key (operator_key,idempotency_key), + KEY idx_ll_relabel_pda_operation_task (customer_id,task_id,created_at) +) ENGINE=InnoDB COMMENT='海外PDA换标写操作幂等记录'; + +CREATE TABLE IF NOT EXISTS ll_relabel_pda_history ( + id BIGINT NOT NULL COMMENT '换标PDA任务历史ID', + task_id BIGINT NOT NULL COMMENT '换标PDA任务ID', + application_id BIGINT NOT NULL COMMENT '换标申请单ID', + customer_id VARCHAR(128) NOT NULL COMMENT '客户数据隔离键', + action VARCHAR(32) NOT NULL COMMENT '现场动作类型', + from_status VARCHAR(32) NULL COMMENT '动作前任务状态', + to_status VARCHAR(32) NOT NULL COMMENT '动作后任务状态', + operator_key VARCHAR(128) NOT NULL COMMENT '操作账号标识', + operator_name VARCHAR(255) NOT NULL COMMENT '操作员名称快照', + detail_json LONGTEXT NULL COMMENT '动作详情JSON', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + PRIMARY KEY (id), + KEY idx_ll_relabel_pda_history_task (customer_id,task_id,created_at,id), + KEY idx_ll_relabel_pda_history_application (customer_id,application_id,created_at) +) ENGINE=InnoDB COMMENT='海外PDA换标任务不可变操作历史'; + +CREATE TABLE IF NOT EXISTS ll_dropship_order_profile ( + id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', order_id BIGINT NOT NULL COMMENT '订单ID', + warehouse_pallet_id BIGINT NULL COMMENT '仓库实物托盘ID', pallet_no VARCHAR(128) NULL COMMENT '托盘号', + source_pallet_no VARCHAR(128) NOT NULL COMMENT '来源托盘号快照', + inventory_position_id BIGINT NULL COMMENT '库存仓位记录ID', status VARCHAR(32) NOT NULL DEFAULT 'PENDING_INBOUND' COMMENT '业务状态', + idempotency_key VARCHAR(255) NOT NULL 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 '更新时间', + PRIMARY KEY(id), UNIQUE KEY uk_ll_dropship_profile_order_pallet(customer_id,order_id,source_pallet_no), + UNIQUE KEY uk_ll_dropship_profile_key(customer_id,idempotency_key), KEY idx_ll_dropship_profile_order(customer_id,order_id) +) ENGINE=InnoDB COMMENT='一件代发托盘档案'; + +CREATE TABLE IF NOT EXISTS ll_dropship_carton ( + id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', order_id BIGINT NOT NULL COMMENT '订单ID', profile_id BIGINT NOT NULL COMMENT '托盘档案ID', + warehouse_pallet_id BIGINT NULL COMMENT '仓库实物托盘ID', pallet_no VARCHAR(128) NULL COMMENT '托盘号', sku_code VARCHAR(128) NOT NULL COMMENT 'SKU编码', carton_mark VARCHAR(128) NOT NULL COMMENT '箱唛', + carton_sequence INT NOT NULL COMMENT '箱唛序号', status VARCHAR(32) NOT NULL DEFAULT 'PENDING_INBOUND' COMMENT '业务状态', outbound_order_no VARCHAR(128) NULL 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 '更新时间', + PRIMARY KEY(id), UNIQUE KEY uk_ll_dropship_carton(customer_id,order_id,carton_mark), + KEY idx_ll_dropship_carton_status(customer_id,status), KEY idx_ll_dropship_carton_sku(customer_id,sku_code,status) +) ENGINE=InnoDB COMMENT='一件代发箱唛明细'; + +CREATE TABLE IF NOT EXISTS ll_dropship_sku_inventory ( + id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', order_id BIGINT NOT NULL COMMENT '订单ID', profile_id BIGINT NOT NULL COMMENT '托盘档案ID', + sku_code VARCHAR(128) NOT NULL COMMENT 'SKU编码', product_name VARCHAR(255) NULL COMMENT '产品名称', package_count BIGINT NOT NULL COMMENT '箱数', product_quantity BIGINT NOT NULL 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_dropship_sku_inventory(profile_id,sku_code), + KEY idx_ll_dropship_sku_order(customer_id,order_id,sku_code) +) ENGINE=InnoDB COMMENT='一件代发SKU库存汇总'; + +CREATE TABLE IF NOT EXISTS ll_dropship_carton_ledger ( + id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', order_id BIGINT NOT NULL COMMENT '订单ID', carton_id BIGINT NOT NULL COMMENT '箱唛记录ID', + carton_mark VARCHAR(128) NOT NULL COMMENT '箱唛', sku_code VARCHAR(128) NOT NULL COMMENT 'SKU编码', operation_type VARCHAR(32) NOT NULL COMMENT '操作类型', + before_packages BIGINT NOT NULL COMMENT '变动前箱数', delta_packages BIGINT NOT NULL COMMENT '箱数变动量', after_packages BIGINT NOT NULL COMMENT '变动后箱数', + business_order_no VARCHAR(128) NULL COMMENT '业务单号', idempotency_key VARCHAR(255) NOT NULL COMMENT '业务幂等键', remark VARCHAR(1000) NULL COMMENT '备注', + occurred_at DATETIME(3) NOT NULL COMMENT '业务发生时间', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', PRIMARY KEY(id), + UNIQUE KEY uk_ll_dropship_ledger_key(customer_id,idempotency_key), KEY idx_ll_dropship_ledger_sku(customer_id,sku_code,carton_mark) +) ENGINE=InnoDB COMMENT='一件代发箱唛库存流水'; + +CREATE TABLE IF NOT EXISTS ll_cargo_discard_application ( + id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', + discard_no VARCHAR(64) NOT NULL COMMENT '废弃申请单号', overseas_warehouse_id VARCHAR(128) NOT NULL COMMENT '海外仓ID', + overseas_warehouse_name VARCHAR(255) NULL COMMENT '海外仓名称快照', inbound_codes VARCHAR(2000) NOT NULL COMMENT '入仓单号集合', + total_packages INT NOT NULL COMMENT '总箱数', fee_per_package DECIMAL(18,2) NOT NULL COMMENT '每箱处理费用', + total_fee DECIMAL(18,2) NOT NULL COMMENT '费用合计', currency CHAR(3) NOT NULL DEFAULT 'USD' COMMENT '币种', + status VARCHAR(32) NOT NULL DEFAULT 'PENDING_REVIEW' COMMENT '业务状态', remark VARCHAR(1000) NULL COMMENT '备注', + idempotency_key VARCHAR(255) NOT NULL COMMENT '业务幂等键', request_hash CHAR(64) NOT NULL COMMENT '请求内容哈希', + created_by VARCHAR(128) NULL COMMENT '创建人ID', reviewed_by VARCHAR(128) NULL COMMENT '审核人ID', review_remark VARCHAR(1000) NULL COMMENT '审核备注', + review_idempotency_key VARCHAR(255) NULL COMMENT '审核幂等键', review_request_hash CHAR(64) NULL COMMENT '审核请求内容哈希', + reviewed_at DATETIME(3) NULL 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_cargo_discard_no(customer_id,discard_no), + UNIQUE KEY uk_ll_cargo_discard_key(customer_id,idempotency_key), + KEY idx_ll_cargo_discard_list(customer_id,status,created_at), + KEY idx_ll_cargo_discard_warehouse(customer_id,overseas_warehouse_id,created_at) +) ENGINE=InnoDB COMMENT='货物废弃申请单'; + +CREATE TABLE IF NOT EXISTS ll_cargo_discard_item ( + id BIGINT NOT NULL COMMENT '主键ID', application_id BIGINT NOT NULL COMMENT '申请单ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', + order_id BIGINT NOT NULL COMMENT '订单ID', inbound_code VARCHAR(128) NOT NULL COMMENT '入仓单号快照', profile_id BIGINT NOT NULL COMMENT '托盘档案ID', + sku_inventory_id BIGINT NOT NULL COMMENT 'SKU库存记录ID', sku_code VARCHAR(128) NOT NULL COMMENT 'SKU编码', product_name VARCHAR(255) NULL COMMENT '产品名称', + available_packages_snapshot INT NOT NULL COMMENT '可用箱数快照', discard_packages INT NOT NULL COMMENT '废弃箱数', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', PRIMARY KEY(id), + UNIQUE KEY uk_ll_cargo_discard_item(application_id,profile_id,sku_code), + KEY idx_ll_cargo_discard_item_list(customer_id,application_id), + KEY idx_ll_cargo_discard_item_inbound(customer_id,inbound_code,sku_code) +) ENGINE=InnoDB COMMENT='货物废弃SKU明细'; + +CREATE TABLE IF NOT EXISTS ll_cargo_discard_carton ( + id BIGINT NOT NULL COMMENT '主键ID', application_id BIGINT NOT NULL COMMENT '申请单ID', item_id BIGINT NOT NULL COMMENT '明细ID', + customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', carton_id BIGINT NOT NULL COMMENT '箱唛记录ID', carton_mark VARCHAR(128) NOT NULL COMMENT '箱唛', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', PRIMARY KEY(id), + UNIQUE KEY uk_ll_cargo_discard_carton(application_id,carton_id), + KEY idx_ll_cargo_discard_carton_application(customer_id,application_id,item_id), + KEY idx_ll_cargo_discard_carton_identity(customer_id,carton_id) +) ENGINE=InnoDB COMMENT='货物废弃锁定箱明细'; + +CREATE TABLE IF NOT EXISTS ll_cargo_discard_status_history ( + id BIGINT NOT NULL COMMENT '主键ID', application_id BIGINT NOT NULL COMMENT '申请单ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', + from_status VARCHAR(32) NULL COMMENT '变更前状态', to_status VARCHAR(32) NOT NULL COMMENT '变更后状态', operator_id VARCHAR(128) NULL COMMENT '操作人ID', + remark VARCHAR(1000) NULL COMMENT '备注', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', PRIMARY KEY(id), + KEY idx_ll_cargo_discard_history(customer_id,application_id,created_at) +) ENGINE=InnoDB COMMENT='货物废弃审批历史'; + +CREATE TABLE IF NOT EXISTS ll_overseas_procurement_order ( + id BIGINT NOT NULL COMMENT '主键ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', + customer_name VARCHAR(255) NULL COMMENT '客户名称快照', order_no VARCHAR(64) NOT NULL COMMENT '订单号', + purchase_category VARCHAR(128) NOT NULL COMMENT '采购品类', product_name VARCHAR(255) NOT NULL COMMENT '产品名称', + product_specification VARCHAR(500) NULL COMMENT '产品规格', product_url VARCHAR(2000) NULL COMMENT '产品链接', + purchase_quantity BIGINT NOT NULL COMMENT '采购数量', budget_unit_price DECIMAL(18,2) NULL COMMENT '预算单价', + currency CHAR(3) NOT NULL DEFAULT 'USD' COMMENT '币种', receiving_address VARCHAR(1000) NULL COMMENT '收货地址', + contact_name VARCHAR(128) NOT NULL COMMENT '联系人姓名', contact_phone VARCHAR(64) NOT NULL COMMENT '联系人电话', + purchase_warehouse_id VARCHAR(128) NULL COMMENT '采购入仓仓库ID', purchase_warehouse_name VARCHAR(255) NULL COMMENT '采购入仓仓库名称', outbound_details JSON NULL COMMENT '出库明细', + inbound_quantity BIGINT NOT NULL DEFAULT 0 COMMENT '采购单据累计入库数量', + domestic_carrier VARCHAR(128) NULL COMMENT '境内承运商', domestic_tracking_no VARCHAR(128) NULL COMMENT '境内物流单号', + overseas_carrier VARCHAR(128) NULL COMMENT '海外承运商', overseas_tracking_no VARCHAR(128) NULL COMMENT '海外物流单号', + estimated_arrival_at DATETIME(3) NULL COMMENT '预计到达时间', status VARCHAR(32) NOT NULL DEFAULT 'PENDING_REVIEW' COMMENT '业务状态', + remark VARCHAR(1000) NULL COMMENT '备注', idempotency_key VARCHAR(255) NOT NULL COMMENT '业务幂等键', request_hash CHAR(64) NOT NULL COMMENT '请求内容哈希', + created_by VARCHAR(128) NULL COMMENT '创建人ID', reviewed_by VARCHAR(128) NULL COMMENT '审核人ID', review_remark VARCHAR(1000) NULL COMMENT '审核备注', + review_idempotency_key VARCHAR(255) NULL COMMENT '审核幂等键', review_request_hash CHAR(64) NULL COMMENT '审核请求内容哈希', + reviewed_at DATETIME(3) NULL 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 '更新时间', + PRIMARY KEY(id), UNIQUE KEY uk_ll_overseas_procurement_order_no(customer_id,order_no), + UNIQUE KEY uk_ll_overseas_procurement_order_key(customer_id,idempotency_key), + KEY idx_ll_overseas_procurement_list(customer_id,status,created_at), + KEY idx_ll_overseas_procurement_warehouse(customer_id,purchase_warehouse_id,created_at) +) ENGINE=InnoDB COMMENT='海外采购订单'; + +CREATE TABLE IF NOT EXISTS ll_overseas_procurement_status_history ( + id BIGINT NOT NULL COMMENT '主键ID', order_id BIGINT NOT NULL COMMENT '订单ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', + operation_type VARCHAR(32) NOT NULL COMMENT '操作类型:CREATE、REVIEW或UPDATE', + from_status VARCHAR(32) NULL COMMENT '变更前状态', to_status VARCHAR(32) NOT NULL COMMENT '变更后状态', operator_id VARCHAR(128) NULL COMMENT '操作人ID', + idempotency_key VARCHAR(255) NULL COMMENT '操作幂等键', change_detail JSON NULL COMMENT '字段变更前后值JSON', remark VARCHAR(1000) NULL COMMENT '备注', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + PRIMARY KEY(id), KEY idx_ll_overseas_procurement_history(customer_id,order_id,created_at) +) ENGINE=InnoDB COMMENT='海外采购操作流水'; + +CREATE TABLE IF NOT EXISTS ll_us_truck_order ( + id BIGINT NOT NULL COMMENT '主键ID', + customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', + order_no VARCHAR(64) NOT 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 '目的地址', + container_type VARCHAR(32) NULL COMMENT '历史兼容设备类型,零担托盘业务不采集', + pallet_count INT NOT NULL DEFAULT 1 COMMENT '客户申报托盘数', + package_count INT NULL COMMENT '可选箱数,零或空值均有效', + received_pallet_count INT NOT NULL DEFAULT 0 COMMENT 'PDA实收托盘数', + allocated_pallet_count INT NOT NULL DEFAULT 0 COMMENT '已分配到合车行程的托盘数', + total_weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT '总重量(千克)', + total_volume_cbm DECIMAL(18,6) NOT NULL DEFAULT 0 COMMENT '总体积(立方米)', + budget_amount DECIMAL(18,2) NULL COMMENT '预算金额', + currency VARCHAR(3) NOT NULL DEFAULT 'USD' COMMENT '币种', + contact_name VARCHAR(128) NOT NULL COMMENT '联系人姓名', + contact_info VARCHAR(128) NOT NULL COMMENT '联系人联系方式', + requested_date DATE NOT NULL COMMENT '要求服务日期', + status VARCHAR(32) NOT NULL COMMENT '美国卡车订单状态', + remark VARCHAR(1000) NULL COMMENT '备注', + idempotency_key VARCHAR(255) NOT NULL COMMENT '业务幂等键', + request_hash CHAR(64) NOT NULL COMMENT '请求内容哈希', + created_by VARCHAR(128) NULL COMMENT '创建人ID', + 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) COMMENT '更新时间', + PRIMARY KEY(id), + UNIQUE KEY uk_ll_us_truck_order_no(customer_id,order_no), + UNIQUE KEY uk_ll_us_truck_order_key(customer_id,idempotency_key), + KEY idx_ll_us_truck_order_list(customer_id,status,created_at), + KEY idx_ll_us_truck_order_date(customer_id,requested_date,created_at) +) ENGINE=InnoDB COMMENT='美国卡车运输订单'; + +CREATE TABLE IF NOT EXISTS ll_us_truck_order_pallet ( + id BIGINT NOT NULL COMMENT '托盘记录ID', + order_id BIGINT NOT NULL COMMENT '美国卡车订单ID', + customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', + pallet_no VARCHAR(128) NOT NULL COMMENT '业务托盘号', + sequence_no INT NOT NULL COMMENT '订单内托盘序号', + source VARCHAR(32) NOT NULL COMMENT '托盘来源:订单计划或PDA超收', + status VARCHAR(32) NOT NULL COMMENT '托盘状态:待收、已收或超收待审核', + warehouse_pallet_id BIGINT NULL COMMENT '仓库实物托盘资源ID', + package_count INT NULL COMMENT '托盘箱数', + received_at DATETIME(3) NULL 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_us_truck_order_pallet_no(customer_id,order_id,pallet_no), + UNIQUE KEY uk_ll_us_truck_order_pallet_seq(customer_id,order_id,sequence_no), + KEY idx_ll_us_truck_order_pallet_list(customer_id,order_id,status) +) ENGINE=InnoDB COMMENT='美国卡车订单托盘实收记录'; + +CREATE TABLE IF NOT EXISTS ll_us_truck_order_operation ( + id BIGINT NOT NULL COMMENT '操作记录ID', order_id BIGINT NOT NULL COMMENT '美国卡车订单ID', + customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', operation_type VARCHAR(64) NOT NULL COMMENT '具名操作类型', + idempotency_key VARCHAR(255) NOT NULL COMMENT '操作幂等键', request_hash CHAR(64) NOT NULL COMMENT '操作请求内容哈希', + result_version BIGINT NOT NULL COMMENT '操作完成后的订单版本', created_by VARCHAR(128) NULL COMMENT '操作人ID', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', PRIMARY KEY(id), + UNIQUE KEY uk_ll_us_truck_operation_key(customer_id,order_id,idempotency_key), + KEY idx_ll_us_truck_operation_order(customer_id,order_id,created_at) +) ENGINE=InnoDB COMMENT='美国卡车订单幂等操作账'; + +CREATE TABLE IF NOT EXISTS ll_us_truck_order_status_history ( + id BIGINT NOT NULL COMMENT '主键ID', + order_id BIGINT NOT NULL COMMENT '订单ID', + customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', + operation_type VARCHAR(64) NOT NULL COMMENT '具名操作类型', + from_status VARCHAR(32) NULL COMMENT '变更前状态', + to_status VARCHAR(32) NOT NULL COMMENT '变更后状态', + operator_id VARCHAR(128) NULL COMMENT '操作人ID', + idempotency_key VARCHAR(255) NULL COMMENT '操作幂等键', + remark VARCHAR(1000) NULL COMMENT '备注', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + PRIMARY KEY(id), + KEY idx_ll_us_truck_history(customer_id,order_id,created_at) +) 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', + 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 '目的详细地址、码头或仓号', + container_type VARCHAR(32) NULL COMMENT '柜型', package_count INT NOT NULL DEFAULT 0 COMMENT '货物件数', + total_weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT '总重量(千克)', + total_volume_cbm DECIMAL(18,6) NOT NULL DEFAULT 0 COMMENT '总体积(立方米)', + budget_amount DECIMAL(18,2) NULL COMMENT '预算总价', currency CHAR(3) NOT NULL DEFAULT 'USD' COMMENT '预算币种', + contact_name VARCHAR(128) NULL COMMENT '联系人姓名', contact_info VARCHAR(128) NULL COMMENT '联系人联系方式', + requested_date DATE NULL COMMENT '要求服务日期', status VARCHAR(32) NOT NULL DEFAULT 'PENDING_ACCEPTANCE' COMMENT '本土散板订单状态', + remark VARCHAR(1000) NULL COMMENT '备注', idempotency_key VARCHAR(255) NOT NULL COMMENT '创建幂等键', + request_hash CHAR(64) NOT NULL COMMENT '创建请求内容哈希', created_by VARCHAR(128) NULL COMMENT '创建人ID', + 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) +) ENGINE=InnoDB COMMENT='本土散板订单'; + +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', + operation_type VARCHAR(32) NOT NULL COMMENT '操作类型:CREATE、UPDATE或STATUS', from_status VARCHAR(32) NULL COMMENT '变更前状态', + to_status VARCHAR(32) NOT NULL COMMENT '变更后状态', operator_id VARCHAR(128) NULL COMMENT '操作人ID', + idempotency_key VARCHAR(255) NULL COMMENT '操作幂等键', change_detail JSON NULL COMMENT '字段变更前后值JSON', remark VARCHAR(1000) NULL COMMENT '操作备注', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', PRIMARY KEY(id), + KEY idx_ll_local_looseboard_history(customer_id,order_id,created_at) +) ENGINE=InnoDB COMMENT='本土散板操作流水'; + +CREATE TABLE IF NOT EXISTS ll_local_looseboard_operation ( + id BIGINT NOT NULL COMMENT '操作记录ID', order_id BIGINT NOT NULL COMMENT '本土散板订单ID', + customer_id VARCHAR(128) NOT NULL COMMENT '客户租户ID', operation_type VARCHAR(32) NOT NULL COMMENT '操作类型', + idempotency_key VARCHAR(255) NOT NULL COMMENT '操作幂等键', request_hash CHAR(64) NOT NULL COMMENT '操作请求内容哈希', + result_version BIGINT NOT NULL COMMENT '操作完成后的订单版本', created_by VARCHAR(128) NULL COMMENT '操作人ID', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', PRIMARY KEY(id), + UNIQUE KEY uk_ll_local_looseboard_operation_key(customer_id,order_id,idempotency_key), + KEY idx_ll_local_looseboard_operation_order(customer_id,order_id,created_at) +) ENGINE=InnoDB COMMENT='本土散板订单幂等操作账'; + +CREATE TABLE IF NOT EXISTS ll_exception_order ( + id BIGINT NOT NULL COMMENT '异常订单ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户数据隔离键', exception_no VARCHAR(64) NOT NULL COMMENT '异常订单号', + exception_type VARCHAR(32) NOT NULL COMMENT '异常类型', status VARCHAR(32) NOT NULL DEFAULT 'PENDING' COMMENT '异常处理状态', reason VARCHAR(1000) NOT NULL COMMENT '异常原因', + last_mile_order_id BIGINT NULL COMMENT '关联尾程订单ID', last_mile_order_no VARCHAR(64) NULL COMMENT '关联尾程订单号', inbound_order_id BIGINT NULL COMMENT '关联入仓单ID', inbound_code VARCHAR(128) NULL COMMENT '关联入仓码', + delivery_order_id BIGINT NULL COMMENT '关联派送单ID', delivery_no VARCHAR(64) NULL COMMENT '关联派送单号', outbound_no VARCHAR(64) NULL COMMENT '关联出库单号', + customer_name VARCHAR(255) NULL COMMENT '客户名称快照', overseas_warehouse_id VARCHAR(128) NULL COMMENT '海外仓ID快照', overseas_warehouse_name VARCHAR(255) NULL COMMENT '海外仓名称快照', + sku_code VARCHAR(128) NULL COMMENT '异常货物SKU', package_count INT NULL COMMENT '异常箱数', return_direction VARCHAR(32) NULL COMMENT '退回方向', return_logistics_no VARCHAR(128) NULL COMMENT '退回物流单号', + handling_action VARCHAR(500) NULL COMMENT '异常处理措施', handling_result VARCHAR(2000) NULL COMMENT '异常处理结果', + evidence_file_ids_json JSON NULL COMMENT '异常处理证据文件ID列表', inventory_adjustment_id BIGINT NULL COMMENT '退回库存恢复操作ID', + handler_id VARCHAR(128) NULL COMMENT '处理人ID', handler_name VARCHAR(128) NULL COMMENT '处理人名称', + source_type VARCHAR(32) NOT NULL DEFAULT 'MANUAL' COMMENT '异常来源类型', created_by VARCHAR(128) NULL COMMENT '创建人ID', + exception_at DATETIME(3) NOT NULL COMMENT '异常发生时间', completed_at DATETIME(3) NULL 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 '更新时间', + PRIMARY KEY(id), UNIQUE KEY uk_ll_exception_no(customer_id,exception_no), + UNIQUE KEY uk_ll_exception_delivery_return(customer_id,delivery_order_id,exception_type), + KEY idx_ll_exception_list(customer_id,status,exception_at), KEY idx_ll_exception_tail(customer_id,last_mile_order_no), + KEY idx_ll_exception_inbound(customer_id,inbound_code) +) ENGINE=InnoDB COMMENT='海外仓异常订单'; + +CREATE TABLE IF NOT EXISTS ll_exception_return_item ( + id BIGINT NOT NULL COMMENT '异常退回明细ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户数据隔离键', + exception_order_id BIGINT NOT NULL COMMENT '异常订单ID', outbound_pallet_item_id BIGINT NOT NULL COMMENT '原出库托盘明细ID', + inventory_position_id BIGINT NOT NULL COMMENT '原出库库存位置ID', pallet_no VARCHAR(128) NULL COMMENT '原出库托盘码快照', + sku_code VARCHAR(128) NULL COMMENT '原出库SKU快照', max_return_packages BIGINT NOT NULL COMMENT '原出库可退箱数上限', + approved_return_packages BIGINT NOT NULL COMMENT '批准退回箱数', created_at DATETIME(3) NOT NULL COMMENT '创建时间', + PRIMARY KEY(id), UNIQUE KEY uk_ll_exception_return_source(customer_id,exception_order_id,outbound_pallet_item_id,inventory_position_id), + KEY idx_ll_exception_return_order(customer_id,exception_order_id) +) ENGINE=InnoDB COMMENT='异常退回原出库库存冻结明细'; + +CREATE TABLE IF NOT EXISTS ll_exception_order_command ( + id BIGINT NOT NULL COMMENT '异常处理命令ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户数据隔离键', + exception_order_id BIGINT NOT NULL COMMENT '异常订单ID', action VARCHAR(32) NOT NULL COMMENT '异常处理动作', + idempotency_key VARCHAR(255) NOT NULL COMMENT '幂等键', request_hash VARCHAR(64) NOT NULL COMMENT '请求内容SHA256哈希', + result_json JSON NOT NULL COMMENT '命令结果快照', created_at DATETIME(3) NOT NULL COMMENT '创建时间', + PRIMARY KEY(id), UNIQUE KEY uk_ll_exception_command_key(customer_id,action,idempotency_key), + KEY idx_ll_exception_command_order(customer_id,exception_order_id,created_at) +) ENGINE=InnoDB COMMENT='异常处理命令幂等审计'; + +CREATE TABLE IF NOT EXISTS ll_customer_visit ( + id BIGINT NOT NULL COMMENT '客户回访记录ID', + customer_id VARCHAR(128) NOT NULL COMMENT '客户数据隔离键', + delivery_order_id BIGINT NOT NULL COMMENT '关联派送单ID', + delivery_no VARCHAR(64) NOT NULL COMMENT '关联派送单号', + customer_name VARCHAR(255) NULL COMMENT '客户名称快照', + contact_name VARCHAR(128) NULL COMMENT '客户联系人姓名快照', + contact_phone VARCHAR(64) NULL COMMENT '客户联系人电话快照', + destination VARCHAR(500) NULL COMMENT '派送目的仓编码汇总快照', + delivery_completed_at DATETIME(3) NOT NULL COMMENT '派送完成时间', + status VARCHAR(32) NOT NULL DEFAULT 'PENDING' COMMENT '回访状态:PENDING待回访或COMPLETED已回访', + selected_last_mile_order_id BIGINT NULL COMMENT '本次回访选择的尾程订单ID', + selected_last_mile_order_no VARCHAR(64) NULL COMMENT '本次回访选择的尾程订单号', + selected_inbound_order_id BIGINT NULL COMMENT '本次回访选择的入仓单ID', + selected_inbound_code VARCHAR(128) NULL COMMENT '本次回访选择的入仓码', + visit_result VARCHAR(32) NULL COMMENT '最近一次回访结果', + customer_feedback VARCHAR(2000) NULL COMMENT '最近一次客户反馈', + remark VARCHAR(1000) NULL COMMENT '最近一次回访备注', + attachment_json JSON NULL COMMENT '当前回访资料文件元数据列表', + visited_by VARCHAR(128) NULL COMMENT '最近一次回访操作人ID', + visited_at DATETIME(3) NULL COMMENT '最近一次回访时间', + visit_count INT 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_customer_visit_delivery(customer_id,delivery_order_id), + KEY idx_ll_customer_visit_list(customer_id,status,delivery_completed_at), + KEY idx_ll_customer_visit_delivery_no(customer_id,delivery_no) +) ENGINE=InnoDB COMMENT='派送单维度客户回访记录'; +CREATE TABLE IF NOT EXISTS ll_security_event_inbox ( + event_id VARCHAR(64) NOT NULL COMMENT '跨区安全事件ID', + event_type VARCHAR(96) NOT NULL COMMENT 'IAM安全事件类型', + payload_hash CHAR(64) NOT NULL COMMENT '规范化载荷SHA-256哈希', + payload_json JSON NOT NULL COMMENT '冻结事件载荷JSON', + status VARCHAR(16) NOT NULL COMMENT '消费状态:RECEIVED或PROCESSED', + result VARCHAR(32) NULL COMMENT '处理结果:APPLIED、STALE、DUPLICATE或DUPLICATE_VERSION', + received_at DATETIME(3) NOT NULL COMMENT '接收时间', + processed_at DATETIME(3) NULL COMMENT '处理完成时间', + PRIMARY KEY (event_id), + KEY idx_security_inbox_type_time (event_type,received_at) +) ENGINE=InnoDB COMMENT='海外IAM安全事件消费者收件箱'; + +CREATE TABLE IF NOT EXISTS ll_security_projection ( + aggregate_type VARCHAR(32) NOT NULL COMMENT '聚合类型:USER、TENANT或AUTHORIZATION', + aggregate_id VARCHAR(64) NOT NULL COMMENT '用户或客户聚合ID', + tenant_id BIGINT NOT NULL COMMENT '平台0或客户ID', + security_status VARCHAR(24) NULL COMMENT '当前安全状态:ACTIVE或DISABLED', + permission_version BIGINT NULL COMMENT '当前单调权限版本', + roles_json JSON NOT NULL COMMENT 'IAM投影的当前角色快照', + authorities_json JSON NOT NULL COMMENT 'IAM投影的当前海外权限快照', + aggregate_version BIGINT NOT NULL COMMENT '事件聚合单调版本', + payload_hash CHAR(64) NOT NULL COMMENT '当前版本载荷哈希', + last_event_id VARCHAR(64) NOT NULL COMMENT '最后应用的事件ID', + changed_at DATETIME(3) NOT NULL COMMENT '国内IAM事实发生时间', + updated_at DATETIME(3) NOT NULL COMMENT '投影更新时间', + PRIMARY KEY (aggregate_type,aggregate_id), + KEY idx_security_projection_tenant (tenant_id), + KEY idx_security_projection_permission (aggregate_id,permission_version) +) ENGINE=InnoDB COMMENT='海外IAM安全状态与权限版本投影'; + +-- 空库基线固定管理员必须携带当前海外授权快照;后续真实 IAM 事件从 aggregate_version=1 单调覆盖该 version=0 投影。 +SET @platform_admin_overseas_roles = JSON_ARRAY('PLATFORM_ADMIN'); +SET @platform_admin_overseas_authorities = JSON_ARRAY( + 'platform:finance:bill:adjust', + 'platform:finance:bill:update', + 'platform:finance:bill:void', + 'platform:finance:cost:create', + 'platform:finance:cost:view', + 'platform:finance:customer-account:adjust', + 'platform:finance:customer-account:export', + 'platform:finance:customer-account:list', + 'platform:finance:customer-arap:export', + 'platform:finance:customer-arap:list', + 'platform:finance:customer-statement:export', + 'platform:finance:customer-statement:list', + 'platform:finance:customer-terms:update', + 'platform:finance:customer-terms:view', + 'platform:finance:fee-source:edit', + 'platform:finance:fee-source:review', + 'platform:finance:payment:create', + 'platform:finance:receipt:create', + 'platform:finance:receipt:view', + 'platform:finance:receivable-statistics:export', + 'platform:finance:receivable-statistics:list', + 'platform:finance:recharge:review', + 'platform:finance:reconciliation:adjust', + 'platform:finance:reconciliation:create', + 'platform:finance:reconciliation:issue', + 'platform:finance:reconciliation:review', + 'platform:finance:reconciliation:void', + 'platform:finance:settlement-ap:create', + 'platform:finance:settlement-ap:export', + 'platform:finance:settlement-ap:list', + 'platform:finance:settlement-ar:create', + 'platform:finance:settlement-ar:export', + 'platform:finance:settlement-ar:list', + 'platform:finance:settlement:void', + 'platform:overseas-business:looseboard:create', + 'platform:overseas-business:looseboard:edit', + 'platform:overseas-business:looseboard:list', + 'platform:overseas-business:looseboard:status', + 'platform:overseas-business:purchase:edit', + 'platform:overseas-business:purchase:list', + 'platform:overseas-business:purchase:review', + 'platform:overseas-business:truck:create', + 'platform:overseas-business:truck:edit', + 'platform:overseas-business:truck:list', + 'platform:overseas-business:truck:status', + 'platform:overseas-master-data:exchange-rate:create', + 'platform:overseas-master-data:exchange-rate:edit', + 'platform:overseas-master-data:exchange-rate:export', + 'platform:overseas-master-data:exchange-rate:list', + 'platform:overseas-master-data:exchange-rate:status', + 'platform:overseas-master-data:quote:create', + 'platform:overseas-master-data:quote:edit', + 'platform:overseas-master-data:quote:export', + 'platform:overseas-master-data:quote:list', + 'platform:overseas-master-data:quote:status', + 'platform:overseas-master-data:vehicle:create', + 'platform:overseas-master-data:vehicle:edit', + 'platform:overseas-master-data:vehicle:export', + 'platform:overseas-master-data:vehicle:list', + 'platform:overseas-master-data:vehicle:status', + 'platform:overseas-warehouse:carton:create', + 'platform:overseas-warehouse:carton:edit', + 'platform:overseas-warehouse:carton:list', + 'platform:overseas-warehouse:customer-visit:edit', + 'platform:overseas-warehouse:customer-visit:list', + 'platform:overseas-warehouse:delivery:documents', + 'platform:overseas-warehouse:delivery:list', + 'platform:overseas-warehouse:delivery:return', + 'platform:overseas-warehouse:delivery:status', + 'platform:overseas-warehouse:dispatch:cancel', + 'platform:overseas-warehouse:dispatch:edit', + 'platform:overseas-warehouse:dispatch:list', + 'platform:overseas-warehouse:dispatch:transfer-delivery', + 'platform:overseas-warehouse:exception-order:create', + 'platform:overseas-warehouse:exception-order:list', + 'platform:overseas-warehouse:exception-order:process', + 'platform:overseas-warehouse:inbound-order:create', + 'platform:overseas-warehouse:inbound-order:edit', + 'platform:overseas-warehouse:inbound-order:fba-appointment', + 'platform:overseas-warehouse:inbound-order:list', + 'platform:overseas-warehouse:inbound-order:smart-dispatch', + 'platform:overseas-warehouse:inventory:adjust', + 'platform:overseas-warehouse:inventory:edit', + 'platform:overseas-warehouse:inventory:export', + 'platform:overseas-warehouse:inventory:list', + 'platform:overseas-warehouse:last-mile-order:edit', + 'platform:overseas-warehouse:last-mile-order:export', + 'platform:overseas-warehouse:last-mile-order:list', + 'platform:overseas-warehouse:location:create', + 'platform:overseas-warehouse:location:delete', + 'platform:overseas-warehouse:location:edit', + 'platform:overseas-warehouse:location:list', + 'platform:overseas-warehouse:outbound:cancel', + 'platform:overseas-warehouse:outbound:create', + 'platform:overseas-warehouse:outbound:list', + 'platform:overseas-warehouse:pallet:create', + 'platform:overseas-warehouse:pallet:delete', + 'platform:overseas-warehouse:pallet:edit', + 'platform:overseas-warehouse:pallet:list', + 'platform:overseas-warehouse:product:create', + 'platform:overseas-warehouse:product:edit', + 'platform:overseas-warehouse:product:list', + 'platform:overseas-warehouse:relabel-order:create', + 'platform:overseas-warehouse:relabel-order:list', + 'platform:overseas-warehouse:relabel-order:status', + 'platform:overseas-warehouse:warehouse:create', + 'platform:overseas-warehouse:warehouse:edit', + 'platform:overseas-warehouse:warehouse:list', + 'platform:overseas-warehouse:warehouse:status' +); +INSERT IGNORE INTO ll_security_projection ( + aggregate_type,aggregate_id,tenant_id,security_status,permission_version,roles_json,authorities_json,aggregate_version, + payload_hash,last_event_id,changed_at,updated_at +) VALUES + ('USER','1000001',0,'ACTIVE',0,JSON_ARRAY(),JSON_ARRAY(),0,SHA2('lastleg-baseline:platform_admin:USER:0',256), + 'baseline-platform-admin-user-v0','1970-01-01 00:00:00.000','1970-01-01 00:00:00.000'), + ('AUTHORIZATION','1000001',0,NULL,0,@platform_admin_overseas_roles,@platform_admin_overseas_authorities,0, + SHA2('lastleg-baseline:platform_admin:AUTHORIZATION:0:current-authorities',256), + 'baseline-platform-admin-authorization-v0','1970-01-01 00:00:00.000','1970-01-01 00:00:00.000'); + +UPDATE ll_security_projection +SET roles_json=@platform_admin_overseas_roles, + authorities_json=@platform_admin_overseas_authorities, + payload_hash=SHA2('lastleg-baseline:platform_admin:AUTHORIZATION:0:current-authorities',256), + updated_at=CURRENT_TIMESTAMP(3) +WHERE aggregate_type='AUTHORIZATION' AND aggregate_id='1000001' AND tenant_id=0 + AND aggregate_version=0 AND last_event_id='baseline-platform-admin-authorization-v0'; + + +CREATE TABLE IF NOT EXISTS ll_wallet_event_inbox ( + event_id VARCHAR(64) NOT NULL COMMENT '国内Finance资金结果事件ID', + command_event_id VARCHAR(64) NOT NULL COMMENT '原海外资金指令事件ID', + payload_hash VARCHAR(71) NOT NULL COMMENT '规范化载荷SHA-256,包含sha256:前缀', + payload_json JSON NOT NULL COMMENT '冻结事件信封', + status VARCHAR(16) NOT NULL COMMENT 'RECEIVED或PROCESSED', + received_at DATETIME(3) NOT NULL COMMENT '接收时间', + processed_at DATETIME(3) NULL COMMENT '处理完成时间', + PRIMARY KEY (event_id), + UNIQUE KEY uk_ll_wallet_command_result (command_event_id), + KEY idx_ll_wallet_event_status (status,received_at) +) ENGINE=InnoDB COMMENT='跨区客户资金结果幂等收件箱'; + + + +CREATE TABLE IF NOT EXISTS ll_procurement_daily_sequence ( + customer_id VARCHAR(128) NOT NULL COMMENT '客户ID', + business_date DATE NOT NULL COMMENT '北京时间业务日期', + sequence_value INT NOT NULL COMMENT '客户当日已分配最大序号', + PRIMARY KEY (customer_id,business_date), + CONSTRAINT chk_ll_procurement_sequence CHECK (sequence_value BETWEEN 1 AND 9999) +) ENGINE=InnoDB COMMENT='海外采购客户日序列'; + + + +CREATE TABLE IF NOT EXISTS ll_overseas_procurement_file ( + id BIGINT NOT NULL COMMENT '主键ID', order_id BIGINT NOT NULL COMMENT '采购订单ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户ID', + category VARCHAR(16) NOT NULL COMMENT 'RECEIPT签收文件或VOUCHER费用凭证', file_id BIGINT NOT NULL COMMENT 'File服务文件ID', + file_name VARCHAR(255) NOT NULL COMMENT '文件名快照', content_type VARCHAR(128) NULL COMMENT '媒体类型', size_bytes BIGINT NOT NULL DEFAULT 0 COMMENT '文件大小', + created_by VARCHAR(128) NULL COMMENT '上传人', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '上传时间', + PRIMARY KEY(id), UNIQUE KEY uk_ll_procurement_file(order_id,file_id), KEY idx_ll_procurement_file_order(customer_id,order_id,category,created_at), + CONSTRAINT chk_ll_procurement_file_category CHECK(category IN ('RECEIPT','VOUCHER')) +) ENGINE=InnoDB COMMENT='海外采购签收文件及费用凭证'; + +CREATE TABLE IF NOT EXISTS ll_overseas_procurement_fee ( + id BIGINT NOT NULL COMMENT '主键ID', order_id BIGINT NOT NULL COMMENT '采购订单ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户ID', + fee_no VARCHAR(64) NOT NULL COMMENT '费用业务号', fee_item_code VARCHAR(32) NOT NULL COMMENT '费用项编码', fee_item_name VARCHAR(128) NOT NULL COMMENT '费用项名称', + amount DECIMAL(20,2) NOT NULL COMMENT 'CNY金额', currency CHAR(3) NOT NULL DEFAULT 'CNY' COMMENT '费用币种', voucher_file_id BIGINT NOT NULL COMMENT '凭证文件ID', + voucher_file_name VARCHAR(255) NOT NULL COMMENT '凭证文件名', remark VARCHAR(500) NULL COMMENT '说明', + wallet_status VARCHAR(32) NOT NULL COMMENT '资金状态', wallet_error_code VARCHAR(64) NULL COMMENT '资金错误码', wallet_error_message VARCHAR(500) NULL COMMENT '资金错误说明', + wallet_command_event_id VARCHAR(64) NULL COMMENT '当前资金指令事件ID', created_by VARCHAR(128) NULL 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_procurement_fee_no(fee_no), KEY idx_ll_procurement_fee_order(customer_id,order_id,created_at), + CONSTRAINT chk_ll_procurement_fee_amount CHECK(amount>0), CONSTRAINT chk_ll_procurement_fee_currency CHECK(currency='CNY') +) ENGINE=InnoDB COMMENT='海外采购费用明细及资金生命周期'; + + + +CREATE TABLE IF NOT EXISTS ll_destuff_pallet_cost ( + id BIGINT NOT NULL COMMENT '主键ID', source_container_booking_id VARCHAR(128) NOT NULL COMMENT '来源柜次业务ID', container_no VARCHAR(64) NOT NULL COMMENT '集装箱号', warehouse_id BIGINT NULL COMMENT '海外仓ID', + pallet_count INT NOT NULL COMMENT '实际打托数', total_actual_volume_cbm DECIMAL(20,6) NOT NULL COMMENT '客户实收总体积', total_cost DECIMAL(20,2) NOT NULL COMMENT '打托总成本', currency CHAR(3) NOT NULL DEFAULT 'CNY' COMMENT '成本币种', + status VARCHAR(16) NOT NULL COMMENT '生成状态', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', PRIMARY KEY(id), + UNIQUE KEY uk_ll_destuff_cost_booking(source_container_booking_id), CONSTRAINT chk_ll_destuff_cost CHECK(pallet_count>0 AND total_actual_volume_cbm>0 AND total_cost>0 AND currency='CNY') +) ENGINE=InnoDB COMMENT='拆柜打托实际成本汇总'; +CREATE TABLE IF NOT EXISTS ll_destuff_pallet_cost_allocation ( + id BIGINT NOT NULL COMMENT '主键ID', cost_id BIGINT NOT NULL COMMENT '打托成本汇总ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户ID', actual_volume_cbm DECIMAL(20,6) NOT NULL COMMENT '客户实收体积', + allocated_amount DECIMAL(20,2) NOT NULL COMMENT '客户分摊金额', fee_source_id BIGINT NOT NULL COMMENT '海外费用来源ID', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + PRIMARY KEY(id), UNIQUE KEY uk_ll_destuff_customer(cost_id,customer_id), UNIQUE KEY uk_ll_destuff_fee_source(fee_source_id), + CONSTRAINT chk_ll_destuff_allocation CHECK(actual_volume_cbm>0 AND allocated_amount>=0) +) ENGINE=InnoDB COMMENT='拆柜打托成本按客户实收体积分摊'; + +-- END INLINE EMPTY-DATABASE BASELINE: LASTLEG + +-- BEGIN INLINE EMPTY-DATABASE BASELINE: OVERSEAS WAREHOUSE +CREATE DATABASE IF NOT EXISTS baiyue_lastlag_warehouse + DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +USE baiyue_lastlag_warehouse; +-- This script is run through warehouse-overseas-service's datasource only. +-- The datasource guard requires the selected schema to be baiyue_lastlag_warehouse; +-- database creation and USE are intentionally forbidden here so a miswired +-- service account cannot create or switch schemas during startup. + +SET NAMES utf8mb4; + +CREATE TABLE IF NOT EXISTS wh_location ( + id BIGINT UNSIGNED NOT NULL COMMENT '库位ID(雪花ID)', + customer_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', + warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '所属仓库ID', + zone_code VARCHAR(50) NOT NULL COMMENT '仓库分区编码', + location_code VARCHAR(64) NOT NULL COMMENT '仓库内唯一库位编码', + location_type VARCHAR(32) NOT NULL COMMENT '库位类型', + max_load_kg DECIMAL(18,3) NOT NULL COMMENT '最大承重(千克)', + status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE' COMMENT '库位状态:ACTIVE启用、INACTIVE停用', + remark VARCHAR(500) DEFAULT NULL COMMENT '备注', + created_by BIGINT UNSIGNED DEFAULT NULL COMMENT '创建人ID', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + updated_by BIGINT UNSIGNED DEFAULT NULL COMMENT '最后修改人ID', + updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间', + deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记', + version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', + PRIMARY KEY (id), + UNIQUE KEY uk_location_code (customer_id, warehouse_id, location_code, deleted), + KEY idx_location_query (customer_id, warehouse_id, zone_code, status, deleted) +) ENGINE=InnoDB COMMENT='仓库库位表:按租户和仓库维护库位及承重信息'; + +CREATE TABLE IF NOT EXISTS wh_pallet ( + id BIGINT UNSIGNED NOT NULL COMMENT '托盘ID(雪花ID)', + customer_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', + warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '所属仓库ID', + location_id BIGINT UNSIGNED DEFAULT NULL COMMENT '同仓库内绑定的库位ID,可为空', + pallet_code VARCHAR(128) NOT NULL COMMENT '仓库内唯一托盘编码', + pallet_type VARCHAR(32) NOT NULL COMMENT '托盘类型', + capacity_cbm DECIMAL(18,4) NOT NULL DEFAULT 2.0000 COMMENT '托盘容量(立方米)', + unit_price DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '托盘单价', + status VARCHAR(20) NOT NULL DEFAULT 'IDLE' COMMENT '托盘状态:IDLE闲置、OCCUPIED占用、SCRAPPED废弃', + remark VARCHAR(500) DEFAULT NULL COMMENT '备注', + fba_warehouse_code VARCHAR(128) DEFAULT NULL COMMENT '所属FBA仓编码', + created_by BIGINT UNSIGNED DEFAULT NULL COMMENT '创建人ID', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + updated_by BIGINT UNSIGNED DEFAULT NULL COMMENT '最后修改人ID', + updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间', + deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记', + version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', + PRIMARY KEY (id), + UNIQUE KEY uk_pallet_code (customer_id, warehouse_id, pallet_code, deleted), + KEY idx_pallet_query (customer_id, warehouse_id, status, deleted), + KEY idx_pallet_location (customer_id, warehouse_id, location_id, deleted) +) ENGINE=InnoDB COMMENT='仓库托盘表:按租户和仓库维护托盘、容量、单价及状态'; + +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() + AND table_name='wh_pallet' AND column_name='fba_warehouse_code'), + 'SELECT 1', + 'ALTER TABLE wh_pallet ADD COLUMN fba_warehouse_code VARCHAR(128) NULL COMMENT ''所属FBA仓编码'' AFTER remark')); +PREPARE pallet_fba_warehouse_stmt FROM @ddl; +EXECUTE pallet_fba_warehouse_stmt; +DEALLOCATE PREPARE pallet_fba_warehouse_stmt; + +CREATE TABLE IF NOT EXISTS wh_resource_lock ( + id BIGINT UNSIGNED NOT NULL COMMENT '主键ID', + customer_id BIGINT UNSIGNED NOT NULL COMMENT '客户租户ID', + warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '仓库ID', + pallet_id BIGINT UNSIGNED NOT NULL COMMENT '托盘ID', + location_id BIGINT UNSIGNED NOT NULL COMMENT '库位ID', + business_type VARCHAR(50) NOT NULL COMMENT '业务类型', + business_id VARCHAR(100) NOT NULL COMMENT '业务记录ID', + reservation_key VARCHAR(160) NOT NULL COMMENT '资源预留幂等键', + status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE' COMMENT '资源锁状态', + locked_at DATETIME(3) NOT NULL COMMENT '锁定时间', + released_at DATETIME(3) DEFAULT NULL 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_resource_lock_reservation (customer_id, reservation_key), + KEY idx_resource_lock_pallet (customer_id, warehouse_id, pallet_id, status), + KEY idx_resource_lock_location (customer_id, warehouse_id, location_id, status), + KEY idx_resource_lock_business (customer_id, business_type, business_id) +) ENGINE=InnoDB COMMENT='仓储资源锁定记录'; + +-- warehouse_id references the domestic warehouse-service authority. This overseas database does +-- not duplicate warehouse master, address or capability tables and has no cross-database FK. + +-- 修复既有数据库中因历史脚本编码错误或英文初始化产生的表与字段注释。 +ALTER TABLE wh_location COMMENT='仓库库位表:按租户和仓库维护库位及承重信息'; +ALTER TABLE wh_location + MODIFY COLUMN id BIGINT UNSIGNED NOT NULL COMMENT '库位ID(雪花ID)', + MODIFY COLUMN customer_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', + MODIFY COLUMN warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '所属仓库ID', + MODIFY COLUMN zone_code VARCHAR(50) NOT NULL COMMENT '仓库分区编码', + MODIFY COLUMN location_code VARCHAR(64) NOT NULL COMMENT '仓库内唯一库位编码', + MODIFY COLUMN location_type VARCHAR(32) NOT NULL COMMENT '库位类型', + MODIFY COLUMN max_load_kg DECIMAL(18,3) NOT NULL COMMENT '最大承重(千克)', + MODIFY COLUMN status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE' COMMENT '库位状态:ACTIVE启用、INACTIVE停用', + MODIFY COLUMN remark VARCHAR(500) NULL COMMENT '备注', + MODIFY COLUMN created_by BIGINT UNSIGNED NULL COMMENT '创建人ID', + MODIFY COLUMN created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + MODIFY COLUMN updated_by BIGINT UNSIGNED NULL COMMENT '最后修改人ID', + MODIFY COLUMN updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间', + MODIFY COLUMN deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记', + MODIFY COLUMN version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号'; + +CREATE TABLE IF NOT EXISTS wh_pallet_business_binding ( + id BIGINT NOT NULL COMMENT '主键ID', customer_id BIGINT NOT NULL COMMENT '客户租户ID', warehouse_id BIGINT NOT NULL COMMENT '仓库ID', + business_type VARCHAR(32) NOT NULL COMMENT '业务类型', business_id VARCHAR(128) NOT NULL COMMENT '绑定业务记录ID', + pallet_id BIGINT NOT NULL COMMENT '托盘ID', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + PRIMARY KEY (id), UNIQUE KEY uk_wh_pallet_binding (customer_id,warehouse_id,business_type,business_id), + UNIQUE KEY uk_wh_pallet_binding_pallet (pallet_id) +) ENGINE=InnoDB COMMENT='托盘业务绑定关系'; + +CREATE TABLE IF NOT EXISTS wh_pallet_operation ( + id BIGINT NOT NULL COMMENT '托盘状态操作ID', operation_key VARCHAR(128) NOT NULL COMMENT '跨服务幂等操作键', + request_hash VARCHAR(128) NOT NULL COMMENT '状态变更请求摘要', warehouse_id BIGINT NOT NULL COMMENT '实际海外仓ID', + pallet_id BIGINT NOT NULL COMMENT '实物托盘ID', pallet_code VARCHAR(128) NOT NULL COMMENT '实物托盘码快照', + before_status VARCHAR(20) NOT NULL COMMENT '变更前托盘状态', after_status VARCHAR(20) NOT NULL COMMENT '变更后托盘状态', + before_location_id BIGINT NOT NULL COMMENT '变更前库位ID', after_location_id BIGINT NOT NULL COMMENT '变更后库位ID', + before_version INT NOT NULL COMMENT '变更前托盘版本', after_version INT NOT NULL COMMENT '变更后托盘版本', + operation_status VARCHAR(32) NOT NULL COMMENT '操作状态:APPLIED已应用、COMPENSATED已补偿', + reason VARCHAR(500) NULL 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_wh_pallet_operation_key (operation_key), + KEY idx_wh_pallet_operation_pallet (warehouse_id,pallet_id,created_at) +) ENGINE=InnoDB COMMENT='实物托盘状态幂等变更与补偿记录'; + +CREATE TABLE IF NOT EXISTS wh_pallet_batch ( + id BIGINT NOT NULL COMMENT '批量托盘预留ID', + warehouse_id BIGINT NOT NULL COMMENT '实际海外仓ID', + batch_key VARCHAR(160) NOT NULL COMMENT '柜级入库计划批次稳定业务键', + request_hash CHAR(64) NOT NULL COMMENT '规范化批量请求SHA-256摘要', + status VARCHAR(32) NOT NULL COMMENT '批次状态:RESERVED、CONFIRMED、COMPENSATED', + attempt_no INT NOT NULL DEFAULT 1 COMMENT '同批次重新预留尝试次数', + confirm_command_key VARCHAR(160) NULL COMMENT '确认命令幂等键', + confirm_command_hash CHAR(64) NULL COMMENT '确认命令规范化SHA-256摘要', + compensate_command_key VARCHAR(160) NULL COMMENT '补偿命令幂等键', + compensate_command_hash CHAR(64) NULL COMMENT '补偿命令规范化SHA-256摘要', + compensate_reason VARCHAR(500) NULL COMMENT '补偿原因', + confirmed_at DATETIME(3) NULL COMMENT '确认绑定时间', + compensated_at DATETIME(3) NULL 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_wh_pallet_batch_key (warehouse_id,batch_key), + KEY idx_wh_pallet_batch_status (warehouse_id,status,updated_at) +) ENGINE=InnoDB COMMENT='柜级入库计划批量托盘预留与确认记录'; + +CREATE TABLE IF NOT EXISTS wh_pallet_batch_item ( + id BIGINT NOT NULL COMMENT '批量托盘明细ID', + batch_id BIGINT NOT NULL COMMENT '所属批量托盘预留ID', + customer_id BIGINT NOT NULL COMMENT '实际占用客户ID', + warehouse_id BIGINT NOT NULL COMMENT '实际海外仓ID', + business_key VARCHAR(100) NOT NULL COMMENT '柜号入库计划序号组成的稳定业务键', + inbound_plan_no VARCHAR(128) NOT NULL COMMENT '入库计划号快照', + sequence_no INT NOT NULL COMMENT '计划内托盘序号', + pallet_id BIGINT NOT NULL COMMENT '公共实物托盘ID', + pallet_code VARCHAR(64) NOT NULL COMMENT '资源服务生成的规范托盘码快照', + location_id BIGINT NOT NULL COMMENT '公共托盘当前预留库位ID', + resource_lock_id BIGINT NOT NULL COMMENT '按客户隔离的资源锁ID', + status VARCHAR(32) NOT NULL COMMENT '明细状态:RESERVED、CONFIRMED、RELEASED', + 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_wh_pallet_batch_item_business (customer_id,warehouse_id,business_key), + UNIQUE KEY uk_wh_pallet_batch_item_sequence (batch_id,sequence_no), + UNIQUE KEY uk_wh_pallet_batch_item_pallet (pallet_id), + KEY idx_wh_pallet_batch_item_batch (batch_id,status) +) ENGINE=InnoDB COMMENT='柜级入库计划批量托盘明细与资源锁关联'; + +ALTER TABLE wh_pallet COMMENT='仓库托盘表:按租户和仓库维护托盘、容量、单价及状态'; +ALTER TABLE wh_pallet + MODIFY COLUMN id BIGINT UNSIGNED NOT NULL COMMENT '托盘ID(雪花ID)', + MODIFY COLUMN customer_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', + MODIFY COLUMN warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '所属仓库ID', + MODIFY COLUMN location_id BIGINT UNSIGNED NULL COMMENT '同仓库内绑定的库位ID,可为空', + MODIFY COLUMN pallet_code VARCHAR(128) NOT NULL COMMENT '仓库内唯一托盘编码', + MODIFY COLUMN pallet_type VARCHAR(32) NOT NULL COMMENT '托盘类型', + MODIFY COLUMN capacity_cbm DECIMAL(18,4) NOT NULL DEFAULT 2.0000 COMMENT '托盘容量(立方米)', + MODIFY COLUMN unit_price DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '托盘单价', + MODIFY COLUMN status VARCHAR(20) NOT NULL DEFAULT 'IDLE' COMMENT '托盘状态:IDLE闲置、OCCUPIED占用、SCRAPPED废弃', + MODIFY COLUMN remark VARCHAR(500) NULL COMMENT '备注', + MODIFY COLUMN fba_warehouse_code VARCHAR(128) NULL COMMENT '所属FBA仓编码', + MODIFY COLUMN created_by BIGINT UNSIGNED NULL COMMENT '创建人ID', + MODIFY COLUMN created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + MODIFY COLUMN updated_by BIGINT UNSIGNED NULL COMMENT '最后修改人ID', + MODIFY COLUMN updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间', + MODIFY COLUMN deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记', + MODIFY COLUMN version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号'; + +-- END INLINE EMPTY-DATABASE BASELINE: OVERSEAS WAREHOUSE + +-- BEGIN UNIFIED OVERSEAS INCREMENTAL AND FINAL ASSERTIONS CREATE DATABASE IF NOT EXISTS baiyue_event_bridge_overseas DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE baiyue_event_bridge_overseas; @@ -623,7 +3074,7 @@ CALL assert_pending_orders_do_not_use_source_warehouse(); DROP PROCEDURE assert_pending_orders_do_not_use_source_warehouse; CREATE TABLE IF NOT EXISTS ll_receiving_container_session ( - id BIGINT NOT NULL COMMENT '柜号签收会话ID', customer_id VARCHAR(128) NOT NULL COMMENT '所选柜解析出的客户标识', + id BIGINT NOT NULL COMMENT '柜号签收会话ID', source_container_booking_id VARCHAR(128) NOT NULL COMMENT '稳定来源柜预订ID', container_fact_hash VARCHAR(64) NOT NULL COMMENT '领取时整柜订单与计划事实摘要', container_no VARCHAR(64) NOT NULL COMMENT '集装箱号', overseas_warehouse_id BIGINT NOT NULL COMMENT '操作员Token绑定并冻结的海外实际收货共享物理仓ID', overseas_warehouse_name VARCHAR(255) NOT NULL COMMENT '仓库服务校验后的海外实际收货共享物理仓名称快照', operator_key VARCHAR(128) NOT NULL COMMENT '操作账号唯一标识', operator_name VARCHAR(255) NULL COMMENT '操作账号名称', active_operator_key VARCHAR(255) NULL COMMENT '未完成会话平台操作账号全局唯一锁键', @@ -632,11 +3083,11 @@ CREATE TABLE IF NOT EXISTS ll_receiving_container_session ( receiving_at DATETIME(3) NULL COMMENT '首次收货时间', completed_at DATETIME(3) NULL 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 '更新时间', version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本', PRIMARY KEY(id), UNIQUE KEY uk_ll_receiving_active_operator(active_operator_key), UNIQUE KEY uk_ll_receiving_active_container(active_container_key), - KEY idx_ll_receiving_container(customer_id,container_no,status), KEY idx_ll_receiving_operator(customer_id,operator_key,created_at) + KEY idx_ll_receiving_container(container_no,status), KEY idx_ll_receiving_operator(operator_key,created_at) ) ENGINE=InnoDB COMMENT='海外PDA柜号签收收货会话'; SET @ddl = (SELECT IF(EXISTS(SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_receiving_container_session' AND column_name='source_container_booking_id'), - 'SELECT 1','ALTER TABLE ll_receiving_container_session ADD COLUMN source_container_booking_id VARCHAR(128) NULL COMMENT ''稳定来源柜预订ID'' AFTER customer_id')); + 'SELECT 1','ALTER TABLE ll_receiving_container_session ADD COLUMN source_container_booking_id VARCHAR(128) NULL COMMENT ''稳定来源柜预订ID'' AFTER id')); PREPARE phase4_receiving_fact_stmt FROM @ddl; EXECUTE phase4_receiving_fact_stmt; DEALLOCATE PREPARE phase4_receiving_fact_stmt; SET @ddl = (SELECT IF(EXISTS(SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_receiving_container_session' AND column_name='container_fact_hash'), 'SELECT 1','ALTER TABLE ll_receiving_container_session ADD COLUMN container_fact_hash VARCHAR(64) NULL COMMENT ''领取时整柜订单与计划事实摘要'' AFTER source_container_booking_id')); @@ -697,9 +3148,20 @@ SET active_operator_key=CONCAT('OPERATOR:',operator_key), active_container_key=CONCAT('CONTAINER:',UPPER(TRIM(container_no))) WHERE active_operator_key IS NOT NULL OR active_container_key IS NOT NULL; ALTER TABLE ll_receiving_container_session - MODIFY COLUMN customer_id VARCHAR(128) NOT NULL COMMENT '所选柜解析出的客户标识', MODIFY COLUMN active_operator_key VARCHAR(255) NULL COMMENT '未完成会话平台操作账号全局唯一锁键', MODIFY COLUMN active_container_key VARCHAR(255) NULL COMMENT '未完成会话柜号全局唯一锁键'; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_receiving_container_session' AND index_name='idx_ll_receiving_container'), + 'ALTER TABLE ll_receiving_container_session DROP INDEX idx_ll_receiving_container', 'SELECT 1')); +PREPARE phase4_receiving_scope_stmt FROM @ddl; EXECUTE phase4_receiving_scope_stmt; DEALLOCATE PREPARE phase4_receiving_scope_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_receiving_container_session' AND index_name='idx_ll_receiving_operator'), + 'ALTER TABLE ll_receiving_container_session DROP INDEX idx_ll_receiving_operator', 'SELECT 1')); +PREPARE phase4_receiving_scope_stmt FROM @ddl; EXECUTE phase4_receiving_scope_stmt; DEALLOCATE PREPARE phase4_receiving_scope_stmt; +ALTER TABLE ll_receiving_container_session + ADD KEY idx_ll_receiving_container(container_no,status), + ADD KEY idx_ll_receiving_operator(operator_key,created_at); +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_receiving_container_session' AND column_name='customer_id'), + 'ALTER TABLE ll_receiving_container_session DROP COLUMN customer_id', 'SELECT 1')); +PREPARE phase4_receiving_scope_stmt FROM @ddl; EXECUTE phase4_receiving_scope_stmt; DEALLOCATE PREPARE phase4_receiving_scope_stmt; CREATE TABLE IF NOT EXISTS ll_receiving_sku_receipt ( id BIGINT NOT NULL COMMENT 'FBA SKU实收进度ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户账号标识', @@ -716,7 +3178,7 @@ CREATE TABLE IF NOT EXISTS ll_receiving_sku_receipt ( ) ENGINE=InnoDB COMMENT='海外PDA柜号FBA SKU实收进度'; CREATE TABLE IF NOT EXISTS ll_receiving_container_operation ( - id BIGINT NOT NULL COMMENT '柜号收货操作ID', session_id BIGINT NOT NULL COMMENT '柜号签收会话ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户账号标识', + id BIGINT NOT NULL COMMENT '柜号收货操作ID', session_id BIGINT NOT NULL COMMENT '柜号签收会话ID', operator_key VARCHAR(128) NOT NULL COMMENT '操作账号唯一标识', action VARCHAR(32) NOT NULL COMMENT '操作动作', idempotency_key VARCHAR(255) NOT NULL COMMENT '客户端幂等键', request_hash VARCHAR(128) NOT NULL COMMENT '请求内容哈希', request_json LONGTEXT NOT NULL COMMENT '请求内容快照', result_json LONGTEXT NOT NULL COMMENT '响应内容快照', created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', PRIMARY KEY(id), @@ -753,6 +3215,9 @@ SET @ddl = IF(@receiving_operation_global_index=1, 'SELECT 1', 'ALTER TABLE ll_receiving_container_operation DROP INDEX uk_ll_receiving_operation, ADD UNIQUE KEY uk_ll_receiving_operation(operator_key,idempotency_key)', 'ALTER TABLE ll_receiving_container_operation ADD UNIQUE KEY uk_ll_receiving_operation(operator_key,idempotency_key)')); PREPARE phase4_receiving_operation_index_stmt FROM @ddl; EXECUTE phase4_receiving_operation_index_stmt; DEALLOCATE PREPARE phase4_receiving_operation_index_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_receiving_container_operation' AND column_name='customer_id'), + 'ALTER TABLE ll_receiving_container_operation DROP COLUMN customer_id', 'SELECT 1')); +PREPARE phase4_receiving_operation_scope_stmt FROM @ddl; EXECUTE phase4_receiving_operation_scope_stmt; DEALLOCATE PREPARE phase4_receiving_operation_scope_stmt; SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_inbound_order' AND column_name='destination_type'), 'ALTER TABLE ll_inbound_order MODIFY COLUMN destination_type VARCHAR(32) NULL COMMENT ''目的类型:FBA按箱数接收、OVERSEAS逐箱扫码''', 'ALTER TABLE ll_inbound_order ADD COLUMN destination_type VARCHAR(32) NULL COMMENT ''目的类型:FBA按箱数接收、OVERSEAS逐箱扫码'' AFTER destination_warehouse_name')); @@ -842,7 +3307,6 @@ PREPARE phase4_inventory_stmt FROM @ddl; EXECUTE phase4_inventory_stmt; DEALLOCA -- 海运节点达到可提柜后才允许海外 PDA 认领;装柜完成只建立等待门禁快照。 CREATE TABLE IF NOT EXISTS ll_container_shipping_gate ( container_booking_id VARCHAR(128) NOT NULL COMMENT '来源头程柜预订ID', - customer_id VARCHAR(128) NOT NULL COMMENT '客户标识', ocean_booking_id VARCHAR(128) NOT NULL COMMENT '来源海运订舱ID', container_no VARCHAR(64) NOT NULL COMMENT '实际集装箱号', status VARCHAR(32) NOT NULL DEFAULT 'AWAITING_PICKUP_READY' COMMENT '海外收货门禁状态:AWAITING_PICKUP_READY、RECEIVING_AVAILABLE', @@ -864,20 +3328,27 @@ CREATE TABLE IF NOT EXISTS ll_container_shipping_gate ( 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(container_booking_id), - KEY idx_ll_shipping_gate_candidate(customer_id,container_no,status), + KEY idx_ll_shipping_gate_candidate(container_no,status), KEY idx_ll_shipping_gate_version(container_booking_id,aggregate_version,node_sequence) ) ENGINE=InnoDB COMMENT='海外PDA柜号船节点收货资格快照'; INSERT IGNORE INTO ll_container_shipping_gate ( - container_booking_id,customer_id,ocean_booking_id,container_no,status, + container_booking_id,ocean_booking_id,container_no,status, node_sequence,aggregate_version,handover_version,created_at,updated_at ) -SELECT source_container_booking_id,customer_id,'',container_no,'AWAITING_PICKUP_READY', +SELECT source_container_booking_id,'',container_no,'AWAITING_PICKUP_READY', 0,COALESCE(MAX(source_loading_plan_version),0),COALESCE(MAX(source_handover_version),1),NOW(3),NOW(3) FROM ll_order WHERE source_container_booking_id IS NOT NULL AND source_container_booking_id <> '' AND container_no IS NOT NULL AND container_no <> '' -GROUP BY source_container_booking_id,customer_id,container_no; +GROUP BY source_container_booking_id,container_no; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_container_shipping_gate' AND index_name='idx_ll_shipping_gate_candidate'), + 'ALTER TABLE ll_container_shipping_gate DROP INDEX idx_ll_shipping_gate_candidate', 'SELECT 1')); +PREPARE phase4_shipping_gate_scope_stmt FROM @ddl; EXECUTE phase4_shipping_gate_scope_stmt; DEALLOCATE PREPARE phase4_shipping_gate_scope_stmt; +ALTER TABLE ll_container_shipping_gate ADD KEY idx_ll_shipping_gate_candidate(container_no,status); +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_container_shipping_gate' AND column_name='customer_id'), + 'ALTER TABLE ll_container_shipping_gate DROP COLUMN customer_id', 'SELECT 1')); +PREPARE phase4_shipping_gate_scope_stmt FROM @ddl; EXECUTE phase4_shipping_gate_scope_stmt; DEALLOCATE PREPARE phase4_shipping_gate_scope_stmt; CREATE TABLE IF NOT EXISTS ll_pda_operation_history ( id BIGINT NOT NULL COMMENT 'PDA作业历史ID', @@ -1713,9 +4184,18 @@ INSERT IGNORE INTO ll_security_projection ( ) VALUES ('USER','1000001',0,'ACTIVE',0,JSON_ARRAY(),JSON_ARRAY(),0,SHA2('lastleg-baseline:platform_admin:USER:0',256), 'baseline-platform-admin-user-v0','1970-01-01 00:00:00.000','1970-01-01 00:00:00.000'), - ('AUTHORIZATION','1000001',0,NULL,0,JSON_ARRAY(),JSON_ARRAY(),0,SHA2('lastleg-baseline:platform_admin:AUTHORIZATION:0',256), + ('AUTHORIZATION','1000001',0,NULL,0,@platform_admin_overseas_roles,@platform_admin_overseas_authorities,0, + SHA2('lastleg-baseline:platform_admin:AUTHORIZATION:0:current-authorities',256), 'baseline-platform-admin-authorization-v0','1970-01-01 00:00:00.000','1970-01-01 00:00:00.000'); +UPDATE ll_security_projection +SET roles_json=@platform_admin_overseas_roles, + authorities_json=@platform_admin_overseas_authorities, + payload_hash=SHA2('lastleg-baseline:platform_admin:AUTHORIZATION:0:current-authorities',256), + updated_at=CURRENT_TIMESTAMP(3) +WHERE aggregate_type='AUTHORIZATION' AND aggregate_id='1000001' AND tenant_id=0 + AND aggregate_version=0 AND last_event_id='baseline-platform-admin-authorization-v0'; + DROP PROCEDURE IF EXISTS assert_lastleg_security_projection_ready; DELIMITER $$ CREATE PROCEDURE assert_lastleg_security_projection_ready() @@ -1724,6 +4204,22 @@ BEGIN AND table_name IN ('ll_security_event_inbox','ll_security_projection')) <> 2 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='海外IAM安全Inbox或投影表未完整部署'; END IF; + IF EXISTS ( + SELECT 1 FROM ll_security_projection + WHERE aggregate_type='AUTHORIZATION' AND aggregate_id='1000001' AND tenant_id=0 + AND aggregate_version=0 AND last_event_id='baseline-platform-admin-authorization-v0' + AND (JSON_LENGTH(roles_json) <> 1 + OR NOT JSON_CONTAINS(roles_json, JSON_QUOTE('PLATFORM_ADMIN')) + OR JSON_LENGTH(authorities_json) <> 109 + OR NOT JSON_CONTAINS(authorities_json, JSON_QUOTE('platform:overseas-warehouse:last-mile-order:list')) + OR NOT JSON_CONTAINS(authorities_json, JSON_QUOTE('platform:overseas-warehouse:inbound-order:list')) + OR NOT JSON_CONTAINS(authorities_json, JSON_QUOTE('platform:overseas-warehouse:dispatch:list')) + OR NOT JSON_CONTAINS(authorities_json, JSON_QUOTE('platform:overseas-business:purchase:list')) + OR NOT JSON_CONTAINS(authorities_json, JSON_QUOTE('platform:overseas-master-data:quote:list')) + OR NOT JSON_CONTAINS(authorities_json, JSON_QUOTE('platform:finance:fee-source:review'))) + ) THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='海外IAM固定管理员version=0授权快照不完整'; + END IF; END$$ DELIMITER ; CALL assert_lastleg_security_projection_ready(); @@ -1917,20 +4413,6 @@ DROP PROCEDURE assert_receiving_warehouse_assignment_ready; SELECT 'PASS' AS migration_status, '海外接柜仓预分配与柜级修改结构已就绪' AS message; --- 海外 PDA 接柜、作业与船节点均按柜级建模;客户归属保留在柜内各入仓计划。 -SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() - AND table_name='ll_receiving_container_session' AND column_name='customer_id'), - 'ALTER TABLE ll_receiving_container_session DROP COLUMN customer_id', 'SELECT 1')); -PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt; -SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() - AND table_name='ll_receiving_container_operation' AND column_name='customer_id'), - 'ALTER TABLE ll_receiving_container_operation DROP COLUMN customer_id', 'SELECT 1')); -PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt; -SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() - AND table_name='ll_container_shipping_gate' AND column_name='customer_id'), - 'ALTER TABLE ll_container_shipping_gate DROP COLUMN customer_id', 'SELECT 1')); -PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt; - -- 一件代发来源托盘与海外仓实物托盘分离,实物绑定不得覆盖来源身份。 USE baiyue_lastleg; SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() diff --git a/iam/src/main/resources/db/lastleg-permissions.sql b/iam/src/main/resources/db/lastleg-permissions.sql index ade5b0d87..a2c971e26 100644 --- a/iam/src/main/resources/db/lastleg-permissions.sql +++ b/iam/src/main/resources/db/lastleg-permissions.sql @@ -39,6 +39,12 @@ VALUES (5107061,0,'PLATFORM','BUTTON',5107060,'platform:overseas-warehouse:location:create','创建海外库位',NULL,'POST',761,'ACTIVE',1,0), (5107062,0,'PLATFORM','BUTTON',5107060,'platform:overseas-warehouse:location:edit','编辑海外库位',NULL,'PUT',762,'ACTIVE',1,0), (5107063,0,'PLATFORM','BUTTON',5107060,'platform:overseas-warehouse:location:delete','删除海外库位',NULL,'DELETE',763,'ACTIVE',1,0), + (5107230,0,'PLATFORM','MENU',0,'platform:overseas-warehouse:carton:list','海外箱号','/overseas-warehouse/cartons','GET',930,'ACTIVE',1,0), + (5107231,0,'PLATFORM','BUTTON',5107230,'platform:overseas-warehouse:carton:create','创建海外箱号',NULL,'POST',931,'ACTIVE',1,0), + (5107232,0,'PLATFORM','BUTTON',5107230,'platform:overseas-warehouse:carton:edit','编辑海外箱号',NULL,'PUT',932,'ACTIVE',1,0), + (5107240,0,'PLATFORM','MENU',0,'platform:overseas-warehouse:product:list','海外产品库','/overseas-warehouse/products','GET',940,'ACTIVE',1,0), + (5107241,0,'PLATFORM','BUTTON',5107240,'platform:overseas-warehouse:product:create','创建海外产品',NULL,'POST',941,'ACTIVE',1,0), + (5107242,0,'PLATFORM','BUTTON',5107240,'platform:overseas-warehouse:product:edit','编辑海外产品',NULL,'PUT',942,'ACTIVE',1,0), (5107090,0,'PLATFORM','MENU',0,'platform:overseas-warehouse:outbound:list','海外出库单','/overseas-warehouse/outbounds','GET',765,'ACTIVE',1,0), (5107091,0,'PLATFORM','BUTTON',5107090,'platform:overseas-warehouse:outbound:create','创建海外出库单',NULL,'POST',766,'ACTIVE',1,0), (5107092,0,'PLATFORM','MENU',0,'platform:overseas-warehouse:delivery:list','海外派送单','/overseas-warehouse/delivery-orders','GET',767,'ACTIVE',1,0), diff --git a/lastleg-service/AGENTS.md b/lastleg-service/AGENTS.md index dbe8ac3c4..c57fb819a 100644 --- a/lastleg-service/AGENTS.md +++ b/lastleg-service/AGENTS.md @@ -93,7 +93,7 @@ - 内部接口先执行常量时间令牌校验;缺失或错误令牌不得进入消费事务。 - IAM 安全事件只能进入 `/internal/lastleg/security/v1/events`;通用 `/internal/lastleg/events` 只白名单接收装柜完成与实际船节点事件,禁止把 IAM 事件交给 `LoadingCompletedConsumer`。 - Lastleg 安全投影只接受 `ACTIVE`/`DISABLED` 两态安全语义;客户建档和审核的 `PROFILE_REQUIRED`/`PENDING`/`REJECTED`/`ARCHIVED` 由 IAM 在发布前统一折叠为 `DISABLED`,Lastleg 不兼容或解释国内工作流状态。 -- 开发基线只为国内固定 `authUserId=1000001`、`tenantId=0` 的 `platform_admin` 写 USER/AUTHORIZATION version=0 投影;使用 `INSERT IGNORE`,后续 IAM version>=1 事件单调覆盖。不得扩大为历史账号运行时补齐或缺失投影默认放行。 +- 开发/测试空库基线只为国内固定 `authUserId=1000001`、`tenantId=0` 的 `platform_admin` 写 USER/AUTHORIZATION version=0 投影;AUTHORIZATION 必须携带当前 `PLATFORM_ADMIN` 角色及按 IAM 跨区前缀筛选的完整权限快照,统一 SQL 重放只修正仍处于固定 baseline version=0 的该行,后续 IAM version>=1 事件单调覆盖。不得扩大为其他历史账号运行时补齐或缺失投影默认放行。 - PDA 入仓任务完成前必须先通过 `/unload-photos` 保存非空且不重复的卸柜照片文件 ID;照片上传时间由服务端记录,预计报空时间固定为照片上传时间加两天。完成入仓时,库存入账、计划/任务状态推进与 `OVERSEAS_CONTAINER_UNLOADED` V1 Outbox 必须处于同一事务;事件绑定来源头程货柜身份,不得从前端请求拼装或回查头程数据库。 - 海外 PDA 柜号签收只允许平台 `OVERSEAS_WAREHOUSE_PDA_OPERATOR` 使用 `lastleg:pda:operate`;国内头程 PDA 角色不得进入。PDA 不接收 `customerId`,候选按来源柜跨客户聚合,同柜允许多个客户;选柜会话、三类照片及整柜完成事实只绑定 `sourceContainerBookingId + containerNo`,文件与柜级跨区事件使用 `tenantId=0` 平台技术范围。签收后按每个入仓计划自身客户生成任务,扫码、SKU 实收、托盘和库存始终使用入仓码所属票的真实客户。未完成会话按平台操作账号和柜号分别全局唯一;平台管理员和客户账号不得进入该作业入口。 diff --git a/lastleg-service/src/main/resources/db/lastleg.sql b/lastleg-service/src/main/resources/db/lastleg.sql index c3af13fd4..4d39e0b4f 100644 --- a/lastleg-service/src/main/resources/db/lastleg.sql +++ b/lastleg-service/src/main/resources/db/lastleg.sql @@ -2058,16 +2058,137 @@ CREATE TABLE IF NOT EXISTS ll_security_projection ( KEY idx_security_projection_permission (aggregate_id,permission_version) ) ENGINE=InnoDB COMMENT='海外IAM安全状态与权限版本投影'; --- 开发基线固定管理员;后续真实 IAM 事件从 aggregate_version=1 单调覆盖该 version=0 投影。 +-- 空库基线固定管理员必须携带当前海外授权快照;后续真实 IAM 事件从 aggregate_version=1 单调覆盖该 version=0 投影。 +SET @platform_admin_overseas_roles = JSON_ARRAY('PLATFORM_ADMIN'); +SET @platform_admin_overseas_authorities = JSON_ARRAY( + 'platform:finance:bill:adjust', + 'platform:finance:bill:update', + 'platform:finance:bill:void', + 'platform:finance:cost:create', + 'platform:finance:cost:view', + 'platform:finance:customer-account:adjust', + 'platform:finance:customer-account:export', + 'platform:finance:customer-account:list', + 'platform:finance:customer-arap:export', + 'platform:finance:customer-arap:list', + 'platform:finance:customer-statement:export', + 'platform:finance:customer-statement:list', + 'platform:finance:customer-terms:update', + 'platform:finance:customer-terms:view', + 'platform:finance:fee-source:edit', + 'platform:finance:fee-source:review', + 'platform:finance:payment:create', + 'platform:finance:receipt:create', + 'platform:finance:receipt:view', + 'platform:finance:receivable-statistics:export', + 'platform:finance:receivable-statistics:list', + 'platform:finance:recharge:review', + 'platform:finance:reconciliation:adjust', + 'platform:finance:reconciliation:create', + 'platform:finance:reconciliation:issue', + 'platform:finance:reconciliation:review', + 'platform:finance:reconciliation:void', + 'platform:finance:settlement-ap:create', + 'platform:finance:settlement-ap:export', + 'platform:finance:settlement-ap:list', + 'platform:finance:settlement-ar:create', + 'platform:finance:settlement-ar:export', + 'platform:finance:settlement-ar:list', + 'platform:finance:settlement:void', + 'platform:overseas-business:looseboard:create', + 'platform:overseas-business:looseboard:edit', + 'platform:overseas-business:looseboard:list', + 'platform:overseas-business:looseboard:status', + 'platform:overseas-business:purchase:edit', + 'platform:overseas-business:purchase:list', + 'platform:overseas-business:purchase:review', + 'platform:overseas-business:truck:create', + 'platform:overseas-business:truck:edit', + 'platform:overseas-business:truck:list', + 'platform:overseas-business:truck:status', + 'platform:overseas-master-data:exchange-rate:create', + 'platform:overseas-master-data:exchange-rate:edit', + 'platform:overseas-master-data:exchange-rate:export', + 'platform:overseas-master-data:exchange-rate:list', + 'platform:overseas-master-data:exchange-rate:status', + 'platform:overseas-master-data:quote:create', + 'platform:overseas-master-data:quote:edit', + 'platform:overseas-master-data:quote:export', + 'platform:overseas-master-data:quote:list', + 'platform:overseas-master-data:quote:status', + 'platform:overseas-master-data:vehicle:create', + 'platform:overseas-master-data:vehicle:edit', + 'platform:overseas-master-data:vehicle:export', + 'platform:overseas-master-data:vehicle:list', + 'platform:overseas-master-data:vehicle:status', + 'platform:overseas-warehouse:carton:create', + 'platform:overseas-warehouse:carton:edit', + 'platform:overseas-warehouse:carton:list', + 'platform:overseas-warehouse:customer-visit:edit', + 'platform:overseas-warehouse:customer-visit:list', + 'platform:overseas-warehouse:delivery:documents', + 'platform:overseas-warehouse:delivery:list', + 'platform:overseas-warehouse:delivery:return', + 'platform:overseas-warehouse:delivery:status', + 'platform:overseas-warehouse:dispatch:cancel', + 'platform:overseas-warehouse:dispatch:edit', + 'platform:overseas-warehouse:dispatch:list', + 'platform:overseas-warehouse:dispatch:transfer-delivery', + 'platform:overseas-warehouse:exception-order:create', + 'platform:overseas-warehouse:exception-order:list', + 'platform:overseas-warehouse:exception-order:process', + 'platform:overseas-warehouse:inbound-order:create', + 'platform:overseas-warehouse:inbound-order:edit', + 'platform:overseas-warehouse:inbound-order:fba-appointment', + 'platform:overseas-warehouse:inbound-order:list', + 'platform:overseas-warehouse:inbound-order:smart-dispatch', + 'platform:overseas-warehouse:inventory:adjust', + 'platform:overseas-warehouse:inventory:edit', + 'platform:overseas-warehouse:inventory:export', + 'platform:overseas-warehouse:inventory:list', + 'platform:overseas-warehouse:last-mile-order:edit', + 'platform:overseas-warehouse:last-mile-order:export', + 'platform:overseas-warehouse:last-mile-order:list', + 'platform:overseas-warehouse:location:create', + 'platform:overseas-warehouse:location:delete', + 'platform:overseas-warehouse:location:edit', + 'platform:overseas-warehouse:location:list', + 'platform:overseas-warehouse:outbound:cancel', + 'platform:overseas-warehouse:outbound:create', + 'platform:overseas-warehouse:outbound:list', + 'platform:overseas-warehouse:pallet:create', + 'platform:overseas-warehouse:pallet:delete', + 'platform:overseas-warehouse:pallet:edit', + 'platform:overseas-warehouse:pallet:list', + 'platform:overseas-warehouse:product:create', + 'platform:overseas-warehouse:product:edit', + 'platform:overseas-warehouse:product:list', + 'platform:overseas-warehouse:relabel-order:create', + 'platform:overseas-warehouse:relabel-order:list', + 'platform:overseas-warehouse:relabel-order:status', + 'platform:overseas-warehouse:warehouse:create', + 'platform:overseas-warehouse:warehouse:edit', + 'platform:overseas-warehouse:warehouse:list', + 'platform:overseas-warehouse:warehouse:status' +); INSERT IGNORE INTO ll_security_projection ( aggregate_type,aggregate_id,tenant_id,security_status,permission_version,roles_json,authorities_json,aggregate_version, payload_hash,last_event_id,changed_at,updated_at ) VALUES ('USER','1000001',0,'ACTIVE',0,JSON_ARRAY(),JSON_ARRAY(),0,SHA2('lastleg-baseline:platform_admin:USER:0',256), 'baseline-platform-admin-user-v0','1970-01-01 00:00:00.000','1970-01-01 00:00:00.000'), - ('AUTHORIZATION','1000001',0,NULL,0,JSON_ARRAY(),JSON_ARRAY(),0,SHA2('lastleg-baseline:platform_admin:AUTHORIZATION:0',256), + ('AUTHORIZATION','1000001',0,NULL,0,@platform_admin_overseas_roles,@platform_admin_overseas_authorities,0, + SHA2('lastleg-baseline:platform_admin:AUTHORIZATION:0:current-authorities',256), 'baseline-platform-admin-authorization-v0','1970-01-01 00:00:00.000','1970-01-01 00:00:00.000'); +UPDATE ll_security_projection +SET roles_json=@platform_admin_overseas_roles, + authorities_json=@platform_admin_overseas_authorities, + payload_hash=SHA2('lastleg-baseline:platform_admin:AUTHORIZATION:0:current-authorities',256), + updated_at=CURRENT_TIMESTAMP(3) +WHERE aggregate_type='AUTHORIZATION' AND aggregate_id='1000001' AND tenant_id=0 + AND aggregate_version=0 AND last_event_id='baseline-platform-admin-authorization-v0'; + CREATE TABLE IF NOT EXISTS ll_wallet_event_inbox ( event_id VARCHAR(64) NOT NULL COMMENT '国内Finance资金结果事件ID', diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/security/LastlegSecurityContractTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/security/LastlegSecurityContractTest.java index 76bf71249..03f575d40 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/security/LastlegSecurityContractTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/security/LastlegSecurityContractTest.java @@ -23,11 +23,21 @@ class LastlegSecurityContractTest { "PRIMARY KEY (aggregate_type,aggregate_id)", "aggregate_version BIGINT", "permission_version BIGINT", "roles_json JSON", "authorities_json JSON"); for (String sql : new String[]{baseline, migration}) { - assertThat(sql).contains("('USER','1000001',0,'ACTIVE',0,JSON_ARRAY(),JSON_ARRAY(),0", - "('AUTHORIZATION','1000001',0,NULL,0,JSON_ARRAY(),JSON_ARRAY(),0", + assertThat(sql).contains("SET @platform_admin_overseas_roles = JSON_ARRAY('PLATFORM_ADMIN')", + "SET @platform_admin_overseas_authorities = JSON_ARRAY(", + "('USER','1000001',0,'ACTIVE',0,JSON_ARRAY(),JSON_ARRAY(),0", + "('AUTHORIZATION','1000001',0,NULL,0,@platform_admin_overseas_roles,@platform_admin_overseas_authorities,0", "baseline-platform-admin-user-v0", - "baseline-platform-admin-authorization-v0") - .doesNotContain("baseline-merchant-admin"); + "baseline-platform-admin-authorization-v0", + "platform:overseas-warehouse:last-mile-order:list", + "platform:overseas-warehouse:inbound-order:list", + "platform:overseas-warehouse:dispatch:list", + "platform:overseas-business:purchase:list", + "platform:overseas-master-data:quote:list", + "platform:finance:fee-source:review", + "AND aggregate_version=0 AND last_event_id='baseline-platform-admin-authorization-v0'") + .doesNotContain("('AUTHORIZATION','1000001',0,NULL,0,JSON_ARRAY(),JSON_ARRAY(),0", + "baseline-merchant-admin"); } assertThat(authSeed).contains("(1000001, 'platform_admin'", "default_tenant_id", "default_app_code"); diff --git a/scripts/README.md b/scripts/README.md index 69aba35e5..d00aa01d3 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -235,18 +235,19 @@ Dry-run 只检查根 `pom.xml` 和关键回归测试文件并打印 Maven 命令 ### 本地 MySQL 全量验收 -正式入口是 `local-db-full-acceptance.ps1`,只允许连接本机 `127.0.0.1` 或 `localhost`,默认端口 `3306`;测试库 `8.163.30.128` 会立即以退出码 2 拒绝。脚本固定校验统一 SQL SHA-256 `00E8F9C24CE16FE6B4D2B6AF60460AE1AF1D264DFE110D4BDCE611F09EB69EAA` 和 15403 行,并静态确认中文注释升级采用校验入队、每表一次 ALTER、结构签名复核、九库覆盖断言及唯一最终 `PASS`。 +正式入口是 `local-db-full-acceptance.ps1`,只允许连接本机 `127.0.0.1` 或 `localhost`,默认端口 `3306`;任何非本机地址都会立即以退出码 2 拒绝。非 dry-run 默认连续执行两轮,每轮精确删除 12 个 `baiyue_*` 国内/海外业务库,再只执行 `deploy/sql/头程数据库统一增量-MySQL8.sql` 和 `deploy/sql/海外区域统一增量-MySQL8.sql`,最后核对两个文件的最终 `PASS` 与 12 库完整性。 ```powershell .\scripts\local-db-full-acceptance.ps1 -DryRun .\scripts\local-db-full-acceptance.ps1 +.\scripts\local-db-full-acceptance.ps1 -Rounds 1 ``` -Dry-run 只校验 SQL 输入并打印空库、复跑、两类 picking 旧结构、WAIT_SPLIT/IAM、海外仓旧结构、中断恢复和最终结构的执行顺序,不启动 MySQL、不写库。真实运行每阶段写入 `.codex-tmp/p23-04-acceptance/acceptance-run-*.log`,不使用 `--force`,任一 SQL 错误立即停止并透传退出码,最后执行 `verify-final.sql`。脚本不保存或传入数据库密码,使用本机默认认证环境。 +Dry-run 只校验两个统一 SQL 的入口标记,打印当前 SHA-256、行数、清理范围和执行顺序,不连接 MySQL。真实运行会先把中文文件名按原始字节暂存为英文文件名,规避 Windows MySQL 客户端的 `source` 路径问题;每轮日志写入 `.codex-tmp/local-db-full-acceptance/round-*/`。脚本不保存数据库密码,认证由本机登录配置或当前进程的 `MYSQL_PWD` 等外部方式提供。成功后保留第二轮完整初始化结果;失败时可能留下半初始化库,下次运行会先清理精确范围再重建。 仓库中 `phase2-test-env-acceptance.ps1`、`admin-test-env-acceptance.mjs`、`client-test-env-acceptance.mjs`、`export-center-test-env-acceptance.ps1` 等属于测试环境/API 验收脚本;它们可能读取或写入测试环境数据,不由本入口调用。`migrate-local-qualification-file.ps1`、`local-env.ps1` 等是环境/迁移辅助脚本,也不由本入口调用。 -本入口不读取数据库凭据、不执行 MySQL、HTTP、迁移、DDL、INSERT/UPDATE/DELETE,也不启动 Nacos、Gateway 或业务服务。需要数据库矩阵时必须单独运行受控验收流程。 +本入口只操作明确指定的本机 MySQL,不访问开发或测试 HTTP 环境,不启动 Nacos、Gateway 或业务服务,也不会清理系统库或非 `baiyue_*` 数据库。 ## 退出码 diff --git a/scripts/admin-test-env-modules/initialization-baseline-contract.test.mjs b/scripts/admin-test-env-modules/initialization-baseline-contract.test.mjs index 2bc64aff1..a5a501293 100644 --- a/scripts/admin-test-env-modules/initialization-baseline-contract.test.mjs +++ b/scripts/admin-test-env-modules/initialization-baseline-contract.test.mjs @@ -3,6 +3,13 @@ import { readFileSync } from 'node:fs' import test from 'node:test' const sql = readFileSync(new URL('../../deploy/sql/头程数据库统一增量-MySQL8.sql', import.meta.url), 'utf8') +const overseasSql = readFileSync(new URL('../../deploy/sql/海外区域统一增量-MySQL8.sql', import.meta.url), 'utf8') +const lastlegBaseline = readFileSync(new URL('../../lastleg-service/src/main/resources/db/lastleg.sql', import.meta.url), 'utf8') +const lastlegPermissionSeed = readFileSync(new URL('../../iam/src/main/resources/db/lastleg-permissions.sql', import.meta.url), 'utf8') +const phase5DeliveryPermissionSeed = readFileSync(new URL('../../iam/src/main/resources/db/phase5-delivery-permissions.sql', import.meta.url), 'utf8') +const overseasWarehousePermissions = readFileSync(new URL('../../../apps/admin-web/src/modules/overseas-warehouse/permissions.ts', import.meta.url), 'utf8') +const overseasBusinessPermissions = readFileSync(new URL('../../../apps/admin-web/src/modules/overseas-business/permissions.ts', import.meta.url), 'utf8') +const overseasMasterDataPermissions = readFileSync(new URL('../../../apps/admin-web/src/modules/overseas-master-data/permissions.ts', import.meta.url), 'utf8') const iamSeed = readFileSync(new URL('../../iam/src/main/resources/db/phase1-admin-rbac.sql', import.meta.url), 'utf8') const oceanBookingSchema = readFileSync(new URL('../../firstleg/src/main/resources/db/phase3-ocean-booking-schema.sql', import.meta.url), 'utf8') const truckAppointmentSchema = readFileSync(new URL('../../firstleg/src/main/resources/db/phase3-truck-appointment-schema.sql', import.meta.url), 'utf8') @@ -50,6 +57,81 @@ test('国内统一初始化支持真实空库且不越界创建海外业务库', ) }) +test('国内统一初始化在注释门禁前创建订单申报快照表', () => { + for (const table of ['fl_order_declaration_snapshot', 'fl_order_declaration_event']) { + const createIndex = sql.indexOf(`CREATE TABLE IF NOT EXISTS ${table} (`) + const commentIndex = sql.indexOf(`CALL queue_table_comment('baiyue_firstleg', '${table}'`) + assert.ok(createIndex >= 0, `missing domestic baseline table: ${table}`) + assert.ok(commentIndex >= 0, `missing domestic table comment gate: ${table}`) + assert.ok(createIndex < commentIndex, `${table} must exist before the table-comment gate runs`) + } +}) + +test('海外统一初始化内联空库基线且不依赖模块 SQL', () => { + assert.match(overseasSql, /BEGIN INLINE EMPTY-DATABASE BASELINE: LASTLEG/) + assert.match(overseasSql, /BEGIN INLINE EMPTY-DATABASE BASELINE: OVERSEAS WAREHOUSE/) + assert.match(overseasSql, /CREATE DATABASE IF NOT EXISTS baiyue_lastleg/) + assert.match(overseasSql, /CREATE DATABASE IF NOT EXISTS baiyue_lastlag_warehouse/) + assert.match(overseasSql, /CREATE TABLE IF NOT EXISTS ll_order \(/) + assert.match(overseasSql, /CREATE TABLE IF NOT EXISTS wh_location \(/) + assert.ok( + overseasSql.indexOf('CREATE TABLE IF NOT EXISTS ll_order (') < + overseasSql.indexOf('CREATE TABLE IF NOT EXISTS ll_order_source_pallet ('), + 'll_order must exist before the source-pallet foreign key is created', + ) + assert.ok( + overseasSql.indexOf('CREATE DATABASE IF NOT EXISTS baiyue_lastlag_warehouse') < + overseasSql.indexOf('CREATE TABLE IF NOT EXISTS wh_location ('), + 'the overseas warehouse schema must be selected before its baseline tables are created', + ) +}) + +test('海外空库基线为固定平台管理员提供完整version0授权投影', () => { + const authorityBlock = lastlegBaseline.match( + /SET @platform_admin_overseas_authorities = JSON_ARRAY\(([\s\S]*?)\n\);/, + )?.[1] + assert.ok(authorityBlock, 'missing platform_admin overseas authority snapshot') + const authorities = [...authorityBlock.matchAll(/'([^']+)'/g)].map((match) => match[1]) + assert.equal(authorities.length, 109) + assert.equal(new Set(authorities).size, authorities.length, 'baseline authorities must not contain duplicates') + assert.equal(authorities.filter((item) => item.startsWith('platform:finance:')).length, 34) + assert.equal(authorities.filter((item) => item.startsWith('platform:overseas-business:')).length, 11) + assert.equal(authorities.filter((item) => item.startsWith('platform:overseas-master-data:')).length, 15) + assert.equal(authorities.filter((item) => item.startsWith('platform:overseas-warehouse:')).length, 49) + for (const authority of authorities) { + assert.ok(sql.includes(`'${authority}'`), `authority is absent from the domestic IAM baseline: ${authority}`) + } + for (const source of [overseasWarehousePermissions, overseasBusinessPermissions, overseasMasterDataPermissions]) { + const declared = [...source.matchAll(/'((?:platform:overseas-warehouse|platform:overseas-business|platform:overseas-master-data):[^']+)'/g)] + .map((match) => match[1]) + for (const authority of declared) { + assert.ok( + [lastlegPermissionSeed, phase5DeliveryPermissionSeed].some((seed) => seed.includes(`'${authority}'`)), + `Admin permission is absent from the IAM source seed: ${authority}`, + ) + assert.ok(authorities.includes(authority), `Admin permission is absent from the fixed-admin overseas snapshot: ${authority}`) + } + } + for (const required of [ + 'platform:overseas-warehouse:last-mile-order:list', + 'platform:overseas-warehouse:inbound-order:list', + 'platform:overseas-warehouse:dispatch:list', + 'platform:overseas-business:purchase:list', + 'platform:overseas-master-data:quote:list', + 'platform:finance:fee-source:review', + ]) assert.ok(authorities.includes(required), `missing fixed-admin overseas authority: ${required}`) + + assert.match(lastlegBaseline, /SET @platform_admin_overseas_roles = JSON_ARRAY\('PLATFORM_ADMIN'\)/) + assert.doesNotMatch(lastlegBaseline, /\('AUTHORIZATION','1000001',0,NULL,0,JSON_ARRAY\(\),JSON_ARRAY\(\)/) + assert.doesNotMatch(overseasSql, /\('AUTHORIZATION','1000001',0,NULL,0,JSON_ARRAY\(\),JSON_ARRAY\(\)/) + + const inline = overseasSql.match( + /-- BEGIN INLINE EMPTY-DATABASE BASELINE: LASTLEG\r?\n([\s\S]*?)\r?\n\r?\n-- END INLINE EMPTY-DATABASE BASELINE: LASTLEG/, + )?.[1] + assert.ok(inline, 'missing inline Lastleg baseline') + assert.equal(inline.replace(/\r\n/g, '\n').trim(), lastlegBaseline.replace(/\r\n/g, '\n').trim()) +}) + test('国内统一初始化补齐入库费用人工调整基线字段', () => { assert.match(sql, /baseline_chargeable_quantity DECIMAL\(18,6\) NOT NULL/) assert.match(sql, /SET baseline_chargeable_quantity = chargeable_quantity[\s\S]*?WHERE baseline_chargeable_quantity IS NULL/) diff --git a/scripts/local-db-full-acceptance.ps1 b/scripts/local-db-full-acceptance.ps1 index 733ad8e4a..dbb65e998 100644 --- a/scripts/local-db-full-acceptance.ps1 +++ b/scripts/local-db-full-acceptance.ps1 @@ -2,92 +2,128 @@ param( [switch]$DryRun, [Alias('Host')][string]$TargetHost = '127.0.0.1', [int]$Port = 3306, - [string]$InputPath, [string]$MysqlExe = 'C:\Program Files\MySQL\MySQL Server 8.4\bin\mysql.exe', - [string]$User = 'root' + [string]$User = 'root', + [ValidateRange(1, 10)][int]$Rounds = 2 ) $ErrorActionPreference = 'Stop' $Repo = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path $Workspace = (Resolve-Path (Join-Path $Repo '..')).Path -$Acceptance = Join-Path $Workspace '.codex-tmp\p23-04-acceptance' -$ExpectedSha256 = '0EE13427AB60CECCEB42985CF1E78A05645C18CD9BF2886110C0AFEAF3CA7B9B' -$ExpectedLines = 15674 +$ReportRoot = Join-Path $Workspace '.codex-tmp\local-db-full-acceptance' +$DomesticSql = Join-Path $Repo 'deploy\sql\头程数据库统一增量-MySQL8.sql' +$OverseasSql = Join-Path $Repo 'deploy\sql\海外区域统一增量-MySQL8.sql' +$Schemas = @( + 'baiyue_auth', 'baiyue_iam', 'baiyue_order', 'baiyue_warehouse', + 'baiyue_firstleg', 'baiyue_file', 'baiyue_audit', 'baiyue_event_bridge', + 'baiyue_finance', 'baiyue_lastleg', 'baiyue_lastlag_warehouse', + 'baiyue_event_bridge_overseas' +) if ($TargetHost -notin @('127.0.0.1', 'localhost')) { [Console]::Error.WriteLine('Refusing non-local database; only 127.0.0.1 or localhost is allowed.') exit 2 } -if ([string]::IsNullOrWhiteSpace($InputPath)) { - $InputPath = (Get-ChildItem (Join-Path $Repo 'deploy\sql') -Filter '*MySQL8.sql' -File | - Where-Object { $_.Length -gt 100000 } | Select-Object -First 1).FullName -} -if (-not (Test-Path -LiteralPath $InputPath)) { - [Console]::Error.WriteLine("InputPath does not exist: $InputPath") +if (-not (Test-Path -LiteralPath $MysqlExe)) { + [Console]::Error.WriteLine("mysql executable does not exist: $MysqlExe") exit 2 } -$actual = ((Get-FileHash -Algorithm SHA256 -LiteralPath $InputPath).Hash).Trim().ToUpperInvariant() -$lines = [System.IO.File]::ReadAllLines($InputPath).Length -if ($actual -ne $ExpectedSha256 -or $lines -ne $ExpectedLines) { - [Console]::Error.WriteLine("Unified SQL snapshot mismatch: $actual/$lines") - exit 2 -} -$sqlText = [System.IO.File]::ReadAllText($InputPath) -$commentGateTokens = @( - 'CREATE PROCEDURE queue_column_comment', - 'CREATE PROCEDURE apply_one_table_comments', - 'CALL apply_queued_comments();', - '字段注释批量升级改变了结构签名', - '存在缺失中文注释的业务表', - '存在缺失中文注释的业务字段' -) -foreach ($token in $commentGateTokens) { - if (-not $sqlText.Contains($token)) { - [Console]::Error.WriteLine("Unified SQL comment acceptance marker missing: $token") +foreach ($sqlFile in @($DomesticSql, $OverseasSql)) { + if (-not (Test-Path -LiteralPath $sqlFile)) { + [Console]::Error.WriteLine("Unified SQL does not exist: $sqlFile") exit 2 } } -if ([regex]::Matches($sqlText, "SELECT 'PASS' AS migration_status").Count -ne 1) { - [Console]::Error.WriteLine('Unified SQL must contain exactly one final PASS statement') + +$DomesticText = [IO.File]::ReadAllText($DomesticSql) +$OverseasText = [IO.File]::ReadAllText($OverseasSql) +if (-not $DomesticText.Contains("SELECT 'PASS' AS migration_status")) { + [Console]::Error.WriteLine('Domestic unified SQL is missing its final PASS statement.') exit 2 } +foreach ($marker in @( + '-- BEGIN INLINE EMPTY-DATABASE BASELINE: LASTLEG', + '-- BEGIN INLINE EMPTY-DATABASE BASELINE: OVERSEAS WAREHOUSE', + "SELECT 'PASS' AS migration_status" +)) { + if (-not $OverseasText.Contains($marker)) { + [Console]::Error.WriteLine("Overseas unified SQL is missing marker: $marker") + exit 2 + } +} -$sequence = @( - 'empty: base.sql', 'replay: base.sql + input.sql + input.sql', - 'smart-old: fixture-smart-old.sql', 'inbound-old: fixture-inbound-old.sql', - 'waitsplit-iam: fixture-test-shape.sql', 'overseas-old: fixture-overseas-old.sql', - 'overseas-missing-objects: fixture-overseas-missing-objects.sql', - 'interrupted-recovery: fixture-interrupted.sql', 'final-structure: verify-final.sql') -Write-Output "SQL SHA-256: $actual / lines: $lines" -Write-Output 'Comment gate: queued validation, per-table ALTER, signature verification, nine-schema coverage, unique PASS' +$DomesticHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $DomesticSql).Hash +$OverseasHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $OverseasSql).Hash Write-Output "Target: $TargetHost`:$Port" -Write-Output 'Fixture order:' -$sequence | ForEach-Object { Write-Output " $_" } -if ($DryRun) { Write-Output 'DRY-RUN PASS (mysql not started; no database writes)'; exit 0 } +Write-Output "Rounds: $Rounds" +Write-Output "Domestic SQL: $DomesticHash / $([IO.File]::ReadAllLines($DomesticSql).Length) lines" +Write-Output "Overseas SQL: $OverseasHash / $([IO.File]::ReadAllLines($OverseasSql).Length) lines" +Write-Output "Reset scope: $($Schemas -join ', ')" +Write-Output 'Execution order per round: clear exact schemas -> domestic unified SQL -> overseas unified SQL -> final PASS and schema checks' +if ($DryRun) { + Write-Output 'DRY-RUN PASS (mysql not contacted; no database writes)' + exit 0 +} -$acceptanceInput = Join-Path $Acceptance 'input.sql' -Copy-Item -LiteralPath $InputPath -Destination $acceptanceInput -Force -$stagedHash = ((Get-FileHash -Algorithm SHA256 -LiteralPath $acceptanceInput).Hash).Trim().ToUpperInvariant() -if ($stagedHash -ne $actual) { - [Console]::Error.WriteLine("Failed to stage the validated unified SQL snapshot: $stagedHash") +New-Item -ItemType Directory -Path $ReportRoot -Force | Out-Null +$StagedDomestic = Join-Path $ReportRoot 'domestic.sql' +$StagedOverseas = Join-Path $ReportRoot 'overseas.sql' +Copy-Item -LiteralPath $DomesticSql -Destination $StagedDomestic -Force +Copy-Item -LiteralPath $OverseasSql -Destination $StagedOverseas -Force +if ((Get-FileHash -Algorithm SHA256 -LiteralPath $StagedDomestic).Hash -ne $DomesticHash -or + (Get-FileHash -Algorithm SHA256 -LiteralPath $StagedOverseas).Hash -ne $OverseasHash) { + [Console]::Error.WriteLine('Failed to stage byte-identical SQL files under ASCII filenames.') exit 2 } -$run = 0 -function Invoke-Stage([string]$Name, [string]$File) { - $script = Join-Path $Acceptance $File - $log = Join-Path $Acceptance ("acceptance-run-{0:D2}-{1}.log" -f $script:run, $Name) - $script:run++ - if (-not (Test-Path -LiteralPath $script)) { [Console]::Error.WriteLine("Script does not exist: $script"); return 2 } - & $MysqlExe '--protocol=TCP' "--host=$TargetHost" "--port=$Port" "--user=$User" '--database=mysql' '--batch' ("--execute=source " + $script.Replace('\','/')) *> $log - return $LASTEXITCODE +$MysqlCommon = @( + '--protocol=TCP', "--host=$TargetHost", "--port=$Port", "--user=$User", + '--default-character-set=utf8mb4', '--batch' +) +& $MysqlExe @MysqlCommon '--skip-column-names' '--execute=SELECT VERSION(), @@hostname, @@port;' | Out-Null +if ($LASTEXITCODE -ne 0) { + [Console]::Error.WriteLine('Cannot connect to the local MySQL target. Configure authentication outside this script, for example with MYSQL_PWD for the current process.') + exit $LASTEXITCODE } -$calls = @( - @('empty','base.sql'), @('replay-reset','base.sql'), @('replay-1','input.sql'), @('replay-2','input.sql'), - @('smart-old','fixture-smart-old.sql'), @('inbound-old','fixture-inbound-old.sql'), @('waitsplit-iam','fixture-test-shape.sql'), - @('overseas-old','fixture-overseas-old.sql'), @('overseas-missing','fixture-overseas-missing-objects.sql'), - @('interrupted','fixture-interrupted.sql'), @('final-verify','verify-final.sql')) -foreach ($call in $calls) { $code = Invoke-Stage $call[0] $call[1]; if ($code -ne 0) { exit $code } } -Write-Output 'FULL LOCAL ACCEPTANCE PASS' +$DropSql = ($Schemas | ForEach-Object { "DROP DATABASE IF EXISTS ``$_```;" }) -join ' ' +for ($round = 1; $round -le $Rounds; $round++) { + $RoundRoot = Join-Path $ReportRoot ("round-{0}" -f $round) + New-Item -ItemType Directory -Path $RoundRoot -Force | Out-Null + $Stopwatch = [Diagnostics.Stopwatch]::StartNew() + + & $MysqlExe @MysqlCommon "--execute=$DropSql" *> (Join-Path $RoundRoot '00-clear.log') + if ($LASTEXITCODE -ne 0) { + [Console]::Error.WriteLine("Round $round failed while clearing the exact project schemas.") + exit $LASTEXITCODE + } + + $DomesticLog = Join-Path $RoundRoot '10-domestic.log' + $DomesticSource = $StagedDomestic.Replace('\', '/') + & $MysqlExe @MysqlCommon '--database=mysql' "--execute=source $DomesticSource" *> $DomesticLog + if ($LASTEXITCODE -ne 0 -or -not (Select-String -LiteralPath $DomesticLog -Pattern '^PASS\s+统一增量全部执行完成' -Quiet)) { + [Console]::Error.WriteLine("Round $round domestic unified SQL failed. See $DomesticLog") + exit $(if ($LASTEXITCODE -ne 0) { $LASTEXITCODE } else { 2 }) + } + + $OverseasLog = Join-Path $RoundRoot '20-overseas.log' + $OverseasSource = $StagedOverseas.Replace('\', '/') + & $MysqlExe @MysqlCommon '--database=mysql' "--execute=source $OverseasSource" *> $OverseasLog + if ($LASTEXITCODE -ne 0 -or -not (Select-String -LiteralPath $OverseasLog -Pattern '^PASS\s+海外财务P0费用来源字段已就绪$' -Quiet)) { + [Console]::Error.WriteLine("Round $round overseas unified SQL failed. See $OverseasLog") + exit $(if ($LASTEXITCODE -ne 0) { $LASTEXITCODE } else { 2 }) + } + + $SchemaList = "'" + ($Schemas -join "','") + "'" + $SchemaCount = (& $MysqlExe @MysqlCommon '--skip-column-names' "--execute=SELECT COUNT(*) FROM information_schema.schemata WHERE schema_name IN ($SchemaList);").Trim() + if ($LASTEXITCODE -ne 0 -or [int]$SchemaCount -ne $Schemas.Count) { + [Console]::Error.WriteLine("Round $round schema verification failed: $SchemaCount/$($Schemas.Count)") + exit 2 + } + + $Stopwatch.Stop() + Write-Output "ROUND $round PASS / $SchemaCount schemas / $([Math]::Round($Stopwatch.Elapsed.TotalSeconds, 1)) seconds" +} + +Write-Output 'FULL LOCAL TWO-REGION DATABASE ACCEPTANCE PASS' exit 0