From 25d2a135811c18fa25175ba21ca50673ecd85b36 Mon Sep 17 00:00:00 2001 From: liuyihe <2513762445@qq.com> Date: Tue, 4 Aug 2026 10:24:43 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BA=8C=E6=9C=9F=E5=86=85=E5=AE=B9=E6=8F=90?= =?UTF-8?q?=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...8-04-二期入库新增表字段-MySQL8一键执行.sql | 1231 +++++++++++++++++ 1 file changed, 1231 insertions(+) create mode 100644 deploy/sql/2026-08-04-二期入库新增表字段-MySQL8一键执行.sql diff --git a/deploy/sql/2026-08-04-二期入库新增表字段-MySQL8一键执行.sql b/deploy/sql/2026-08-04-二期入库新增表字段-MySQL8一键执行.sql new file mode 100644 index 000000000..0dd1a364f --- /dev/null +++ b/deploy/sql/2026-08-04-二期入库新增表字段-MySQL8一键执行.sql @@ -0,0 +1,1231 @@ +-- 柏悦头程二期入库新增表���字段:MySQL 8 一键执行脚本 +-- 生成日期:2026-08-04 +-- 适用版本:MySQL 8.0+(需要 GENERATED COLUMN、JSON、CHECK 约束支持) +-- 适用场景: +-- 1. 新环境:在一期基线完成后直接执行,创建二期最终表结构。 +-- 2. 存量二期环境:可重复执行,自动补齐新字段、索引和 A0 共享溢出约束。 +-- 执行范围:仅 CREATE / ALTER 表、字段、索引、约束;不插入权限、不写业务单据。 +-- 目标数据库:baiyue_file、baiyue_firstleg、baiyue_warehouse。 +-- 前置条件:一期的 baiyue_firstleg.fl_fulfillment_task 与 baiyue_warehouse.wh_warehouse 已存在。 +-- 注意: +-- - MySQL DDL 自动提交,执行前必须完成备份并在临时/预发布库演练。 +-- - 既有数据若已违反即将建立的唯一约束,脚本会停止,需要先治理数据。 +-- - 本脚本由 MySQL Client / MySQL Workbench 直接执行;包含 DELIMITER 的存储过程定义。 +-- - 不连接、不修改正式库;请在明确指定的目标库连接中执行。 + +SET NAMES utf8mb4; +SET SESSION sql_mode = 'ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'; + +SELECT VERSION() AS mysql_version, + '开始执行二期入库新增表与字段脚本' AS execution_message; + +SELECT + EXISTS(SELECT 1 FROM information_schema.tables WHERE table_schema = 'baiyue_firstleg' AND table_name = 'fl_fulfillment_task') AS has_phase1_fulfillment_task, + EXISTS(SELECT 1 FROM information_schema.tables WHERE table_schema = 'baiyue_warehouse' AND table_name = 'wh_warehouse') AS has_phase1_warehouse; + +-- ============================================================================ +-- 文件中心基线(测量图片依赖) +-- 来源:file/src/main/resources/db/file.sql +-- ============================================================================ +-- 文件中心独占数据库,其他服务只保存文件逻辑ID。 +CREATE DATABASE IF NOT EXISTS baiyue_file + DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci; +USE baiyue_file; + +CREATE TABLE IF NOT EXISTS file_object ( + id BIGINT UNSIGNED NOT NULL COMMENT '文件ID(雪花ID)', + tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', + business_type VARCHAR(32) NULL COMMENT '关联业务类型', + business_id VARCHAR(64) NULL COMMENT '关联业务逻辑ID', + original_name VARCHAR(255) NOT NULL COMMENT '原始文件名', + object_key VARCHAR(512) NOT NULL COMMENT '对象存储键', + storage_provider VARCHAR(32) NOT NULL COMMENT '存储提供方', + bucket_name VARCHAR(128) NOT NULL COMMENT '存储桶名称', + content_type VARCHAR(128) NOT NULL COMMENT '文件内容类型', + size_bytes BIGINT UNSIGNED NOT NULL COMMENT '文件字节数', + sha256 CHAR(64) NULL COMMENT '文件SHA-256摘要', + status VARCHAR(24) NOT NULL DEFAULT 'ACTIVE' COMMENT '文件状态', + created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + updated_by BIGINT UNSIGNED NOT 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_file_tenant_object (tenant_id, object_key), + KEY idx_file_tenant_business (tenant_id, business_type, business_id), + KEY idx_file_tenant_created (tenant_id, created_at) +) ENGINE=InnoDB COMMENT='文件元数据表'; + +CREATE TABLE IF NOT EXISTS file_download_token ( + id BIGINT UNSIGNED NOT NULL COMMENT '下载令牌ID', + tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', + file_id BIGINT UNSIGNED NOT NULL COMMENT '文件逻辑ID', + token_hash CHAR(64) NOT NULL COMMENT '下载令牌SHA-256摘要', + expires_at DATETIME(3) NOT NULL COMMENT '过期时间', + max_downloads INT UNSIGNED NOT NULL DEFAULT 1 COMMENT '最大下载次数', + download_count INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '已下载次数', + revoked_at DATETIME(3) NULL COMMENT '撤销时间', + created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + PRIMARY KEY (id), + UNIQUE KEY uk_download_token_hash (token_hash), + KEY idx_download_file (tenant_id, file_id), + KEY idx_download_expiry (expires_at) +) ENGINE=InnoDB COMMENT='临时下载授权令牌表'; + +CREATE TABLE IF NOT EXISTS file_download_log ( + id BIGINT UNSIGNED NOT NULL COMMENT '下载日志ID', + tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', + file_id BIGINT UNSIGNED NOT NULL COMMENT '文件逻辑ID', + user_id BIGINT UNSIGNED NULL COMMENT '下载用户ID', + request_id VARCHAR(64) NULL COMMENT '请求ID', + client_ip VARCHAR(64) NULL COMMENT '客户端IP', + result VARCHAR(16) NOT NULL COMMENT '下载结果', + failure_reason VARCHAR(500) NULL COMMENT '失败原因', + downloaded_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '下载时间', + PRIMARY KEY (id), + KEY idx_download_log_file (tenant_id, file_id, downloaded_at), + KEY idx_download_log_user (tenant_id, user_id, downloaded_at) +) ENGINE=InnoDB COMMENT='文件下载审计日志表'; + + +-- ============================================================================ +-- Firstleg 二期最终基线表 +-- 来源:firstleg/src/main/resources/db/phase2-inbound-schema.sql +-- ============================================================================ +SET NAMES utf8mb4; + +CREATE DATABASE IF NOT EXISTS baiyue_firstleg + DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci; +USE baiyue_firstleg; + +CREATE TABLE IF NOT EXISTS fl_inbound_order ( + id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID(雪花ID)', + tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', + task_id BIGINT UNSIGNED NULL COMMENT '头程履约任务ID', + order_id BIGINT UNSIGNED NOT NULL COMMENT '订单服务订单ID,不建立跨库外键', + order_no VARCHAR(32) NOT NULL COMMENT '订单号快照', + inbound_code VARCHAR(40) NOT NULL COMMENT '租户内唯一入仓码', + process_status VARCHAR(24) NOT NULL DEFAULT 'WAIT_MEASURE' COMMENT '入库流程状态', + order_status_snapshot VARCHAR(24) NOT NULL COMMENT '订单状态快照', + measure_review_status VARCHAR(24) NOT NULL DEFAULT 'NOT_SUBMITTED' COMMENT '测量数据审核状态', + fee_status VARCHAR(24) NOT NULL DEFAULT 'UNPRICED' COMMENT '费用业务状态', + fee_review_status VARCHAR(24) NOT NULL DEFAULT 'NOT_SUBMITTED' COMMENT '费用审核状态', + notification_status VARCHAR(24) NOT NULL DEFAULT 'NOT_CREATED' COMMENT '客户通知状态', + expected_arrival_at DATETIME(3) NULL COMMENT '预计到达时间', + customer_id BIGINT UNSIGNED NULL COMMENT '客户逻辑ID', + customer_name VARCHAR(128) NOT NULL COMMENT '客户名称快照', + company_id BIGINT UNSIGNED NULL COMMENT '运营公司逻辑ID', + company_name VARCHAR(128) NULL COMMENT '运营公司名称快照', + warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '实际作业仓库ID', + warehouse_name VARCHAR(128) NOT NULL COMMENT '实际作业仓库名称快照', + delivery_warehouse_id BIGINT UNSIGNED NULL COMMENT '交货仓逻辑ID', + delivery_warehouse_name VARCHAR(128) NULL COMMENT '交货仓名称快照', + business_type VARCHAR(32) NOT NULL COMMENT '业务类型', + channel_code VARCHAR(64) NULL COMMENT '渠道编码', + channel_name VARCHAR(128) NULL COMMENT '渠道名称快照', + carrier_id BIGINT UNSIGNED NULL COMMENT '计费船司逻辑ID', + carrier_name VARCHAR(128) NULL COMMENT '计费船司名称快照', + tax_mode VARCHAR(24) NULL COMMENT '买单或退税模式', + container_mode VARCHAR(24) NULL COMMENT '整柜或散货模式', + customs_inspection TINYINT(1) NULL COMMENT '是否商检', + destination_type VARCHAR(24) NULL COMMENT '目的仓类型', + destination_warehouse_id BIGINT UNSIGNED NULL COMMENT '目的仓逻辑ID', + destination_code VARCHAR(32) NULL COMMENT '目的仓编码,例如FBA仓编码', + destination_name VARCHAR(128) NULL COMMENT '目的仓名称快照', + hold_flag TINYINT(1) NOT NULL DEFAULT 0 COMMENT '当前是否扣货', + current_hold_reason VARCHAR(500) NULL COMMENT '当前扣货理由快照', + total_package_count INT UNSIGNED NULL COMMENT '确认后的总箱数', + actual_gross_weight_kg DECIMAL(18,3) NULL COMMENT '确认后的实际总毛重kg', + chargeable_weight_kg DECIMAL(18,3) NULL COMMENT '当前费用快照的计费重量kg', + actual_volume_cbm DECIMAL(18,4) NULL COMMENT '确认后的实际总体积m3', + chargeable_volume_cbm DECIMAL(18,4) NULL COMMENT '当前费用快照的计费体积m3', + pallet_count INT UNSIGNED NULL COMMENT '已分配总托盘数快照', + volume_weight_kg DECIMAL(18,3) NULL COMMENT '总材积重kg', + actual_net_weight_kg DECIMAL(18,3) NULL COMMENT '总净重kg', + measure_completed_at DATETIME(3) NULL COMMENT '测量完成时间', + inbounded_at DATETIME(3) NULL COMMENT '入库完成时间', + express_company_code VARCHAR(64) NULL COMMENT '快递公司编码快照', + express_company_name VARCHAR(128) NULL COMMENT '快递公司名称快照', + express_tracking_no VARCHAR(128) NULL COMMENT '快递单号快照', + public_remark VARCHAR(1000) NULL COMMENT '客户可见备注', + created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID', + created_by_name VARCHAR(64) NULL COMMENT '创建人名称快照', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + updated_by BIGINT UNSIGNED NOT 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_inbound_tenant_code (tenant_id, inbound_code), + UNIQUE KEY uk_inbound_tenant_order (tenant_id, order_id), + KEY idx_inbound_process_time (tenant_id, process_status, created_at, id), + KEY idx_inbound_measure_time (tenant_id, measure_review_status, created_at, id), + KEY idx_inbound_fee_time (tenant_id, fee_review_status, created_at, id), + KEY idx_inbound_warehouse_status (tenant_id, warehouse_id, process_status, created_at), + KEY idx_inbound_customer_time (tenant_id, customer_id, created_at), + KEY idx_inbound_company_time (tenant_id, company_id, created_at), + KEY idx_inbound_arrival (tenant_id, expected_arrival_at), + KEY idx_inbound_destination (tenant_id, destination_code, created_at), + KEY idx_inbound_channel (tenant_id, channel_code, created_at), + CONSTRAINT fk_inbound_task FOREIGN KEY (task_id) REFERENCES fl_fulfillment_task (id) +) ENGINE=InnoDB COMMENT='二期头程入库单聚合根和列表查询快照'; + +CREATE TABLE IF NOT EXISTS fl_inbound_pickup_snapshot ( + id BIGINT UNSIGNED NOT NULL COMMENT '提货信息快照ID', + tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', + inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID', + pickup_method VARCHAR(24) NULL COMMENT '提货方式', + pickup_at DATETIME(3) NULL COMMENT '提货时间', + contact_name VARCHAR(64) NULL COMMENT '提货联系人', + contact_phone VARCHAR(32) NULL COMMENT '提货联系人电话', + contact_email VARCHAR(128) NULL COMMENT '提货联系人邮箱', + country_code CHAR(2) NULL COMMENT '国家二字码', + province VARCHAR(64) NULL COMMENT '省或州', + city VARCHAR(64) NULL COMMENT '城市', + district VARCHAR(64) NULL COMMENT '区县', + address_line VARCHAR(512) NULL COMMENT '提货详细地址', + postal_code VARCHAR(20) NULL COMMENT '提货邮编', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + PRIMARY KEY (id), + UNIQUE KEY uk_pickup_inbound (tenant_id, inbound_id), + CONSTRAINT fk_pickup_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id) +) ENGINE=InnoDB COMMENT='入库单提货信息不可变快照'; + +CREATE TABLE IF NOT EXISTS fl_inbound_destination_snapshot ( + id BIGINT UNSIGNED NOT NULL COMMENT '目的仓地址快照ID', + tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', + inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID', + destination_type VARCHAR(24) NULL COMMENT '目的仓类型', + destination_code VARCHAR(32) NULL COMMENT '目的仓编码', + destination_name VARCHAR(128) NULL COMMENT '目的仓名称', + country_code CHAR(2) NULL COMMENT '国家二字码', + state_province VARCHAR(100) NULL COMMENT '省或州', + city VARCHAR(100) NULL COMMENT '城市', + address_line VARCHAR(512) NULL COMMENT '详细地址', + postal_code VARCHAR(20) NULL COMMENT '邮编', + contact_name VARCHAR(64) NULL COMMENT '联系人', + contact_phone VARCHAR(32) NULL COMMENT '联系人电话', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + PRIMARY KEY (id), + UNIQUE KEY uk_destination_inbound (tenant_id, inbound_id), + CONSTRAINT fk_destination_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id) +) ENGINE=InnoDB COMMENT='入库单目的仓地址和联系人快照'; + +CREATE TABLE IF NOT EXISTS fl_inbound_transport_snapshot ( + id BIGINT UNSIGNED NOT NULL COMMENT '运输信息快照ID', + tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', + inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID', + loading_status VARCHAR(24) NULL COMMENT '配载状态快照', + container_no VARCHAR(64) NULL COMMENT '集装箱号', + cabinet_no VARCHAR(64) NULL COMMENT '柜号或内部柜单号', + vessel_name VARCHAR(128) NULL COMMENT '船名', + voyage_no VARCHAR(64) NULL COMMENT '航次', + sailing_at DATETIME(3) NULL COMMENT '开船时间', + port_arrival_at DATETIME(3) NULL COMMENT '到港时间', + source_updated_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_transport_inbound (tenant_id, inbound_id), + KEY idx_transport_container (tenant_id, container_no), + KEY idx_transport_cabinet (tenant_id, cabinet_no), + KEY idx_transport_loading (tenant_id, loading_status, sailing_at), + CONSTRAINT fk_transport_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id) +) ENGINE=InnoDB COMMENT='入库单关联运输信息只读快照'; + +CREATE TABLE IF NOT EXISTS fl_inbound_measurement ( + id BIGINT UNSIGNED NOT NULL COMMENT '测量批次ID', + tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', + inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID', + source_type VARCHAR(24) NOT NULL COMMENT '数据来源:THIRD_PARTY或MANUAL', + source_code VARCHAR(64) NULL COMMENT '第三方应用或来源编码', + external_event_no VARCHAR(128) NULL COMMENT '第三方事件号', + device_code VARCHAR(64) NULL COMMENT '测量设备编码', + warehouse_code VARCHAR(50) NULL COMMENT '第三方回传仓库编码', + measured_at DATETIME(3) NULL COMMENT '设备测量时间', + status VARCHAR(24) NOT NULL DEFAULT 'RECEIVED' COMMENT '测量批次状态', + raw_payload_json JSON NULL COMMENT '第三方原始报文', + original_document_no VARCHAR(64) NULL COMMENT '原单据号码快照', + original_gross_weight_kg DECIMAL(18,3) NULL COMMENT '原单据毛重kg', + original_volume_cbm DECIMAL(18,4) NULL COMMENT '原单据体积或材积m3', + actual_goods_name VARCHAR(255) NULL COMMENT '实际品名', + total_package_count INT UNSIGNED NULL COMMENT '本批次总箱数', + total_volume_raw_cbm DECIMAL(18,6) NULL COMMENT '本批次原始总体积m3', + total_volume_cbm DECIMAL(18,4) NULL COMMENT '本批次业务总体积m3', + volume_weight_kg DECIMAL(18,3) NULL COMMENT '本批次总材积重kg', + created_by BIGINT UNSIGNED 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 '更新时间', + version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', + PRIMARY KEY (id), + UNIQUE KEY uk_measure_external_event (tenant_id, source_code, external_event_no), + KEY idx_measure_inbound_time (tenant_id, inbound_id, created_at), + CONSTRAINT fk_measure_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id) +) ENGINE=InnoDB COMMENT='入库长宽高测量批次和第三方原始数据'; + +CREATE TABLE IF NOT EXISTS fl_inbound_measurement_item ( + id BIGINT UNSIGNED NOT NULL COMMENT '测量明细ID', + tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', + measurement_id BIGINT UNSIGNED NOT NULL COMMENT '测量批次ID', + item_no VARCHAR(32) NOT NULL COMMENT '批次内明细号', + box_specification VARCHAR(64) NOT NULL COMMENT '箱规标识', + package_count INT UNSIGNED NOT NULL COMMENT '相同箱规箱数', + length_cm DECIMAL(12,3) NOT NULL COMMENT '原始长度cm', + width_cm DECIMAL(12,3) NOT NULL COMMENT '原始宽度cm', + height_cm DECIMAL(12,3) NOT NULL COMMENT '原始高度cm', + girth_cm DECIMAL(12,3) NOT NULL COMMENT '周长cm,规则为长+2*(宽+高)', + unit_weight_kg DECIMAL(18,3) NULL COMMENT '单箱重量kg,第三方仅回传尺寸时可为空', + unit_volume_cbm DECIMAL(18,4) NOT NULL COMMENT '单箱业务体积m3', + volume_raw_cbm DECIMAL(18,6) NOT NULL COMMENT '原始体积m3', + volume_cbm DECIMAL(18,4) NOT NULL COMMENT '业务体积m3', + total_weight_kg DECIMAL(18,3) NULL COMMENT '本箱规总重量kg', + total_volume_cbm DECIMAL(18,4) NOT NULL COMMENT '本箱规总体积m3', + volume_weight_kg DECIMAL(18,3) NOT NULL COMMENT '本箱规材积重kg', + calculation_rule_version VARCHAR(32) NOT NULL COMMENT '体积计算和舍入规则版本', + source_image_file_id BIGINT UNSIGNED NULL COMMENT '测量设备图片文件ID', + over_limit_warning VARCHAR(500) NULL COMMENT '超线提醒快照', + outer_length_cm DECIMAL(12,3) NULL COMMENT '外箱规长度cm', + outer_width_cm DECIMAL(12,3) NULL COMMENT '外箱规宽度cm', + outer_height_cm DECIMAL(12,3) NULL COMMENT '外箱规高度cm', + remark VARCHAR(500) NULL COMMENT '测量明细备注', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + PRIMARY KEY (id), + UNIQUE KEY uk_measure_item_no (tenant_id, measurement_id, item_no), + CONSTRAINT fk_measure_item_batch FOREIGN KEY (measurement_id) REFERENCES fl_inbound_measurement (id) +) ENGINE=InnoDB COMMENT='入库长宽高和箱数测量明细'; + +CREATE TABLE IF NOT EXISTS fl_inbound_weight_result ( + id BIGINT UNSIGNED NOT NULL COMMENT 'OCR重量结果ID', + tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', + inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID', + file_id BIGINT UNSIGNED NOT NULL COMMENT '称重图片文件ID', + ocr_provider VARCHAR(64) NULL COMMENT 'OCR服务商编码', + ocr_raw_text VARCHAR(1000) NULL COMMENT 'OCR原始文本', + ocr_weight_value DECIMAL(18,3) NULL COMMENT 'OCR识别原始重量值', + ocr_weight_unit VARCHAR(8) NULL COMMENT 'OCR识别原始单位', + confidence DECIMAL(6,5) NULL COMMENT '识别置信度', + adjusted_weight_value DECIMAL(18,3) NULL COMMENT '人工修正后的原单位重量', + adjusted_weight_unit VARCHAR(8) NULL COMMENT '人工修正后的单位', + final_weight_kg DECIMAL(18,3) NULL COMMENT '确认并统一换算后的重量kg', + recognition_status VARCHAR(24) NOT NULL DEFAULT 'PENDING' COMMENT '识别和确认状态', + adjustment_reason VARCHAR(500) NULL COMMENT '人工修正原因', + adjusted_by BIGINT UNSIGNED NULL COMMENT '修正人ID', + adjusted_at DATETIME(3) NULL COMMENT '修正时间', + confirmed_by BIGINT UNSIGNED NULL COMMENT '确认人ID', + confirmed_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 '更新时间', + deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记', + version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', + PRIMARY KEY (id), + UNIQUE KEY uk_weight_inbound_file (tenant_id, inbound_id, file_id), + KEY idx_weight_inbound_status (tenant_id, inbound_id, recognition_status), + CONSTRAINT fk_weight_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id) +) ENGINE=InnoDB COMMENT='称重图片OCR原值、人工修正值和确认重量'; + +CREATE TABLE IF NOT EXISTS fl_inbound_weight_revision ( + id BIGINT UNSIGNED NOT NULL COMMENT 'OCR重量修订记录ID', + tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', + inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID', + weight_result_id BIGINT UNSIGNED NOT NULL COMMENT 'OCR重量结果ID', + revision_no INT UNSIGNED NOT NULL COMMENT '结果版本号,与乐观锁版本一致', + action VARCHAR(24) NOT NULL COMMENT '动作:CORRECT或CONFIRM', + before_weight_value DECIMAL(18,3) NULL COMMENT '变更前原单位重量', + before_weight_unit VARCHAR(8) NULL COMMENT '变更前单位', + after_weight_value DECIMAL(18,3) NULL COMMENT '变更后原单位重量', + after_weight_unit VARCHAR(8) NULL COMMENT '变更后单位', + final_weight_kg DECIMAL(18,3) NULL COMMENT '确认时统一换算后的重量kg', + reason VARCHAR(500) NULL COMMENT '人工修正原因', + operator_id BIGINT UNSIGNED NOT NULL COMMENT '操作人ID', + operator_name VARCHAR(64) NULL COMMENT '操作人名称快照', + operated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '操作时间', + PRIMARY KEY (id), + UNIQUE KEY uk_weight_revision_no (tenant_id, weight_result_id, revision_no), + KEY idx_weight_revision_time (tenant_id, inbound_id, operated_at), + CONSTRAINT fk_weight_revision_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id), + CONSTRAINT fk_weight_revision_result FOREIGN KEY (weight_result_id) REFERENCES fl_inbound_weight_result (id) +) ENGINE=InnoDB COMMENT='OCR重量人工修正和确认的不可变版本历史'; + +CREATE TABLE IF NOT EXISTS fl_measure_review_record ( + id BIGINT UNSIGNED NOT NULL COMMENT '测量审核记录ID', + tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', + inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID', + measurement_id BIGINT UNSIGNED NULL COMMENT '被审核的测量批次ID', + review_result VARCHAR(24) NOT NULL COMMENT '审核结果', + review_reason VARCHAR(1000) NULL COMMENT '审核或驳回原因', + exception_release TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否异常放行', + release_reason VARCHAR(1000) NULL COMMENT '异常放行原因', + allow_rejected_config TINYINT(1) NOT NULL DEFAULT 0 COMMENT '操作时Nacos异常放行开关值', + reviewer_id BIGINT UNSIGNED NOT NULL COMMENT '审核人ID', + reviewer_name VARCHAR(64) NULL COMMENT '审核人名称快照', + reviewed_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '审核时间', + PRIMARY KEY (id), + KEY idx_review_inbound_time (tenant_id, inbound_id, reviewed_at), + KEY idx_review_exception_release (tenant_id, inbound_id, exception_release, reviewed_at), + CONSTRAINT fk_review_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id), + CONSTRAINT fk_review_measurement FOREIGN KEY (measurement_id) REFERENCES fl_inbound_measurement (id) +) ENGINE=InnoDB COMMENT='测量审核、驳回和异常放行不可变记录'; + +CREATE TABLE IF NOT EXISTS fl_customer_charge_rule ( + id BIGINT UNSIGNED NOT NULL COMMENT '客户计费规则ID', + tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', + rule_code VARCHAR(50) NOT NULL COMMENT '租户内规则编码', + rule_version INT UNSIGNED NOT NULL COMMENT '规则版本号', + customer_id BIGINT UNSIGNED NOT NULL COMMENT '客户逻辑ID', + customer_name VARCHAR(128) NOT NULL COMMENT '客户名称快照', + business_type VARCHAR(32) NOT NULL COMMENT '业务类型', + charge_mode VARCHAR(16) NOT NULL COMMENT '计费口径:KG或CBM', + tax_mode VARCHAR(24) NULL COMMENT '包税或不包税类型', + conversion_factor DECIMAL(18,6) NOT NULL COMMENT '重量体积换算系数', + rounding_scale TINYINT UNSIGNED NOT NULL DEFAULT 3 COMMENT '结果保留小数位数', + rounding_mode VARCHAR(24) NOT NULL DEFAULT 'CEILING' COMMENT '舍入方式', + currency CHAR(3) NOT NULL COMMENT '默认币种', + effective_from DATETIME(3) NOT NULL COMMENT '生效开始时间', + effective_to DATETIME(3) NULL COMMENT '生效结束时间', + status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE' COMMENT '规则状态', + remark VARCHAR(500) NULL COMMENT '规则备注', + created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + updated_by BIGINT UNSIGNED NOT 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_charge_rule_version (tenant_id, rule_code, rule_version), + KEY idx_charge_rule_match (tenant_id, customer_id, business_type, status, effective_from, effective_to) +) ENGINE=InnoDB COMMENT='客户头程计费规则版本'; + +CREATE TABLE IF NOT EXISTS fl_carrier_fba_rate ( + id BIGINT UNSIGNED NOT NULL COMMENT '船司FBA价格ID', + tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', + rate_code VARCHAR(50) NOT NULL COMMENT '租户内价格编码', + rate_version INT UNSIGNED NOT NULL COMMENT '价格版本号', + origin_warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '起运仓逻辑ID', + origin_warehouse_name VARCHAR(128) NOT NULL COMMENT '起运仓名称快照', + business_type VARCHAR(32) NOT NULL COMMENT '业务或计费类型', + channel_code VARCHAR(64) NULL COMMENT '渠道编码', + carrier_id BIGINT UNSIGNED NULL COMMENT '船司逻辑ID', + carrier_name VARCHAR(128) NOT NULL COMMENT '船司名称快照', + destination_code VARCHAR(32) NOT NULL COMMENT 'FBA目的仓编码', + charge_unit VARCHAR(16) NOT NULL COMMENT '计费单位:KG或CBM', + unit_price DECIMAL(18,6) NOT NULL COMMENT '基础单价', + currency CHAR(3) NOT NULL COMMENT '币种', + additional_fee DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT '固定附加费', + effective_from DATETIME(3) NOT NULL COMMENT '生效开始时间', + effective_to DATETIME(3) NULL COMMENT '生效结束时间', + status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE' COMMENT '价格状态', + remark VARCHAR(500) NULL COMMENT '价格备注', + created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + updated_by BIGINT UNSIGNED NOT 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_fba_rate_version (tenant_id, rate_code, rate_version), + KEY idx_fba_rate_match (tenant_id, origin_warehouse_id, business_type, channel_code, carrier_id, destination_code, charge_unit, status, effective_from, effective_to) +) ENGINE=InnoDB COMMENT='船司FBA目的仓价格版本'; + +CREATE TABLE IF NOT EXISTS fl_inbound_fee ( + id BIGINT UNSIGNED NOT NULL COMMENT '入库费用ID', + tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', + inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID', + fee_no VARCHAR(40) NOT NULL COMMENT '租户内唯一费用单号', + fee_version INT UNSIGNED NOT NULL DEFAULT 1 COMMENT '费用快照版本', + charge_rule_id BIGINT UNSIGNED NOT NULL COMMENT '客户计费规则ID', + charge_rule_version INT UNSIGNED NOT NULL COMMENT '客户规则版本快照', + carrier_rate_id BIGINT UNSIGNED NOT NULL COMMENT '船司FBA价格ID', + carrier_rate_version INT UNSIGNED NOT NULL COMMENT '船司价格版本快照', + rule_code VARCHAR(50) NOT NULL COMMENT '客户规则编码快照', + customer_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID快照', + customer_name VARCHAR(128) NOT NULL COMMENT '客户名称快照', + business_type VARCHAR(32) NOT NULL COMMENT '业务类型快照', + tax_mode VARCHAR(24) NULL COMMENT '包税模式快照', + rounding_scale TINYINT UNSIGNED NOT NULL COMMENT '计费量保留位数快照', + rounding_mode VARCHAR(24) NOT NULL COMMENT '舍入方式快照', + rule_effective_from DATETIME(3) NOT NULL COMMENT '客户规则生效开始快照', + rule_effective_to DATETIME(3) NULL COMMENT '客户规则生效结束快照', + rate_code VARCHAR(50) NOT NULL COMMENT '船司价格编码快照', + origin_warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '起运仓ID快照', + origin_warehouse_name VARCHAR(128) NOT NULL COMMENT '起运仓名称快照', + channel_code VARCHAR(64) NOT NULL COMMENT '渠道编码快照', + carrier_id BIGINT UNSIGNED NOT NULL COMMENT '船司ID快照', + carrier_name VARCHAR(128) NOT NULL COMMENT '船司名称快照', + destination_code VARCHAR(32) NOT NULL COMMENT 'FBA仓编码快照', + rate_effective_from DATETIME(3) NOT NULL COMMENT '价格生效开始快照', + rate_effective_to DATETIME(3) NULL COMMENT '价格生效结束快照', + charge_mode VARCHAR(16) NOT NULL COMMENT '计费口径:KG或CBM', + conversion_factor DECIMAL(18,6) NOT NULL COMMENT '使用的换算系数快照', + actual_weight_kg DECIMAL(18,3) NOT NULL COMMENT '试算时实际重量kg', + actual_volume_cbm DECIMAL(18,4) NOT NULL COMMENT '试算时实际体积m3', + converted_quantity DECIMAL(18,4) NOT NULL COMMENT '换算后的重量或体积', + chargeable_quantity DECIMAL(18,4) NOT NULL COMMENT '最终计费量', + charge_unit VARCHAR(16) NOT NULL COMMENT '计费单位', + currency CHAR(3) NOT NULL COMMENT '币种', + subtotal_amount DECIMAL(18,2) NOT NULL COMMENT '基础费用金额', + additional_amount DECIMAL(18,2) NOT NULL DEFAULT 0 COMMENT '附加费金额', + total_amount DECIMAL(18,2) NOT NULL COMMENT '总金额', + fee_status VARCHAR(24) NOT NULL DEFAULT 'CALCULATED' COMMENT '费用状态', + review_status VARCHAR(24) NOT NULL DEFAULT 'NOT_SUBMITTED' COMMENT '费用审核状态', + exception_release TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否基于测量异常放行生成', + calculated_by BIGINT UNSIGNED NOT NULL COMMENT '试算人ID', + calculated_at DATETIME(3) NOT NULL COMMENT '试算时间', + confirmed_by BIGINT UNSIGNED NULL COMMENT '确认人ID', + confirmed_at DATETIME(3) NULL COMMENT '确认时间', + sent_by BIGINT UNSIGNED NULL COMMENT '发送人ID', + sent_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_inbound_fee_no (tenant_id, fee_no), + UNIQUE KEY uk_inbound_fee_version (tenant_id, inbound_id, fee_version), + KEY idx_inbound_fee_status (tenant_id, inbound_id, fee_status, review_status), + CONSTRAINT fk_fee_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id), + CONSTRAINT fk_fee_charge_rule FOREIGN KEY (charge_rule_id) REFERENCES fl_customer_charge_rule (id), + CONSTRAINT fk_fee_carrier_rate FOREIGN KEY (carrier_rate_id) REFERENCES fl_carrier_fba_rate (id) +) ENGINE=InnoDB COMMENT='入库头程费用计算和确认快照'; + +CREATE TABLE IF NOT EXISTS fl_inbound_fee_item ( + id BIGINT UNSIGNED NOT NULL COMMENT '费用明细ID', + tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', + fee_id BIGINT UNSIGNED NOT NULL COMMENT '费用头ID', + item_no INT UNSIGNED NOT NULL COMMENT '费用单内序号', + fee_code VARCHAR(50) NOT NULL COMMENT '费用项目编码', + fee_name VARCHAR(128) NOT NULL COMMENT '费用项目名称快照', + quantity DECIMAL(18,4) NOT NULL COMMENT '计费数量', + unit VARCHAR(16) NOT NULL COMMENT '计费单位', + unit_price DECIMAL(18,6) NOT NULL COMMENT '单价', + amount DECIMAL(18,2) NOT NULL COMMENT '金额', + currency CHAR(3) NOT NULL COMMENT '币种', + automatic_flag TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否自动计算', + remark VARCHAR(500) NULL COMMENT '费用备注', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + PRIMARY KEY (id), + UNIQUE KEY uk_fee_item_no (tenant_id, fee_id, item_no), + CONSTRAINT fk_fee_item_header FOREIGN KEY (fee_id) REFERENCES fl_inbound_fee (id) +) ENGINE=InnoDB COMMENT='入库头程费用项目明细'; + +CREATE TABLE IF NOT EXISTS fl_inbound_pallet_allocation ( + id BIGINT UNSIGNED NOT NULL COMMENT '头程托盘分配引用ID', + tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', + inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID', + allocation_no INT UNSIGNED NOT NULL COMMENT '入库单内分配序号', + warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '仓库服务仓库ID', + pallet_id BIGINT UNSIGNED NOT NULL COMMENT '仓库服务托盘ID', + pallet_code VARCHAR(50) NOT NULL COMMENT '托盘编码快照', + pallet_capacity_cbm DECIMAL(18,4) NOT NULL COMMENT '托盘可用容积快照', + allocated_volume_cbm DECIMAL(18,4) NOT NULL COMMENT '本托盘分配体积', + allocated_package_count INT UNSIGNED NULL COMMENT '本托盘分配箱数', + location_id BIGINT UNSIGNED NOT NULL COMMENT '仓库服务库位ID', + location_code VARCHAR(50) NOT NULL COMMENT '库位编码快照', + reservation_no VARCHAR(64) NOT NULL COMMENT '仓库库位预占单号', + allocation_status VARCHAR(24) NOT NULL DEFAULT 'RESERVED' 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_pallet_allocation_no (tenant_id, inbound_id, allocation_no), + UNIQUE KEY uk_pallet_allocation_pallet (tenant_id, inbound_id, pallet_id), + KEY idx_allocation_location (tenant_id, warehouse_id, location_id, allocation_status), + CONSTRAINT fk_allocation_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id) +) ENGINE=InnoDB COMMENT='头程侧托盘、库位和仓库预占结果引用'; + +CREATE TABLE IF NOT EXISTS fl_inbound_file ( + id BIGINT UNSIGNED NOT NULL COMMENT '入库单文件关系ID', + tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', + inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID', + file_id BIGINT UNSIGNED NOT NULL COMMENT '文件服务文件ID', + file_type VARCHAR(24) NOT NULL COMMENT '文件类型:RELATED、MARK、PC_ORDER、MEASURE或WEIGHT', + file_name_snapshot VARCHAR(255) NOT NULL COMMENT '文件名快照', + visibility VARCHAR(16) NOT NULL DEFAULT 'INTERNAL' COMMENT '可见范围', + file_version INT UNSIGNED NOT NULL DEFAULT 1 COMMENT '业务文件版本', + uploaded_by BIGINT UNSIGNED NOT NULL COMMENT '上传人ID', + uploaded_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '上传时间', + deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记', + PRIMARY KEY (id), + UNIQUE KEY uk_inbound_file_relation (tenant_id, inbound_id, file_id, file_type), + KEY idx_inbound_file_type (tenant_id, inbound_id, file_type, deleted), + CONSTRAINT fk_file_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id) +) ENGINE=InnoDB COMMENT='入库单与文件中心对象的分类关系'; + +CREATE TABLE IF NOT EXISTS fl_inbound_hold_record ( + id BIGINT UNSIGNED NOT NULL COMMENT '扣货记录ID', + tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', + inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID', + hold_action VARCHAR(16) NOT NULL COMMENT '动作:HOLD或RELEASE', + reason VARCHAR(1000) NOT NULL COMMENT '扣货或解除原因', + evidence_file_id BIGINT UNSIGNED NULL COMMENT '证据文件ID', + operator_id BIGINT UNSIGNED NOT NULL COMMENT '操作人ID', + operator_name VARCHAR(64) NULL COMMENT '操作人名称快照', + operated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '操作时间', + PRIMARY KEY (id), + KEY idx_hold_inbound_time (tenant_id, inbound_id, operated_at), + CONSTRAINT fk_hold_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id) +) ENGINE=InnoDB COMMENT='入库单扣货和解除扣货不可变历史'; + +CREATE TABLE IF NOT EXISTS fl_inbound_internal_note ( + id BIGINT UNSIGNED NOT NULL COMMENT '客服内部备注ID', + tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', + inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID', + content VARCHAR(2000) NOT NULL COMMENT '仅内部可见备注', + created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID', + created_by_name VARCHAR(64) NULL COMMENT '创建人名称快照', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + PRIMARY KEY (id), + KEY idx_internal_note_time (tenant_id, inbound_id, created_at), + CONSTRAINT fk_internal_note_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id) +) ENGINE=InnoDB COMMENT='入库单客服内部备注历史'; + +CREATE TABLE IF NOT EXISTS fl_inbound_status_history ( + id BIGINT UNSIGNED NOT NULL COMMENT '入库状态历史ID', + tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', + inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID', + from_status VARCHAR(24) NULL COMMENT '变更前状态', + to_status VARCHAR(24) NOT NULL COMMENT '变更后状态', + action VARCHAR(32) NOT NULL COMMENT '触发动作', + reason VARCHAR(1000) NULL COMMENT '变更原因', + operator_id BIGINT UNSIGNED NOT NULL COMMENT '操作人ID', + operator_name VARCHAR(64) NULL COMMENT '操作人名称快照', + occurred_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '发生时间', + PRIMARY KEY (id), + KEY idx_inbound_history_time (tenant_id, inbound_id, occurred_at), + CONSTRAINT fk_history_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id) +) ENGINE=InnoDB COMMENT='入库单流程状态不可变历史'; + +CREATE TABLE IF NOT EXISTS fl_inbound_outbox ( + id BIGINT UNSIGNED NOT NULL COMMENT '领域事件全局ID', + tenant_id BIGINT UNSIGNED NOT NULL COMMENT '事件所属租户ID', + aggregate_type VARCHAR(32) NOT NULL DEFAULT 'INBOUND' COMMENT '聚合类型', + aggregate_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID', + event_type VARCHAR(64) NOT NULL COMMENT '事件类型', + payload_json JSON NOT NULL COMMENT '事件负载快照', + publish_status VARCHAR(16) 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 '发布时间', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + PRIMARY KEY (id), + KEY idx_inbound_outbox_publish (publish_status, next_retry_at, created_at) +) ENGINE=InnoDB COMMENT='入库事务Outbox事件表'; + +CREATE TABLE IF NOT EXISTS fl_inbound_idempotency ( + id BIGINT UNSIGNED NOT NULL COMMENT '入库写操作幂等记录ID', + tenant_id BIGINT UNSIGNED NOT NULL COMMENT '租户ID', + operation VARCHAR(40) NOT NULL COMMENT '操作类型', + operator_id BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '操作者或外部应用ID', + inbound_id BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '入库单ID,创建前可为0', + idempotency_key VARCHAR(128) NOT NULL COMMENT '幂等键', + request_hash CHAR(64) NOT NULL COMMENT '请求内容摘要', + status VARCHAR(16) NOT NULL COMMENT '处理状态', + result_json JSON NULL COMMENT '成功结果摘要', + result_resource_id BIGINT UNSIGNED NULL COMMENT '成功结果资源ID', + 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_inbound_idempotency (tenant_id, operation, operator_id, idempotency_key), + KEY idx_inbound_idempotency_created (created_at) +) ENGINE=InnoDB COMMENT='第三方测量、审核、费用发送和入库写操作幂等记录'; + + +-- ============================================================================ +-- Firstleg 测量与 OCR 存量字段补齐 +-- 来源:firstleg/src/main/resources/db/phase2-inbound-measurement-maintenance.sql +-- ============================================================================ +-- 二期入库测量与 OCR 维护增量(只增不删,可重复执行)。 +-- 执行顺序:phase2-inbound-schema.sql 后执行本文件;已有二期库只需执行本文件。 +SET NAMES utf8mb4; +USE baiyue_firstleg; + +DROP PROCEDURE IF EXISTS phase2_measure_add_column; +DELIMITER $$ +CREATE PROCEDURE phase2_measure_add_column( + IN p_table_name VARCHAR(64), + IN p_column_name VARCHAR(64), + IN p_column_definition TEXT +) +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = DATABASE() + AND table_name = p_table_name + AND column_name = p_column_name + ) THEN + SET @phase2_measure_sql = CONCAT( + 'ALTER TABLE `', REPLACE(p_table_name, '`', '``'), + '` ADD COLUMN `', REPLACE(p_column_name, '`', '``'), '` ', p_column_definition + ); + PREPARE phase2_measure_statement FROM @phase2_measure_sql; + EXECUTE phase2_measure_statement; + DEALLOCATE PREPARE phase2_measure_statement; + END IF; +END$$ +DELIMITER ; + +CALL phase2_measure_add_column('fl_inbound_measurement', 'original_document_no', + 'VARCHAR(64) NULL COMMENT ''原单据号码快照'' AFTER raw_payload_json'); +CALL phase2_measure_add_column('fl_inbound_measurement', 'original_gross_weight_kg', + 'DECIMAL(18,3) NULL COMMENT ''原单据毛重kg'' AFTER original_document_no'); +CALL phase2_measure_add_column('fl_inbound_measurement', 'original_volume_cbm', + 'DECIMAL(18,4) NULL COMMENT ''原单据体积或材积m3'' AFTER original_gross_weight_kg'); +CALL phase2_measure_add_column('fl_inbound_measurement', 'actual_goods_name', + 'VARCHAR(255) NULL COMMENT ''实际品名'' AFTER original_volume_cbm'); +CALL phase2_measure_add_column('fl_inbound_measurement', 'volume_weight_kg', + 'DECIMAL(18,3) NULL COMMENT ''本批次总材积重kg'' AFTER total_volume_cbm'); +CALL phase2_measure_add_column('fl_inbound_measurement', 'version', + 'INT NOT NULL DEFAULT 0 COMMENT ''乐观锁版本号'' AFTER updated_at'); + +CALL phase2_measure_add_column('fl_inbound_measurement_item', 'box_specification', + 'VARCHAR(64) NOT NULL DEFAULT '''' COMMENT ''箱规标识'' AFTER item_no'); +CALL phase2_measure_add_column('fl_inbound_measurement_item', 'girth_cm', + 'DECIMAL(12,3) NOT NULL DEFAULT 0 COMMENT ''周长cm,规则为长+2*(宽+高)'' AFTER height_cm'); +CALL phase2_measure_add_column('fl_inbound_measurement_item', 'unit_weight_kg', + 'DECIMAL(18,3) NULL COMMENT ''单箱重量kg'' AFTER girth_cm'); +CALL phase2_measure_add_column('fl_inbound_measurement_item', 'unit_volume_cbm', + 'DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT ''单箱业务体积m3'' AFTER unit_weight_kg'); +CALL phase2_measure_add_column('fl_inbound_measurement_item', 'total_weight_kg', + 'DECIMAL(18,3) NULL COMMENT ''本箱规总重量kg'' AFTER volume_cbm'); +CALL phase2_measure_add_column('fl_inbound_measurement_item', 'total_volume_cbm', + 'DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT ''本箱规总体积m3'' AFTER total_weight_kg'); +CALL phase2_measure_add_column('fl_inbound_measurement_item', 'volume_weight_kg', + 'DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT ''本箱规材积重kg'' AFTER total_volume_cbm'); +CALL phase2_measure_add_column('fl_inbound_measurement_item', 'over_limit_warning', + 'VARCHAR(500) NULL COMMENT ''超线提醒快照'' AFTER source_image_file_id'); +CALL phase2_measure_add_column('fl_inbound_measurement_item', 'outer_length_cm', + 'DECIMAL(12,3) NULL COMMENT ''外箱规长度cm'' AFTER over_limit_warning'); +CALL phase2_measure_add_column('fl_inbound_measurement_item', 'outer_width_cm', + 'DECIMAL(12,3) NULL COMMENT ''外箱规宽度cm'' AFTER outer_length_cm'); +CALL phase2_measure_add_column('fl_inbound_measurement_item', 'outer_height_cm', + 'DECIMAL(12,3) NULL COMMENT ''外箱规高度cm'' AFTER outer_width_cm'); + +DROP PROCEDURE IF EXISTS phase2_measure_add_column; + +CREATE TABLE IF NOT EXISTS fl_inbound_weight_revision ( + id BIGINT UNSIGNED NOT NULL COMMENT 'OCR重量修订记录ID', + tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', + inbound_id BIGINT UNSIGNED NOT NULL COMMENT '入库单ID', + weight_result_id BIGINT UNSIGNED NOT NULL COMMENT 'OCR重量结果ID', + revision_no INT UNSIGNED NOT NULL COMMENT '结果版本号,与乐观锁版本一致', + action VARCHAR(24) NOT NULL COMMENT '动作:CORRECT或CONFIRM', + before_weight_value DECIMAL(18,3) NULL COMMENT '变更前原单位重量', + before_weight_unit VARCHAR(8) NULL COMMENT '变更前单位', + after_weight_value DECIMAL(18,3) NULL COMMENT '变更后原单位重量', + after_weight_unit VARCHAR(8) NULL COMMENT '变更后单位', + final_weight_kg DECIMAL(18,3) NULL COMMENT '确认时统一换算后的重量kg', + reason VARCHAR(500) NULL COMMENT '人工修正原因', + operator_id BIGINT UNSIGNED NOT NULL COMMENT '操作人ID', + operator_name VARCHAR(64) NULL COMMENT '操作人名称快照', + operated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '操作时间', + PRIMARY KEY (id), + UNIQUE KEY uk_weight_revision_no (tenant_id, weight_result_id, revision_no), + KEY idx_weight_revision_time (tenant_id, inbound_id, operated_at), + CONSTRAINT fk_weight_revision_inbound FOREIGN KEY (inbound_id) REFERENCES fl_inbound_order (id), + CONSTRAINT fk_weight_revision_result FOREIGN KEY (weight_result_id) REFERENCES fl_inbound_weight_result (id) +) ENGINE=InnoDB COMMENT='OCR重量人工修正和确认的不可变版本历史'; + + +-- ============================================================================ +-- Firstleg 审核工作流索引补齐 +-- 来源:firstleg/src/main/resources/db/phase2-inbound-workflow-migration.sql +-- ============================================================================ +-- 业务块4增量迁移:既有二期库补齐异常放行关口查询索引。 +-- 可重复执行;新库仍先执行 phase2-inbound-schema.sql。 +USE baiyue_firstleg; +SET NAMES utf8mb4; + +SET @ddl = IF( + EXISTS(SELECT 1 FROM information_schema.statistics + WHERE table_schema = DATABASE() AND table_name = 'fl_measure_review_record' + AND index_name = 'idx_review_exception_release'), + 'SELECT 1', + 'ALTER TABLE fl_measure_review_record ADD INDEX idx_review_exception_release (tenant_id, inbound_id, exception_release, reviewed_at)' +); +PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt; + + +-- ============================================================================ +-- Firstleg 第三方测量调用日志 +-- 来源:firstleg/src/main/resources/db/phase2-measurement-openapi.sql +-- ============================================================================ +SET NAMES utf8mb4; +USE baiyue_firstleg; + +-- 既有二期数据库增量:先确保 phase2-inbound-schema.sql 中的测量和幂等表已存在。 +CREATE TABLE IF NOT EXISTS fl_third_party_call_log ( + id BIGINT UNSIGNED NOT NULL COMMENT '第三方调用日志ID(雪花ID)', + tenant_id BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '识别出的租户ID,未知应用为0', + app_key VARCHAR(128) NULL COMMENT '第三方应用标识', + signature_subject VARCHAR(128) NULL COMMENT '签名主体快照', + request_method VARCHAR(8) NOT NULL COMMENT 'HTTP方法', + request_path VARCHAR(255) NOT NULL COMMENT '签名使用的规范路径', + request_timestamp VARCHAR(32) NULL COMMENT '请求头Unix毫秒时间戳原值', + nonce VARCHAR(128) NULL COMMENT '请求Nonce', + idempotency_key VARCHAR(128) NULL COMMENT 'HTTP幂等键,查询接口为空', + idempotency_anchor TINYINT UNSIGNED NULL COMMENT '签名通过后首个请求为1,重复调用为空', + signature_value VARCHAR(128) NULL COMMENT '第三方签名原值', + request_hash CHAR(64) NOT NULL COMMENT '原始请求体SHA-256小写十六进制', + raw_payload LONGTEXT NOT NULL COMMENT '未经重新序列化的UTF-8原始请求体', + processing_status VARCHAR(16) NOT NULL COMMENT 'PROCESSING、ACCEPTED或REJECTED', + result_code VARCHAR(32) NULL COMMENT '统一响应业务码', + result_message VARCHAR(1000) NULL COMMENT '处理结果或失败原因', + result_json JSON NULL COMMENT '首次成功结果快照', + requested_at DATETIME(3) NOT NULL COMMENT '服务端接收时间', + processed_at DATETIME(3) NULL COMMENT '处理完成时间', + PRIMARY KEY (id), + KEY idx_third_call_app_time (tenant_id, app_key, requested_at), + KEY idx_third_call_idempotency (tenant_id, app_key, idempotency_key), + KEY idx_third_call_nonce (tenant_id, app_key, nonce), + UNIQUE KEY uk_third_call_idempotency_owner (tenant_id, app_key, idempotency_key, idempotency_anchor) +) ENGINE=InnoDB COMMENT='第三方测量开放接口原始请求、签名主体和处理结果留档'; + + +-- ============================================================================ +-- Firstleg FBA 计费字段与索引补齐 +-- 来源:firstleg/src/main/resources/db/phase2-fba-pricing-migration.sql +-- ============================================================================ +SET NAMES utf8mb4; +USE baiyue_firstleg; + +-- 二期 FBA 计费增量迁移。通过 information_schema 判断,允许重复部署。 +DROP PROCEDURE IF EXISTS phase2_fba_add_column; +DELIMITER $$ +CREATE PROCEDURE phase2_fba_add_column(IN p_table VARCHAR(64), IN p_column VARCHAR(64), IN p_definition TEXT) +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = p_table AND COLUMN_NAME = p_column + ) THEN + SET @sql = CONCAT('ALTER TABLE `', p_table, '` ADD COLUMN `', p_column, '` ', p_definition); + PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; + END IF; +END$$ +DELIMITER ; + +CALL phase2_fba_add_column('fl_inbound_order', 'carrier_id', "BIGINT UNSIGNED NULL COMMENT '计费船司逻辑ID' AFTER channel_name"); +CALL phase2_fba_add_column('fl_inbound_order', 'carrier_name', "VARCHAR(128) NULL COMMENT '计费船司名称快照' AFTER carrier_id"); +CALL phase2_fba_add_column('fl_carrier_fba_rate', 'business_type', "VARCHAR(32) NOT NULL DEFAULT 'FBA' COMMENT '业务或计费类型' AFTER origin_warehouse_name"); +CALL phase2_fba_add_column('fl_customer_charge_rule', 'version', "INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号' AFTER deleted"); +CALL phase2_fba_add_column('fl_carrier_fba_rate', 'version', "INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号' AFTER deleted"); +CALL phase2_fba_add_column('fl_inbound_fee', 'rule_code', "VARCHAR(50) NOT NULL DEFAULT '' COMMENT '客户规则编码快照' AFTER carrier_rate_version"); +CALL phase2_fba_add_column('fl_inbound_fee', 'customer_id', "BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '客户ID快照' AFTER rule_code"); +CALL phase2_fba_add_column('fl_inbound_fee', 'customer_name', "VARCHAR(128) NOT NULL DEFAULT '' COMMENT '客户名称快照' AFTER customer_id"); +CALL phase2_fba_add_column('fl_inbound_fee', 'business_type', "VARCHAR(32) NOT NULL DEFAULT '' COMMENT '业务类型快照' AFTER customer_name"); +CALL phase2_fba_add_column('fl_inbound_fee', 'tax_mode', "VARCHAR(24) NULL COMMENT '包税模式快照' AFTER business_type"); +CALL phase2_fba_add_column('fl_inbound_fee', 'rounding_scale', "TINYINT UNSIGNED NOT NULL DEFAULT 3 COMMENT '计费量保留位数快照' AFTER tax_mode"); +CALL phase2_fba_add_column('fl_inbound_fee', 'rounding_mode', "VARCHAR(24) NOT NULL DEFAULT 'CEILING' COMMENT '舍入方式快照' AFTER rounding_scale"); +CALL phase2_fba_add_column('fl_inbound_fee', 'rule_effective_from', "DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '客户规则生效开始快照' AFTER rounding_mode"); +CALL phase2_fba_add_column('fl_inbound_fee', 'rule_effective_to', "DATETIME(3) NULL COMMENT '客户规则生效结束快照' AFTER rule_effective_from"); +CALL phase2_fba_add_column('fl_inbound_fee', 'rate_code', "VARCHAR(50) NOT NULL DEFAULT '' COMMENT '船司价格编码快照' AFTER rule_effective_to"); +CALL phase2_fba_add_column('fl_inbound_fee', 'origin_warehouse_id', "BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '起运仓ID快照' AFTER rate_code"); +CALL phase2_fba_add_column('fl_inbound_fee', 'origin_warehouse_name', "VARCHAR(128) NOT NULL DEFAULT '' COMMENT '起运仓名称快照' AFTER origin_warehouse_id"); +CALL phase2_fba_add_column('fl_inbound_fee', 'channel_code', "VARCHAR(64) NOT NULL DEFAULT '' COMMENT '渠道编码快照' AFTER origin_warehouse_name"); +CALL phase2_fba_add_column('fl_inbound_fee', 'carrier_id', "BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '船司ID快照' AFTER channel_code"); +CALL phase2_fba_add_column('fl_inbound_fee', 'carrier_name', "VARCHAR(128) NOT NULL DEFAULT '' COMMENT '船司名称快照' AFTER carrier_id"); +CALL phase2_fba_add_column('fl_inbound_fee', 'destination_code', "VARCHAR(32) NOT NULL DEFAULT '' COMMENT 'FBA仓编码快照' AFTER carrier_name"); +CALL phase2_fba_add_column('fl_inbound_fee', 'rate_effective_from', "DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '价格生效开始快照' AFTER destination_code"); +CALL phase2_fba_add_column('fl_inbound_fee', 'rate_effective_to', "DATETIME(3) NULL COMMENT '价格生效结束快照' AFTER rate_effective_from"); +CALL phase2_fba_add_column('fl_inbound_idempotency', 'result_resource_id', "BIGINT UNSIGNED NULL COMMENT '成功结果资源ID' AFTER result_json"); + +DROP PROCEDURE IF EXISTS phase2_fba_add_column; + +-- 旧索引可能缺少业务类型、船司和结束时间;用新索引名并存,避免破坏线上依赖。 +SET @idx_exists = (SELECT COUNT(*) FROM information_schema.STATISTICS + WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'fl_carrier_fba_rate' AND INDEX_NAME = 'idx_fba_rate_unique_match'); +SET @sql = IF(@idx_exists = 0, + 'ALTER TABLE fl_carrier_fba_rate ADD INDEX idx_fba_rate_unique_match (tenant_id, origin_warehouse_id, business_type, channel_code, carrier_id, destination_code, charge_unit, status, effective_from, effective_to)', + 'SELECT 1'); +PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; + + +-- ============================================================================ +-- Warehouse 二期托盘库位最终基线表 +-- 来源:warehouse/src/main/resources/db/phase2-pallet-location-schema.sql +-- ============================================================================ +-- 二期托盘、库位、预占与占用表结构。 +-- 本脚本只新增表、字段、索引和约束,不包含业务数据及流程实现。 +-- 可在 warehouse.sql 和一期增量脚本之后执行;所有建表均可重复执行。 +SET NAMES utf8mb4; + +CREATE DATABASE IF NOT EXISTS baiyue_warehouse + DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci; +USE baiyue_warehouse; + +CREATE TABLE IF NOT EXISTS wh_pallet ( + id BIGINT UNSIGNED NOT NULL COMMENT '托盘ID(雪花ID)', + tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', + warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '所属仓库ID', + pallet_code VARCHAR(50) NOT NULL COMMENT '租户仓库内唯一托盘编码', + pallet_type VARCHAR(32) NOT NULL COMMENT '托盘类型', + pallet_name VARCHAR(100) NULL COMMENT '托盘名称', + capacity_cbm DECIMAL(18,6) NOT NULL COMMENT '托盘可用容积CBM', + status VARCHAR(20) NOT NULL DEFAULT 'AVAILABLE' COMMENT '内部状态:AVAILABLE、RESERVED或OCCUPIED', + remark VARCHAR(500) NULL COMMENT '备注', + created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + updated_by BIGINT UNSIGNED NOT 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_tenant_warehouse_code (tenant_id, warehouse_id, pallet_code), + KEY idx_pallet_available (tenant_id, warehouse_id, status, deleted), + KEY idx_pallet_type (tenant_id, warehouse_id, pallet_type, status, deleted), + CONSTRAINT fk_pallet_warehouse FOREIGN KEY (warehouse_id) REFERENCES wh_warehouse (id), + CONSTRAINT ck_pallet_capacity CHECK (capacity_cbm > 0) +) ENGINE=InnoDB COMMENT='二期托盘基础资料和物理托盘实例'; + +CREATE TABLE IF NOT EXISTS wh_location ( + id BIGINT UNSIGNED NOT NULL COMMENT '库位ID(雪花ID)', + tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', + warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '所属仓库ID', + zone_code VARCHAR(50) NULL COMMENT '库区编码', + zone_name VARCHAR(100) NULL COMMENT '库区名称', + location_code VARCHAR(50) NOT NULL COMMENT '租户仓库内唯一库位编码', + location_name VARCHAR(100) NULL COMMENT '库位名称', + location_type VARCHAR(32) NOT NULL DEFAULT 'PALLET' COMMENT '库位类型,二期为PALLET', + max_weight_kg DECIMAL(18,3) NULL COMMENT '最大承重展示值,二期不作容量拦截', + shared_overflow TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否为系统共享溢出位A0', + status VARCHAR(20) NOT NULL DEFAULT 'AVAILABLE' COMMENT 'AVAILABLE、RESERVED、OCCUPIED或DISABLED', + remark VARCHAR(500) NULL COMMENT '备注', + created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + updated_by BIGINT UNSIGNED NOT 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_tenant_warehouse_code (tenant_id, warehouse_id, location_code), + KEY idx_location_available (tenant_id, warehouse_id, status, deleted), + KEY idx_location_zone (tenant_id, warehouse_id, zone_code, status, deleted), + CONSTRAINT fk_location_warehouse FOREIGN KEY (warehouse_id) REFERENCES wh_warehouse (id), + CONSTRAINT ck_location_max_weight CHECK (max_weight_kg IS NULL OR max_weight_kg > 0) +) ENGINE=InnoDB COMMENT='二期库位基础资料,默认一托盘对应一个库位且不作容量校验'; + +CREATE TABLE IF NOT EXISTS wh_location_reservation ( + id BIGINT UNSIGNED NOT NULL COMMENT '库位预占ID', + tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', + reservation_no VARCHAR(40) NOT NULL COMMENT '租户内唯一预占业务号', + idempotency_key VARCHAR(128) NOT NULL COMMENT '调用方幂等键', + source_service VARCHAR(40) NOT NULL DEFAULT 'FIRSTLEG' COMMENT '来源服务', + business_id BIGINT UNSIGNED NOT NULL COMMENT '来源服务入库单ID', + inbound_code VARCHAR(40) NOT NULL COMMENT '入仓码快照', + warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '仓库ID', + pallet_id BIGINT UNSIGNED NOT NULL COMMENT '托盘ID', + pallet_code VARCHAR(50) NOT NULL COMMENT '托盘编码快照', + location_id BIGINT UNSIGNED NOT NULL COMMENT '库位ID', + location_code VARCHAR(50) NOT NULL COMMENT '库位编码快照', + shared_overflow TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否使用共享溢出位A0', + reservation_status VARCHAR(20) NOT NULL DEFAULT 'RESERVED' COMMENT 'RESERVED、CONFIRMED、RELEASED或EXPIRED', + expires_at DATETIME(3) NOT NULL COMMENT '预占过期时间', + confirmed_at DATETIME(3) NULL COMMENT '确认占用时间', + released_at DATETIME(3) NULL COMMENT '释放时间', + release_reason VARCHAR(500) NULL COMMENT '释放或过期原因', + created_by BIGINT UNSIGNED NOT 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 '更新时间', + version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', + active_reservation TINYINT GENERATED ALWAYS AS (CASE WHEN reservation_status = 'RESERVED' AND shared_overflow = 0 THEN 1 ELSE NULL END) STORED COMMENT '普通库位活动预占唯一标记', + active_pallet_reservation TINYINT GENERATED ALWAYS AS (CASE WHEN reservation_status = 'RESERVED' THEN 1 ELSE NULL END) STORED COMMENT '托盘活动预占唯一标记(包含A0)', + PRIMARY KEY (id), + UNIQUE KEY uk_location_reservation_no (tenant_id, reservation_no), + UNIQUE KEY uk_location_reservation_idempotency (tenant_id, source_service, idempotency_key), + UNIQUE KEY uk_location_active_reservation (tenant_id, location_id, active_reservation), + UNIQUE KEY uk_pallet_active_reservation (tenant_id, pallet_id, active_pallet_reservation), + KEY idx_location_reservation_business (tenant_id, source_service, business_id, reservation_status), + KEY idx_location_reservation_expire (reservation_status, expires_at), + KEY idx_location_reservation_target (tenant_id, warehouse_id, location_id, reservation_status), + CONSTRAINT fk_reservation_warehouse FOREIGN KEY (warehouse_id) REFERENCES wh_warehouse (id), + CONSTRAINT fk_reservation_pallet FOREIGN KEY (pallet_id) REFERENCES wh_pallet (id), + CONSTRAINT fk_reservation_location FOREIGN KEY (location_id) REFERENCES wh_location (id) +) ENGINE=InnoDB COMMENT='跨服务入库Saga的托盘和库位临时预占'; + +CREATE TABLE IF NOT EXISTS wh_location_occupancy ( + id BIGINT UNSIGNED NOT NULL COMMENT '库位占用ID', + tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID', + occupancy_no VARCHAR(40) NOT NULL COMMENT '租户内唯一占用业务号', + reservation_id BIGINT UNSIGNED NOT NULL COMMENT '来源预占ID', + source_service VARCHAR(40) NOT NULL DEFAULT 'FIRSTLEG' COMMENT '来源服务', + business_id BIGINT UNSIGNED NOT NULL COMMENT '来源服务入库单ID', + inbound_code VARCHAR(40) NOT NULL COMMENT '入仓码快照', + warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '仓库ID', + pallet_id BIGINT UNSIGNED NOT NULL COMMENT '托盘ID', + pallet_code VARCHAR(50) NOT NULL COMMENT '托盘编码快照', + location_id BIGINT UNSIGNED NOT NULL COMMENT '库位ID', + location_code VARCHAR(50) NOT NULL COMMENT '库位编码快照', + shared_overflow TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否使用共享溢出位A0', + allocated_volume_cbm DECIMAL(18,6) NOT NULL COMMENT '本托盘分配体积CBM', + allocated_package_count INT UNSIGNED NULL COMMENT '本托盘分配箱数', + occupancy_status VARCHAR(20) NOT NULL DEFAULT 'OCCUPIED' COMMENT 'OCCUPIED或RELEASED', + occupied_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '占用开始时间', + released_at DATETIME(3) NULL COMMENT '释放时间', + release_reason VARCHAR(500) NULL COMMENT '释放原因', + created_by BIGINT UNSIGNED NOT 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 '更新时间', + version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', + active_location_occupancy TINYINT GENERATED ALWAYS AS (CASE WHEN occupancy_status = 'OCCUPIED' AND shared_overflow = 0 THEN 1 ELSE NULL END) STORED COMMENT '普通库位活动占用唯一标记', + active_pallet_occupancy TINYINT GENERATED ALWAYS AS (CASE WHEN occupancy_status = 'OCCUPIED' THEN 1 ELSE NULL END) STORED COMMENT '托盘活动占用唯一标记(包含A0)', + PRIMARY KEY (id), + UNIQUE KEY uk_location_occupancy_no (tenant_id, occupancy_no), + UNIQUE KEY uk_occupancy_reservation (tenant_id, reservation_id), + UNIQUE KEY uk_location_active_occupancy (tenant_id, location_id, active_location_occupancy), + UNIQUE KEY uk_pallet_active_occupancy (tenant_id, pallet_id, active_pallet_occupancy), + KEY idx_occupancy_business (tenant_id, source_service, business_id, occupancy_status), + KEY idx_occupancy_warehouse (tenant_id, warehouse_id, occupancy_status, occupied_at), + CONSTRAINT fk_occupancy_reservation FOREIGN KEY (reservation_id) REFERENCES wh_location_reservation (id), + CONSTRAINT fk_occupancy_warehouse FOREIGN KEY (warehouse_id) REFERENCES wh_warehouse (id), + CONSTRAINT fk_occupancy_pallet FOREIGN KEY (pallet_id) REFERENCES wh_pallet (id), + CONSTRAINT fk_occupancy_location FOREIGN KEY (location_id) REFERENCES wh_location (id), + CONSTRAINT ck_occupancy_volume CHECK (allocated_volume_cbm > 0) +) ENGINE=InnoDB COMMENT='已确认的一托一库位占用事实记录'; + +-- 头程入库单属于其他数据库,不建立跨库外键。 + + +-- ============================================================================ +-- Warehouse 托盘活动预占唯一约束补齐 +-- 来源:warehouse/src/main/resources/db/phase2-pallet-location-schema-migration.sql +-- ============================================================================ +-- 二期托盘、库位表的可重复执行增量迁移。 +-- 执行顺序:phase2-pallet-location-schema.sql 之后。 +SET NAMES utf8mb4; +USE baiyue_warehouse; + +-- 活动预占必须同时保证“一托一位”;A0 共享只放宽库位,不放宽托盘唯一性。 +SET @schema_name = DATABASE(); +DROP PROCEDURE IF EXISTS add_pallet_reservation_column_if_missing; +DELIMITER $$ +CREATE PROCEDURE add_pallet_reservation_column_if_missing() +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = DATABASE() + AND table_name = 'wh_location_reservation' + AND column_name = 'active_pallet_reservation' + ) THEN + ALTER TABLE wh_location_reservation + ADD COLUMN active_pallet_reservation TINYINT + GENERATED ALWAYS AS (CASE WHEN reservation_status = 'RESERVED' THEN 1 ELSE NULL END) + STORED COMMENT '托盘活动预占唯一标记(包含A0)'; + END IF; +END$$ +DELIMITER ; +CALL add_pallet_reservation_column_if_missing(); +DROP PROCEDURE IF EXISTS add_pallet_reservation_column_if_missing; + +SET @sql = IF((SELECT COUNT(*) FROM information_schema.statistics + WHERE table_schema = @schema_name + AND table_name = 'wh_location_reservation' + AND index_name = 'uk_pallet_active_reservation') > 0, + 'ALTER TABLE wh_location_reservation DROP INDEX uk_pallet_active_reservation', + 'SELECT 1'); +PREPARE statement FROM @sql; +EXECUTE statement; +DEALLOCATE PREPARE statement; + +SET @sql = IF((SELECT COUNT(*) FROM information_schema.statistics + WHERE table_schema = @schema_name + AND table_name = 'wh_location_reservation' + AND index_name = 'uk_pallet_active_reservation') = 0, + 'ALTER TABLE wh_location_reservation ADD UNIQUE KEY uk_pallet_active_reservation (tenant_id, pallet_id, active_pallet_reservation)', + 'SELECT 1'); +PREPARE statement FROM @sql; +EXECUTE statement; +DEALLOCATE PREPARE statement; + + +-- ============================================================================ +-- Warehouse A0 共享溢出位与最终并发约束 +-- 来源:warehouse/src/main/resources/db/phase2-location-overflow-migration.sql +-- ============================================================================ +-- 二期库位共享溢出位 A0 增量迁移。 +-- 执行顺序:phase2-pallet-location-schema.sql、本文件。 +-- 普通库位仍保持一托一位;同一租户仓库只有一个 A0,且 A0 可承载多个活动资源。 +SET NAMES utf8mb4; +USE baiyue_warehouse; + +DROP PROCEDURE IF EXISTS add_location_overflow_column_if_missing; +DELIMITER $$ +CREATE PROCEDURE add_location_overflow_column_if_missing( + IN p_table_name VARCHAR(64), + IN p_column_name VARCHAR(64), + IN p_column_definition TEXT +) +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = DATABASE() + AND table_name = p_table_name + AND column_name = p_column_name + ) THEN + SET @column_sql = CONCAT( + 'ALTER TABLE `', REPLACE(p_table_name, '`', '``'), + '` ADD COLUMN `', REPLACE(p_column_name, '`', '``'), '` ', p_column_definition + ); + PREPARE column_statement FROM @column_sql; + EXECUTE column_statement; + DEALLOCATE PREPARE column_statement; + END IF; +END$$ +DELIMITER ; + +CALL add_location_overflow_column_if_missing( + 'wh_location', 'shared_overflow', + 'TINYINT(1) NOT NULL DEFAULT 0 COMMENT ''是否为系统共享溢出位 A0'' AFTER max_weight_kg' +); +CALL add_location_overflow_column_if_missing( + 'wh_location_reservation', 'shared_overflow', + 'TINYINT(1) NOT NULL DEFAULT 0 COMMENT ''是否使用共享溢出位 A0'' AFTER location_code' +); +CALL add_location_overflow_column_if_missing( + 'wh_location_occupancy', 'shared_overflow', + 'TINYINT(1) NOT NULL DEFAULT 0 COMMENT ''是否使用共享溢出位 A0'' AFTER location_code' +); + +-- 兼容迁移前人工或旧流程已创建的 A0,并将资源快照回填一致。 +UPDATE wh_location + SET shared_overflow = 1, + status = 'AVAILABLE' + WHERE location_code = 'A0' AND deleted = 0; + +UPDATE wh_location_reservation r +JOIN wh_location l + ON l.tenant_id = r.tenant_id AND l.id = r.location_id + SET r.shared_overflow = l.shared_overflow; + +UPDATE wh_location_occupancy o +JOIN wh_location l + ON l.tenant_id = o.tenant_id AND l.id = o.location_id + SET o.shared_overflow = l.shared_overflow; + +DROP PROCEDURE IF EXISTS add_location_overflow_column_if_missing; + +-- 先移除依赖旧生成列的唯一索引,再按普通库位/A0 的不同语义重建。 +SET @schema_name = DATABASE(); +SET @sql = IF((SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = @schema_name AND table_name = 'wh_location_reservation' AND index_name = 'uk_location_active_reservation') > 0, + 'ALTER TABLE wh_location_reservation DROP INDEX uk_location_active_reservation', 'SELECT 1'); +PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement; +SET @sql = IF((SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = @schema_name AND table_name = 'wh_location_reservation' AND index_name = 'uk_pallet_active_reservation') > 0, + 'ALTER TABLE wh_location_reservation DROP INDEX uk_pallet_active_reservation', 'SELECT 1'); +PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement; + +ALTER TABLE wh_location_reservation + MODIFY COLUMN active_reservation TINYINT + GENERATED ALWAYS AS ( + CASE WHEN reservation_status = 'RESERVED' AND shared_overflow = 0 THEN 1 ELSE NULL END + ) STORED COMMENT '普通库位活动预占唯一标记,A0 返回 NULL'; + +SET @sql = IF((SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = @schema_name AND table_name = 'wh_location_reservation' AND index_name = 'uk_location_active_reservation') = 0, + 'ALTER TABLE wh_location_reservation ADD UNIQUE KEY uk_location_active_reservation (tenant_id, location_id, active_reservation)', 'SELECT 1'); +PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement; + +SET @sql = IF((SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = @schema_name AND table_name = 'wh_location_occupancy' AND index_name = 'uk_location_active_occupancy') > 0, + 'ALTER TABLE wh_location_occupancy DROP INDEX uk_location_active_occupancy', 'SELECT 1'); +PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement; +SET @sql = IF((SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = @schema_name AND table_name = 'wh_location_occupancy' AND index_name = 'uk_pallet_active_occupancy') > 0, + 'ALTER TABLE wh_location_occupancy DROP INDEX uk_pallet_active_occupancy', 'SELECT 1'); +PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement; + +DROP PROCEDURE IF EXISTS add_location_overflow_column_if_missing; +DELIMITER $$ +CREATE PROCEDURE add_location_overflow_column_if_missing( + IN p_table_name VARCHAR(64), + IN p_column_name VARCHAR(64), + IN p_column_definition TEXT +) +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = DATABASE() + AND table_name = p_table_name + AND column_name = p_column_name + ) THEN + SET @column_sql = CONCAT( + 'ALTER TABLE `', REPLACE(p_table_name, '`', '``'), + '` ADD COLUMN `', REPLACE(p_column_name, '`', '``'), '` ', p_column_definition + ); + PREPARE column_statement FROM @column_sql; + EXECUTE column_statement; + DEALLOCATE PREPARE column_statement; + END IF; +END$$ +DELIMITER ; + +CALL add_location_overflow_column_if_missing( + 'wh_location_reservation', 'active_pallet_reservation', + 'TINYINT GENERATED ALWAYS AS (CASE WHEN reservation_status = ''RESERVED'' THEN 1 ELSE NULL END) STORED COMMENT ''托盘活动预占唯一标记(包含 A0)''' +); +CALL add_location_overflow_column_if_missing( + 'wh_location_occupancy', 'active_location_occupancy', + 'TINYINT GENERATED ALWAYS AS (CASE WHEN occupancy_status = ''OCCUPIED'' AND shared_overflow = 0 THEN 1 ELSE NULL END) STORED COMMENT ''普通库位活动占用唯一标记''' +); +CALL add_location_overflow_column_if_missing( + 'wh_location_occupancy', 'active_pallet_occupancy', + 'TINYINT GENERATED ALWAYS AS (CASE WHEN occupancy_status = ''OCCUPIED'' THEN 1 ELSE NULL END) STORED COMMENT ''托盘活动占用唯一标记(包含 A0)''' +); +DROP PROCEDURE IF EXISTS add_location_overflow_column_if_missing; + +SET @sql = IF((SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = @schema_name AND table_name = 'wh_location_reservation' AND index_name = 'uk_pallet_active_reservation') = 0, + 'ALTER TABLE wh_location_reservation ADD UNIQUE KEY uk_pallet_active_reservation (tenant_id, pallet_id, active_pallet_reservation)', 'SELECT 1'); +PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement; + +SET @sql = IF((SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = @schema_name AND table_name = 'wh_location_occupancy' AND index_name = 'uk_location_active_occupancy') = 0, + 'ALTER TABLE wh_location_occupancy ADD UNIQUE KEY uk_location_active_occupancy (tenant_id, location_id, active_location_occupancy)', 'SELECT 1'); +PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement; +SET @sql = IF((SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = @schema_name AND table_name = 'wh_location_occupancy' AND index_name = 'uk_pallet_active_occupancy') = 0, + 'ALTER TABLE wh_location_occupancy ADD UNIQUE KEY uk_pallet_active_occupancy (tenant_id, pallet_id, active_pallet_occupancy)', 'SELECT 1'); +PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement; + + +-- 结构核对:只读输出,不写业务数据。 +SELECT 'firstleg 二期核心表数量' AS check_item, + COUNT(*) AS actual_value, + IF(COUNT(*) = 21, 'PASS', 'FAIL') AS check_result +FROM information_schema.tables +WHERE table_schema = 'baiyue_firstleg' + AND table_name IN ( + 'fl_inbound_order', 'fl_inbound_pickup_snapshot', 'fl_inbound_destination_snapshot', + 'fl_inbound_transport_snapshot', 'fl_inbound_measurement', 'fl_inbound_measurement_item', + 'fl_inbound_weight_result', 'fl_inbound_weight_revision', 'fl_measure_review_record', + 'fl_customer_charge_rule', 'fl_carrier_fba_rate', 'fl_inbound_fee', 'fl_inbound_fee_item', + 'fl_inbound_pallet_allocation', 'fl_inbound_file', 'fl_inbound_hold_record', + 'fl_inbound_internal_note', 'fl_inbound_status_history', 'fl_inbound_outbox', + 'fl_inbound_idempotency', 'fl_third_party_call_log' + ) +UNION ALL +SELECT 'warehouse 二期核心表数量', + COUNT(*), + IF(COUNT(*) = 4, 'PASS', 'FAIL') +FROM information_schema.tables +WHERE table_schema = 'baiyue_warehouse' + AND table_name IN ('wh_pallet', 'wh_location', 'wh_location_reservation', 'wh_location_occupancy') +UNION ALL +SELECT 'file 图片依赖表数量', + COUNT(*), + IF(COUNT(*) = 3, 'PASS', 'FAIL') +FROM information_schema.tables +WHERE table_schema = 'baiyue_file' + AND table_name IN ('file_object', 'file_download_token', 'file_download_log'); + +SELECT 'A0 共享溢出与活动占用关键列' AS check_item, + COUNT(*) AS actual_value, + IF(COUNT(*) = 6, 'PASS', 'FAIL') AS check_result +FROM information_schema.columns +WHERE (table_schema = 'baiyue_warehouse' AND table_name = 'wh_location' AND column_name = 'shared_overflow') + OR (table_schema = 'baiyue_warehouse' AND table_name = 'wh_location_reservation' + AND column_name IN ('shared_overflow', 'active_reservation', 'active_pallet_reservation')) + OR (table_schema = 'baiyue_warehouse' AND table_name = 'wh_location_occupancy' + AND column_name IN ('shared_overflow', 'active_location_occupancy', 'active_pallet_occupancy')); + +SELECT 'A0 共享溢出与活动占用关键唯一索引' AS check_item, + COUNT(DISTINCT CONCAT(table_schema, '.', table_name, '.', index_name)) AS actual_value, + IF(COUNT(DISTINCT CONCAT(table_schema, '.', table_name, '.', index_name)) = 4, 'PASS', 'FAIL') AS check_result +FROM information_schema.statistics +WHERE table_schema = 'baiyue_warehouse' + AND ( + (table_name = 'wh_location_reservation' AND index_name IN ('uk_location_active_reservation', 'uk_pallet_active_reservation')) + OR (table_name = 'wh_location_occupancy' AND index_name IN ('uk_location_active_occupancy', 'uk_pallet_active_occupancy')) + ); + +SELECT '二期入库新增表与字段脚本执行完成,请确认以上检查全部为 PASS' AS execution_message;