diff --git a/shierfeng-fastapi-backend/module_business/domain/enums.py b/shierfeng-fastapi-backend/module_business/domain/enums.py index c061d8c..42de322 100644 --- a/shierfeng-fastapi-backend/module_business/domain/enums.py +++ b/shierfeng-fastapi-backend/module_business/domain/enums.py @@ -28,3 +28,14 @@ class PalletEventType(str, Enum): USAGE_RECORDED = 'USAGE_RECORDED' RETURN_REQUESTED = 'RETURN_REQUESTED' RETURN_CONFIRMED = 'RETURN_CONFIRMED' + + +class TestBatchStatus(str, Enum): + ACTIVE = 'ACTIVE' + CLOSED = 'CLOSED' + PURGED = 'PURGED' + + +class TestBatchPreviewAction(str, Enum): + ADOPT = 'ADOPT' + PURGE = 'PURGE' diff --git a/shierfeng-fastapi-backend/module_business/entity/do/master_do.py b/shierfeng-fastapi-backend/module_business/entity/do/master_do.py index ac6a350..6352827 100644 --- a/shierfeng-fastapi-backend/module_business/entity/do/master_do.py +++ b/shierfeng-fastapi-backend/module_business/entity/do/master_do.py @@ -27,6 +27,13 @@ class BizStrain(Base): ) strain_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='菌种ID') + test_batch_id = Column( + BigInteger, + ForeignKey('biz_test_batch.batch_id'), + nullable=True, + index=True, + comment='测试数据批次ID;NULL表示正式数据', + ) strain_code = Column(String(64), nullable=False, unique=True, comment='菌种编码') strain_name = Column(String(100), nullable=False, comment='菌种名称') status = Column(CHAR(1), nullable=False, server_default='0', comment='状态(0正常 1停用)') @@ -52,6 +59,13 @@ class BizWarehouse(Base): ) warehouse_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='仓库ID') + test_batch_id = Column( + BigInteger, + ForeignKey('biz_test_batch.batch_id'), + nullable=True, + index=True, + comment='测试数据批次ID;NULL表示正式数据', + ) warehouse_code = Column(String(64), nullable=False, unique=True, comment='仓库编码') warehouse_name = Column(String(100), nullable=False, comment='仓库名称') address = Column(String(500), nullable=True, comment='仓库地址') @@ -78,6 +92,13 @@ class BizCustomer(Base): ) customer_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='客户ID') + test_batch_id = Column( + BigInteger, + ForeignKey('biz_test_batch.batch_id'), + nullable=True, + index=True, + comment='测试数据批次ID;NULL表示正式数据', + ) customer_code = Column(String(64), nullable=False, unique=True, comment='客户编码') customer_name = Column(String(100), nullable=False, comment='客户名称') status = Column(CHAR(1), nullable=False, server_default='0', comment='状态(0正常 1停用)') @@ -104,6 +125,13 @@ class BizCustomerSite(Base): ) site_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='现场ID') + test_batch_id = Column( + BigInteger, + ForeignKey('biz_test_batch.batch_id'), + nullable=True, + index=True, + comment='测试数据批次ID;NULL表示正式数据', + ) customer_id = Column( BigInteger, ForeignKey('biz_customer.customer_id'), @@ -137,6 +165,13 @@ class BizProductionLine(Base): ) line_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='产线ID') + test_batch_id = Column( + BigInteger, + ForeignKey('biz_test_batch.batch_id'), + nullable=True, + index=True, + comment='测试数据批次ID;NULL表示正式数据', + ) site_id = Column( BigInteger, ForeignKey('biz_customer_site.site_id'), @@ -169,6 +204,13 @@ class BizPallet(Base): ) pallet_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='托盘ID') + test_batch_id = Column( + BigInteger, + ForeignKey('biz_test_batch.batch_id'), + nullable=True, + index=True, + comment='测试数据批次ID;NULL表示正式数据', + ) pallet_code = Column(String(64), nullable=False, unique=True, comment='托盘编号') qr_token = Column(String(128), nullable=False, unique=True, comment='二维码固定标识') capacity_packages = Column(Integer, nullable=False, comment='容量包数') diff --git a/shierfeng-fastapi-backend/module_business/entity/do/operation_do.py b/shierfeng-fastapi-backend/module_business/entity/do/operation_do.py index 2c173cd..5fb1309 100644 --- a/shierfeng-fastapi-backend/module_business/entity/do/operation_do.py +++ b/shierfeng-fastapi-backend/module_business/entity/do/operation_do.py @@ -22,6 +22,13 @@ class BizInboundOrder(Base): ) inbound_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='入库单ID') + test_batch_id = Column( + BigInteger, + ForeignKey('biz_test_batch.batch_id'), + nullable=True, + index=True, + comment='测试数据批次ID;NULL表示正式数据', + ) inbound_no = Column(String(64), nullable=False, unique=True, comment='入库单号') warehouse_id = Column( BigInteger, @@ -62,6 +69,13 @@ class BizInboundItem(Base): autoincrement=True, comment='入库明细ID', ) + test_batch_id = Column( + BigInteger, + ForeignKey('biz_test_batch.batch_id'), + nullable=True, + index=True, + comment='测试数据批次ID;NULL表示正式数据', + ) inbound_id = Column( BigInteger, ForeignKey('biz_inbound_order.inbound_id'), @@ -93,6 +107,13 @@ class BizFillingRecord(Base): ) filling_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='装填记录ID') + test_batch_id = Column( + BigInteger, + ForeignKey('biz_test_batch.batch_id'), + nullable=True, + index=True, + comment='测试数据批次ID;NULL表示正式数据', + ) filling_no = Column(String(64), nullable=False, unique=True, comment='装填单号') pallet_id = Column(BigInteger, ForeignKey('biz_pallet.pallet_id'), nullable=False, comment='托盘ID') warehouse_id = Column( @@ -141,6 +162,13 @@ class BizShipmentOrder(Base): ) shipment_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='发货单ID') + test_batch_id = Column( + BigInteger, + ForeignKey('biz_test_batch.batch_id'), + nullable=True, + index=True, + comment='测试数据批次ID;NULL表示正式数据', + ) shipment_no = Column(String(64), nullable=False, unique=True, comment='发货单号') source_warehouse_id = Column( BigInteger, @@ -191,6 +219,13 @@ class BizShipmentItem(Base): autoincrement=True, comment='发货明细ID', ) + test_batch_id = Column( + BigInteger, + ForeignKey('biz_test_batch.batch_id'), + nullable=True, + index=True, + comment='测试数据批次ID;NULL表示正式数据', + ) shipment_id = Column( BigInteger, ForeignKey('biz_shipment_order.shipment_id'), @@ -224,6 +259,13 @@ class BizUsageRecord(Base): ) usage_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='使用记录ID') + test_batch_id = Column( + BigInteger, + ForeignKey('biz_test_batch.batch_id'), + nullable=True, + index=True, + comment='测试数据批次ID;NULL表示正式数据', + ) usage_no = Column(String(64), nullable=False, unique=True, comment='使用记录号') pallet_id = Column(BigInteger, ForeignKey('biz_pallet.pallet_id'), nullable=False, comment='托盘ID') active_filling_id = Column( @@ -264,6 +306,13 @@ class BizReturnOrder(Base): ) return_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='退回单ID') + test_batch_id = Column( + BigInteger, + ForeignKey('biz_test_batch.batch_id'), + nullable=True, + index=True, + comment='测试数据批次ID;NULL表示正式数据', + ) return_no = Column(String(64), nullable=False, unique=True, comment='退回单号') source_site_id = Column( BigInteger, @@ -318,6 +367,13 @@ class BizReturnItem(Base): autoincrement=True, comment='退回明细ID', ) + test_batch_id = Column( + BigInteger, + ForeignKey('biz_test_batch.batch_id'), + nullable=True, + index=True, + comment='测试数据批次ID;NULL表示正式数据', + ) return_id = Column( BigInteger, ForeignKey('biz_return_order.return_id'), @@ -363,6 +419,13 @@ class BizPalletInventory(Base): autoincrement=False, comment='托盘ID', ) + test_batch_id = Column( + BigInteger, + ForeignKey('biz_test_batch.batch_id'), + nullable=True, + index=True, + comment='测试数据批次ID;NULL表示正式数据', + ) active_filling_id = Column( BigInteger, ForeignKey('biz_filling_record.filling_id'), @@ -415,6 +478,13 @@ class BizPalletEvent(Base): ) event_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='事件ID') + test_batch_id = Column( + BigInteger, + ForeignKey('biz_test_batch.batch_id'), + nullable=True, + index=True, + comment='测试数据批次ID;NULL表示正式数据', + ) pallet_id = Column(BigInteger, ForeignKey('biz_pallet.pallet_id'), nullable=False, comment='托盘ID') event_seq = Column(Integer, nullable=False, comment='托盘内事件序号') event_type = Column(String(32), nullable=False, comment='事件类型') diff --git a/shierfeng-fastapi-backend/module_business/entity/do/test_batch_do.py b/shierfeng-fastapi-backend/module_business/entity/do/test_batch_do.py new file mode 100644 index 0000000..0978a4b --- /dev/null +++ b/shierfeng-fastapi-backend/module_business/entity/do/test_batch_do.py @@ -0,0 +1,49 @@ +from datetime import datetime + +from sqlalchemy import CHAR, BigInteger, CheckConstraint, Column, DateTime, ForeignKey, String, Text + +from config.database import Base + + +class BizTestBatch(Base): + """测试数据批次。""" + + __tablename__ = 'biz_test_batch' + __table_args__ = ( + CheckConstraint("status IN ('ACTIVE', 'CLOSED', 'PURGED')", name='ck_biz_test_batch_status'), + CheckConstraint( + "preview_action IS NULL OR preview_action IN ('ADOPT', 'PURGE')", + name='ck_biz_test_batch_preview_action', + ), + {'comment': '测试数据批次'}, + ) + + batch_id = Column(BigInteger, primary_key=True, autoincrement=True) + batch_code = Column(String(64), nullable=False, unique=True) + batch_name = Column(String(100), nullable=False) + status = Column(String(16), nullable=False, server_default='ACTIVE') + create_by = Column(String(64), nullable=False, server_default='') + create_time = Column(DateTime, nullable=False, default=datetime.now) + update_by = Column(String(64), nullable=False, server_default='') + update_time = Column(DateTime, nullable=False, default=datetime.now) + closed_by = Column(BigInteger, ForeignKey('sys_user.user_id'), nullable=True) + closed_at = Column(DateTime, nullable=True) + purged_by = Column(BigInteger, ForeignKey('sys_user.user_id'), nullable=True) + purged_at = Column(DateTime, nullable=True) + purge_summary = Column(Text, nullable=True) + preview_action = Column(String(16), nullable=True) + preview_token_hash = Column(CHAR(64), nullable=True) + preview_scope_hash = Column(CHAR(64), nullable=True) + preview_expires_at = Column(DateTime, nullable=True) + remark = Column(String(500), nullable=True) + + +class BizTestBatchUser(Base): + """测试数据批次与用户关联。""" + + __tablename__ = 'biz_test_batch_user' + + batch_id = Column(BigInteger, ForeignKey('biz_test_batch.batch_id'), primary_key=True) + user_id = Column(BigInteger, ForeignKey('sys_user.user_id'), primary_key=True) + create_by = Column(String(64), nullable=False, server_default='') + create_time = Column(DateTime, nullable=False, default=datetime.now) diff --git a/shierfeng-fastapi-backend/module_business/entity/vo/test_batch_vo.py b/shierfeng-fastapi-backend/module_business/entity/vo/test_batch_vo.py new file mode 100644 index 0000000..1595907 --- /dev/null +++ b/shierfeng-fastapi-backend/module_business/entity/vo/test_batch_vo.py @@ -0,0 +1,116 @@ +from datetime import datetime +from typing import Annotated, Literal + +from pydantic import BaseModel, ConfigDict, Field +from pydantic.alias_generators import to_camel + +from module_business.domain.enums import TestBatchStatus + +PositiveId = Annotated[int, Field(gt=0)] + + +class TestBatchApiModel(BaseModel): + model_config = ConfigDict( + alias_generator=to_camel, + from_attributes=True, + populate_by_name=True, + extra='forbid', + ) + + +class TestBatchCreateModel(TestBatchApiModel): + batch_name: str = Field(min_length=1, max_length=100) + remark: str | None = Field(default=None, max_length=500) + + +class TestBatchEditModel(TestBatchApiModel): + remark: str | None = Field(default=None, max_length=500) + + +class TestBatchUserAssignModel(TestBatchApiModel): + user_ids: list[PositiveId] + + +class TestBatchModel(TestBatchApiModel): + batch_id: PositiveId + batch_code: str + batch_name: str + status: TestBatchStatus + remark: str | None = None + create_by: str + create_time: datetime + update_by: str + update_time: datetime + closed_by: int | None = None + closed_at: datetime | None = None + purged_by: int | None = None + purged_at: datetime | None = None + purge_summary: dict[str, int] | None = None + + +class TestBatchOptionModel(TestBatchApiModel): + batch_id: PositiveId + batch_code: str + batch_name: str + status: TestBatchStatus + + +class BatchImpactModel(TestBatchApiModel): + table_name: str + label: str + count: int = Field(ge=0) + keys: list[str] + + +class TestBatchDetailModel(TestBatchModel): + user_ids: list[int] + data_summary: list[BatchImpactModel] + + +class TestBatchPreviewModel(TestBatchApiModel): + batch_id: PositiveId + batch_name: str + preview_token: str + expires_at: datetime + total_count: int = Field(ge=0) + impacts: list[BatchImpactModel] + + +class PurgePreviewModel(TestBatchPreviewModel): + online_purge_allowed: bool + online_purge_limit: int = Field(ge=0) + blocking_reason: str | None = None + + +AdoptionRootType = Literal[ + 'strain', + 'customer', + 'site', + 'line', + 'warehouse', + 'pallet', + 'inbound', + 'filling', + 'shipment', + 'usage', + 'return', +] + + +class AdoptionRootModel(TestBatchApiModel): + resource_type: AdoptionRootType + resource_ids: list[PositiveId] = Field(min_length=1) + + +class AdoptionPreviewRequestModel(TestBatchApiModel): + roots: list[AdoptionRootModel] = Field(min_length=1) + + +class AdoptionExecuteRequestModel(AdoptionPreviewRequestModel): + batch_name: str = Field(min_length=1, max_length=100) + preview_token: str = Field(min_length=32, max_length=128) + + +class PurgeExecuteRequestModel(TestBatchApiModel): + batch_name: str = Field(min_length=1, max_length=100) + preview_token: str = Field(min_length=32, max_length=128) diff --git a/shierfeng-fastapi-backend/sql/patches/20260922_test_data_batch_mysql.sql b/shierfeng-fastapi-backend/sql/patches/20260922_test_data_batch_mysql.sql new file mode 100644 index 0000000..6cc0184 --- /dev/null +++ b/shierfeng-fastapi-backend/sql/patches/20260922_test_data_batch_mysql.sql @@ -0,0 +1,137 @@ +-- 测试数据批次隔离:MySQL 增量补丁 +-- 本补丁仅增加可空归属字段,不回填既有数据。 + +CREATE TABLE biz_test_batch ( + batch_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '测试数据批次ID', + batch_code VARCHAR(64) NOT NULL COMMENT '批次编码', + batch_name VARCHAR(100) NOT NULL COMMENT '批次名称', + status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE' COMMENT '批次状态', + create_by VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建者', + create_time DATETIME NOT NULL COMMENT '创建时间', + update_by VARCHAR(64) NOT NULL DEFAULT '' COMMENT '更新者', + update_time DATETIME NOT NULL COMMENT '更新时间', + closed_by BIGINT NULL COMMENT '关闭人ID', + closed_at DATETIME NULL COMMENT '关闭时间', + purged_by BIGINT NULL COMMENT '清理人ID', + purged_at DATETIME NULL COMMENT '清理时间', + purge_summary TEXT NULL COMMENT '清理摘要JSON', + preview_action VARCHAR(16) NULL COMMENT '预览动作', + preview_token_hash CHAR(64) NULL COMMENT '预览令牌摘要', + preview_scope_hash CHAR(64) NULL COMMENT '预览范围摘要', + preview_expires_at DATETIME NULL COMMENT '预览过期时间', + remark VARCHAR(500) NULL COMMENT '备注', + PRIMARY KEY (batch_id), + CONSTRAINT uq_biz_test_batch_code UNIQUE (batch_code), + CONSTRAINT ck_biz_test_batch_status CHECK (status IN ('ACTIVE', 'CLOSED', 'PURGED')), + CONSTRAINT ck_biz_test_batch_preview_action CHECK ( + preview_action IS NULL OR preview_action IN ('ADOPT', 'PURGE') + ), + CONSTRAINT fk_biz_test_batch_closed_by FOREIGN KEY (closed_by) REFERENCES sys_user (user_id), + CONSTRAINT fk_biz_test_batch_purged_by FOREIGN KEY (purged_by) REFERENCES sys_user (user_id) +) ENGINE=InnoDB COMMENT='测试数据批次'; + +CREATE TABLE biz_test_batch_user ( + batch_id BIGINT NOT NULL COMMENT '测试数据批次ID', + user_id BIGINT NOT NULL COMMENT '用户ID', + create_by VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建者', + create_time DATETIME NOT NULL COMMENT '创建时间', + PRIMARY KEY (batch_id, user_id), + CONSTRAINT fk_biz_test_batch_user_batch FOREIGN KEY (batch_id) REFERENCES biz_test_batch (batch_id), + CONSTRAINT fk_biz_test_batch_user_user FOREIGN KEY (user_id) REFERENCES sys_user (user_id) +) ENGINE=InnoDB COMMENT='测试数据批次与用户关联'; + +ALTER TABLE biz_strain + ADD COLUMN test_batch_id BIGINT NULL COMMENT '测试数据批次ID;NULL表示正式数据', + ADD INDEX idx_biz_strain_test_batch_id (test_batch_id), + ADD CONSTRAINT fk_biz_strain_test_batch_id + FOREIGN KEY (test_batch_id) REFERENCES biz_test_batch (batch_id); + +ALTER TABLE biz_customer + ADD COLUMN test_batch_id BIGINT NULL COMMENT '测试数据批次ID;NULL表示正式数据', + ADD INDEX idx_biz_customer_test_batch_id (test_batch_id), + ADD CONSTRAINT fk_biz_customer_test_batch_id + FOREIGN KEY (test_batch_id) REFERENCES biz_test_batch (batch_id); + +ALTER TABLE biz_customer_site + ADD COLUMN test_batch_id BIGINT NULL COMMENT '测试数据批次ID;NULL表示正式数据', + ADD INDEX idx_biz_customer_site_test_batch_id (test_batch_id), + ADD CONSTRAINT fk_biz_customer_site_test_batch_id + FOREIGN KEY (test_batch_id) REFERENCES biz_test_batch (batch_id); + +ALTER TABLE biz_production_line + ADD COLUMN test_batch_id BIGINT NULL COMMENT '测试数据批次ID;NULL表示正式数据', + ADD INDEX idx_biz_production_line_test_batch_id (test_batch_id), + ADD CONSTRAINT fk_biz_production_line_test_batch_id + FOREIGN KEY (test_batch_id) REFERENCES biz_test_batch (batch_id); + +ALTER TABLE biz_warehouse + ADD COLUMN test_batch_id BIGINT NULL COMMENT '测试数据批次ID;NULL表示正式数据', + ADD INDEX idx_biz_warehouse_test_batch_id (test_batch_id), + ADD CONSTRAINT fk_biz_warehouse_test_batch_id + FOREIGN KEY (test_batch_id) REFERENCES biz_test_batch (batch_id); + +ALTER TABLE biz_pallet + ADD COLUMN test_batch_id BIGINT NULL COMMENT '测试数据批次ID;NULL表示正式数据', + ADD INDEX idx_biz_pallet_test_batch_id (test_batch_id), + ADD CONSTRAINT fk_biz_pallet_test_batch_id + FOREIGN KEY (test_batch_id) REFERENCES biz_test_batch (batch_id); + +ALTER TABLE biz_inbound_order + ADD COLUMN test_batch_id BIGINT NULL COMMENT '测试数据批次ID;NULL表示正式数据', + ADD INDEX idx_biz_inbound_order_test_batch_id (test_batch_id), + ADD CONSTRAINT fk_biz_inbound_order_test_batch_id + FOREIGN KEY (test_batch_id) REFERENCES biz_test_batch (batch_id); + +ALTER TABLE biz_inbound_item + ADD COLUMN test_batch_id BIGINT NULL COMMENT '测试数据批次ID;NULL表示正式数据', + ADD INDEX idx_biz_inbound_item_test_batch_id (test_batch_id), + ADD CONSTRAINT fk_biz_inbound_item_test_batch_id + FOREIGN KEY (test_batch_id) REFERENCES biz_test_batch (batch_id); + +ALTER TABLE biz_filling_record + ADD COLUMN test_batch_id BIGINT NULL COMMENT '测试数据批次ID;NULL表示正式数据', + ADD INDEX idx_biz_filling_record_test_batch_id (test_batch_id), + ADD CONSTRAINT fk_biz_filling_record_test_batch_id + FOREIGN KEY (test_batch_id) REFERENCES biz_test_batch (batch_id); + +ALTER TABLE biz_shipment_order + ADD COLUMN test_batch_id BIGINT NULL COMMENT '测试数据批次ID;NULL表示正式数据', + ADD INDEX idx_biz_shipment_order_test_batch_id (test_batch_id), + ADD CONSTRAINT fk_biz_shipment_order_test_batch_id + FOREIGN KEY (test_batch_id) REFERENCES biz_test_batch (batch_id); + +ALTER TABLE biz_shipment_item + ADD COLUMN test_batch_id BIGINT NULL COMMENT '测试数据批次ID;NULL表示正式数据', + ADD INDEX idx_biz_shipment_item_test_batch_id (test_batch_id), + ADD CONSTRAINT fk_biz_shipment_item_test_batch_id + FOREIGN KEY (test_batch_id) REFERENCES biz_test_batch (batch_id); + +ALTER TABLE biz_usage_record + ADD COLUMN test_batch_id BIGINT NULL COMMENT '测试数据批次ID;NULL表示正式数据', + ADD INDEX idx_biz_usage_record_test_batch_id (test_batch_id), + ADD CONSTRAINT fk_biz_usage_record_test_batch_id + FOREIGN KEY (test_batch_id) REFERENCES biz_test_batch (batch_id); + +ALTER TABLE biz_return_order + ADD COLUMN test_batch_id BIGINT NULL COMMENT '测试数据批次ID;NULL表示正式数据', + ADD INDEX idx_biz_return_order_test_batch_id (test_batch_id), + ADD CONSTRAINT fk_biz_return_order_test_batch_id + FOREIGN KEY (test_batch_id) REFERENCES biz_test_batch (batch_id); + +ALTER TABLE biz_return_item + ADD COLUMN test_batch_id BIGINT NULL COMMENT '测试数据批次ID;NULL表示正式数据', + ADD INDEX idx_biz_return_item_test_batch_id (test_batch_id), + ADD CONSTRAINT fk_biz_return_item_test_batch_id + FOREIGN KEY (test_batch_id) REFERENCES biz_test_batch (batch_id); + +ALTER TABLE biz_pallet_inventory + ADD COLUMN test_batch_id BIGINT NULL COMMENT '测试数据批次ID;NULL表示正式数据', + ADD INDEX idx_biz_pallet_inventory_test_batch_id (test_batch_id), + ADD CONSTRAINT fk_biz_pallet_inventory_test_batch_id + FOREIGN KEY (test_batch_id) REFERENCES biz_test_batch (batch_id); + +ALTER TABLE biz_pallet_event + ADD COLUMN test_batch_id BIGINT NULL COMMENT '测试数据批次ID;NULL表示正式数据', + ADD INDEX idx_biz_pallet_event_test_batch_id (test_batch_id), + ADD CONSTRAINT fk_biz_pallet_event_test_batch_id + FOREIGN KEY (test_batch_id) REFERENCES biz_test_batch (batch_id); diff --git a/shierfeng-fastapi-backend/sql/patches/20260922_test_data_batch_pg.sql b/shierfeng-fastapi-backend/sql/patches/20260922_test_data_batch_pg.sql new file mode 100644 index 0000000..36ae09b --- /dev/null +++ b/shierfeng-fastapi-backend/sql/patches/20260922_test_data_batch_pg.sql @@ -0,0 +1,139 @@ +-- 测试数据批次隔离:PostgreSQL 增量补丁 +-- 本补丁仅增加可空归属字段,不回填既有数据。 + +CREATE TABLE biz_test_batch ( + batch_id BIGSERIAL NOT NULL, + batch_code VARCHAR(64) NOT NULL, + batch_name VARCHAR(100) NOT NULL, + status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE', + create_by VARCHAR(64) NOT NULL DEFAULT '', + create_time TIMESTAMP WITHOUT TIME ZONE NOT NULL, + update_by VARCHAR(64) NOT NULL DEFAULT '', + update_time TIMESTAMP WITHOUT TIME ZONE NOT NULL, + closed_by BIGINT NULL, + closed_at TIMESTAMP WITHOUT TIME ZONE NULL, + purged_by BIGINT NULL, + purged_at TIMESTAMP WITHOUT TIME ZONE NULL, + purge_summary TEXT NULL, + preview_action VARCHAR(16) NULL, + preview_token_hash CHAR(64) NULL, + preview_scope_hash CHAR(64) NULL, + preview_expires_at TIMESTAMP WITHOUT TIME ZONE NULL, + remark VARCHAR(500) NULL, + PRIMARY KEY (batch_id), + CONSTRAINT uq_biz_test_batch_code UNIQUE (batch_code), + CONSTRAINT ck_biz_test_batch_status CHECK (status IN ('ACTIVE', 'CLOSED', 'PURGED')), + CONSTRAINT ck_biz_test_batch_preview_action CHECK ( + preview_action IS NULL OR preview_action IN ('ADOPT', 'PURGE') + ), + CONSTRAINT fk_biz_test_batch_closed_by FOREIGN KEY (closed_by) REFERENCES sys_user (user_id), + CONSTRAINT fk_biz_test_batch_purged_by FOREIGN KEY (purged_by) REFERENCES sys_user (user_id) +); +COMMENT ON TABLE biz_test_batch IS '测试数据批次'; + +CREATE TABLE biz_test_batch_user ( + batch_id BIGINT NOT NULL, + user_id BIGINT NOT NULL, + create_by VARCHAR(64) NOT NULL DEFAULT '', + create_time TIMESTAMP WITHOUT TIME ZONE NOT NULL, + PRIMARY KEY (batch_id, user_id), + CONSTRAINT fk_biz_test_batch_user_batch FOREIGN KEY (batch_id) REFERENCES biz_test_batch (batch_id), + CONSTRAINT fk_biz_test_batch_user_user FOREIGN KEY (user_id) REFERENCES sys_user (user_id) +); +COMMENT ON TABLE biz_test_batch_user IS '测试数据批次与用户关联'; + +ALTER TABLE biz_strain ADD COLUMN test_batch_id BIGINT NULL; +COMMENT ON COLUMN biz_strain.test_batch_id IS '测试数据批次ID;NULL表示正式数据'; +CREATE INDEX idx_biz_strain_test_batch_id ON biz_strain (test_batch_id); +ALTER TABLE biz_strain ADD CONSTRAINT fk_biz_strain_test_batch_id + FOREIGN KEY (test_batch_id) REFERENCES biz_test_batch (batch_id); + +ALTER TABLE biz_customer ADD COLUMN test_batch_id BIGINT NULL; +COMMENT ON COLUMN biz_customer.test_batch_id IS '测试数据批次ID;NULL表示正式数据'; +CREATE INDEX idx_biz_customer_test_batch_id ON biz_customer (test_batch_id); +ALTER TABLE biz_customer ADD CONSTRAINT fk_biz_customer_test_batch_id + FOREIGN KEY (test_batch_id) REFERENCES biz_test_batch (batch_id); + +ALTER TABLE biz_customer_site ADD COLUMN test_batch_id BIGINT NULL; +COMMENT ON COLUMN biz_customer_site.test_batch_id IS '测试数据批次ID;NULL表示正式数据'; +CREATE INDEX idx_biz_customer_site_test_batch_id ON biz_customer_site (test_batch_id); +ALTER TABLE biz_customer_site ADD CONSTRAINT fk_biz_customer_site_test_batch_id + FOREIGN KEY (test_batch_id) REFERENCES biz_test_batch (batch_id); + +ALTER TABLE biz_production_line ADD COLUMN test_batch_id BIGINT NULL; +COMMENT ON COLUMN biz_production_line.test_batch_id IS '测试数据批次ID;NULL表示正式数据'; +CREATE INDEX idx_biz_production_line_test_batch_id ON biz_production_line (test_batch_id); +ALTER TABLE biz_production_line ADD CONSTRAINT fk_biz_production_line_test_batch_id + FOREIGN KEY (test_batch_id) REFERENCES biz_test_batch (batch_id); + +ALTER TABLE biz_warehouse ADD COLUMN test_batch_id BIGINT NULL; +COMMENT ON COLUMN biz_warehouse.test_batch_id IS '测试数据批次ID;NULL表示正式数据'; +CREATE INDEX idx_biz_warehouse_test_batch_id ON biz_warehouse (test_batch_id); +ALTER TABLE biz_warehouse ADD CONSTRAINT fk_biz_warehouse_test_batch_id + FOREIGN KEY (test_batch_id) REFERENCES biz_test_batch (batch_id); + +ALTER TABLE biz_pallet ADD COLUMN test_batch_id BIGINT NULL; +COMMENT ON COLUMN biz_pallet.test_batch_id IS '测试数据批次ID;NULL表示正式数据'; +CREATE INDEX idx_biz_pallet_test_batch_id ON biz_pallet (test_batch_id); +ALTER TABLE biz_pallet ADD CONSTRAINT fk_biz_pallet_test_batch_id + FOREIGN KEY (test_batch_id) REFERENCES biz_test_batch (batch_id); + +ALTER TABLE biz_inbound_order ADD COLUMN test_batch_id BIGINT NULL; +COMMENT ON COLUMN biz_inbound_order.test_batch_id IS '测试数据批次ID;NULL表示正式数据'; +CREATE INDEX idx_biz_inbound_order_test_batch_id ON biz_inbound_order (test_batch_id); +ALTER TABLE biz_inbound_order ADD CONSTRAINT fk_biz_inbound_order_test_batch_id + FOREIGN KEY (test_batch_id) REFERENCES biz_test_batch (batch_id); + +ALTER TABLE biz_inbound_item ADD COLUMN test_batch_id BIGINT NULL; +COMMENT ON COLUMN biz_inbound_item.test_batch_id IS '测试数据批次ID;NULL表示正式数据'; +CREATE INDEX idx_biz_inbound_item_test_batch_id ON biz_inbound_item (test_batch_id); +ALTER TABLE biz_inbound_item ADD CONSTRAINT fk_biz_inbound_item_test_batch_id + FOREIGN KEY (test_batch_id) REFERENCES biz_test_batch (batch_id); + +ALTER TABLE biz_filling_record ADD COLUMN test_batch_id BIGINT NULL; +COMMENT ON COLUMN biz_filling_record.test_batch_id IS '测试数据批次ID;NULL表示正式数据'; +CREATE INDEX idx_biz_filling_record_test_batch_id ON biz_filling_record (test_batch_id); +ALTER TABLE biz_filling_record ADD CONSTRAINT fk_biz_filling_record_test_batch_id + FOREIGN KEY (test_batch_id) REFERENCES biz_test_batch (batch_id); + +ALTER TABLE biz_shipment_order ADD COLUMN test_batch_id BIGINT NULL; +COMMENT ON COLUMN biz_shipment_order.test_batch_id IS '测试数据批次ID;NULL表示正式数据'; +CREATE INDEX idx_biz_shipment_order_test_batch_id ON biz_shipment_order (test_batch_id); +ALTER TABLE biz_shipment_order ADD CONSTRAINT fk_biz_shipment_order_test_batch_id + FOREIGN KEY (test_batch_id) REFERENCES biz_test_batch (batch_id); + +ALTER TABLE biz_shipment_item ADD COLUMN test_batch_id BIGINT NULL; +COMMENT ON COLUMN biz_shipment_item.test_batch_id IS '测试数据批次ID;NULL表示正式数据'; +CREATE INDEX idx_biz_shipment_item_test_batch_id ON biz_shipment_item (test_batch_id); +ALTER TABLE biz_shipment_item ADD CONSTRAINT fk_biz_shipment_item_test_batch_id + FOREIGN KEY (test_batch_id) REFERENCES biz_test_batch (batch_id); + +ALTER TABLE biz_usage_record ADD COLUMN test_batch_id BIGINT NULL; +COMMENT ON COLUMN biz_usage_record.test_batch_id IS '测试数据批次ID;NULL表示正式数据'; +CREATE INDEX idx_biz_usage_record_test_batch_id ON biz_usage_record (test_batch_id); +ALTER TABLE biz_usage_record ADD CONSTRAINT fk_biz_usage_record_test_batch_id + FOREIGN KEY (test_batch_id) REFERENCES biz_test_batch (batch_id); + +ALTER TABLE biz_return_order ADD COLUMN test_batch_id BIGINT NULL; +COMMENT ON COLUMN biz_return_order.test_batch_id IS '测试数据批次ID;NULL表示正式数据'; +CREATE INDEX idx_biz_return_order_test_batch_id ON biz_return_order (test_batch_id); +ALTER TABLE biz_return_order ADD CONSTRAINT fk_biz_return_order_test_batch_id + FOREIGN KEY (test_batch_id) REFERENCES biz_test_batch (batch_id); + +ALTER TABLE biz_return_item ADD COLUMN test_batch_id BIGINT NULL; +COMMENT ON COLUMN biz_return_item.test_batch_id IS '测试数据批次ID;NULL表示正式数据'; +CREATE INDEX idx_biz_return_item_test_batch_id ON biz_return_item (test_batch_id); +ALTER TABLE biz_return_item ADD CONSTRAINT fk_biz_return_item_test_batch_id + FOREIGN KEY (test_batch_id) REFERENCES biz_test_batch (batch_id); + +ALTER TABLE biz_pallet_inventory ADD COLUMN test_batch_id BIGINT NULL; +COMMENT ON COLUMN biz_pallet_inventory.test_batch_id IS '测试数据批次ID;NULL表示正式数据'; +CREATE INDEX idx_biz_pallet_inventory_test_batch_id ON biz_pallet_inventory (test_batch_id); +ALTER TABLE biz_pallet_inventory ADD CONSTRAINT fk_biz_pallet_inventory_test_batch_id + FOREIGN KEY (test_batch_id) REFERENCES biz_test_batch (batch_id); + +ALTER TABLE biz_pallet_event ADD COLUMN test_batch_id BIGINT NULL; +COMMENT ON COLUMN biz_pallet_event.test_batch_id IS '测试数据批次ID;NULL表示正式数据'; +CREATE INDEX idx_biz_pallet_event_test_batch_id ON biz_pallet_event (test_batch_id); +ALTER TABLE biz_pallet_event ADD CONSTRAINT fk_biz_pallet_event_test_batch_id + FOREIGN KEY (test_batch_id) REFERENCES biz_test_batch (batch_id); diff --git a/shierfeng-fastapi-backend/sql/shierfeng-fastapi-pg.sql b/shierfeng-fastapi-backend/sql/shierfeng-fastapi-pg.sql index d728ab8..17afce4 100644 --- a/shierfeng-fastapi-backend/sql/shierfeng-fastapi-pg.sql +++ b/shierfeng-fastapi-backend/sql/shierfeng-fastapi-pg.sql @@ -19,6 +19,8 @@ drop table if exists biz_customer_site; drop table if exists biz_customer; drop table if exists biz_warehouse; drop table if exists biz_strain; +drop table if exists biz_test_batch_user; +drop table if exists biz_test_batch; -- ---------------------------- -- 1、部门表 @@ -1076,8 +1078,48 @@ $$ IMMUTABLE STRICT LANGUAGE PLPGSQL; -- ---------------------------- -- 20、托盘流转业务表 -- ---------------------------- +create table biz_test_batch ( + batch_id bigserial not null, + batch_code varchar(64) not null, + batch_name varchar(100) not null, + status varchar(16) not null default 'ACTIVE', + create_by varchar(64) not null default '', + create_time timestamp without time zone not null, + update_by varchar(64) not null default '', + update_time timestamp without time zone not null, + closed_by bigint, + closed_at timestamp without time zone, + purged_by bigint, + purged_at timestamp without time zone, + purge_summary text, + preview_action varchar(16), + preview_token_hash char(64), + preview_scope_hash char(64), + preview_expires_at timestamp without time zone, + remark varchar(500), + primary key (batch_id), + constraint uq_biz_test_batch_code unique (batch_code), + constraint ck_biz_test_batch_status check (status in ('ACTIVE', 'CLOSED', 'PURGED')), + constraint ck_biz_test_batch_preview_action check (preview_action is null or preview_action in ('ADOPT', 'PURGE')), + constraint fk_biz_test_batch_closed_by foreign key (closed_by) references sys_user(user_id), + constraint fk_biz_test_batch_purged_by foreign key (purged_by) references sys_user(user_id) +); +comment on table biz_test_batch is '测试数据批次'; + +create table biz_test_batch_user ( + batch_id bigint not null, + user_id bigint not null, + create_by varchar(64) not null default '', + create_time timestamp without time zone not null, + primary key (batch_id, user_id), + constraint fk_biz_test_batch_user_batch foreign key (batch_id) references biz_test_batch(batch_id), + constraint fk_biz_test_batch_user_user foreign key (user_id) references sys_user(user_id) +); +comment on table biz_test_batch_user is '测试数据批次与用户关联'; + create table biz_strain ( strain_id bigserial not null, + test_batch_id bigint null, strain_code varchar(64) not null, strain_name varchar(100) not null, status char(1) not null default '0', @@ -1088,11 +1130,13 @@ create table biz_strain ( remark varchar(500) default null, primary key (strain_id), constraint uq_biz_strain_code unique (strain_code), + constraint fk_biz_strain_test_batch_id foreign key (test_batch_id) references biz_test_batch(batch_id), constraint ck_biz_strain_status check (status in ('0', '1')) ); create table biz_warehouse ( warehouse_id bigserial not null, + test_batch_id bigint null, warehouse_code varchar(64) not null, warehouse_name varchar(100) not null, address varchar(500), @@ -1104,11 +1148,13 @@ create table biz_warehouse ( remark varchar(500) default null, primary key (warehouse_id), constraint uq_biz_warehouse_code unique (warehouse_code), + constraint fk_biz_warehouse_test_batch_id foreign key (test_batch_id) references biz_test_batch(batch_id), constraint ck_biz_warehouse_status check (status in ('0', '1')) ); create table biz_customer ( customer_id bigserial not null, + test_batch_id bigint null, customer_code varchar(64) not null, customer_name varchar(100) not null, status char(1) not null default '0', @@ -1119,11 +1165,13 @@ create table biz_customer ( remark varchar(500) default null, primary key (customer_id), constraint uq_biz_customer_code unique (customer_code), + constraint fk_biz_customer_test_batch_id foreign key (test_batch_id) references biz_test_batch(batch_id), constraint ck_biz_customer_status check (status in ('0', '1')) ); create table biz_customer_site ( site_id bigserial not null, + test_batch_id bigint null, customer_id bigint not null, site_code varchar(64) not null, site_name varchar(100) not null, @@ -1136,12 +1184,14 @@ create table biz_customer_site ( remark varchar(500) default null, primary key (site_id), constraint uq_biz_customer_site_customer_code unique (customer_id, site_code), + constraint fk_biz_customer_site_test_batch_id foreign key (test_batch_id) references biz_test_batch(batch_id), constraint ck_biz_customer_site_status check (status in ('0', '1')), constraint fk_biz_customer_site_customer foreign key (customer_id) references biz_customer(customer_id) ); create table biz_production_line ( line_id bigserial not null, + test_batch_id bigint null, site_id bigint not null, line_code varchar(64) not null, line_name varchar(100) not null, @@ -1153,12 +1203,14 @@ create table biz_production_line ( remark varchar(500) default null, primary key (line_id), constraint uq_biz_production_line_site_code unique (site_id, line_code), + constraint fk_biz_production_line_test_batch_id foreign key (test_batch_id) references biz_test_batch(batch_id), constraint ck_biz_production_line_status check (status in ('0', '1')), constraint fk_biz_production_line_site foreign key (site_id) references biz_customer_site(site_id) ); create table biz_pallet ( pallet_id bigserial not null, + test_batch_id bigint null, pallet_code varchar(64) not null, qr_token varchar(128) not null, capacity_packages integer not null, @@ -1171,6 +1223,7 @@ create table biz_pallet ( primary key (pallet_id), constraint uq_biz_pallet_code unique (pallet_code), constraint uq_biz_pallet_qr_token unique (qr_token), + constraint fk_biz_pallet_test_batch_id foreign key (test_batch_id) references biz_test_batch(batch_id), constraint ck_biz_pallet_capacity_packages check (capacity_packages > 0), constraint ck_biz_pallet_asset_status check (asset_status in ('0', '1')) ); @@ -1197,6 +1250,7 @@ create table biz_user_site ( create table biz_inbound_order ( inbound_id bigserial not null, + test_batch_id bigint null, inbound_no varchar(64) not null, warehouse_id bigint not null, status varchar(32) not null default 'DRAFT', @@ -1213,12 +1267,14 @@ create table biz_inbound_order ( constraint uq_biz_inbound_order_no unique (inbound_no), constraint uq_biz_inbound_order_warehouse_confirm_idempotency unique (warehouse_id, confirm_idempotency_key), constraint ck_biz_inbound_order_status check (status in ('DRAFT', 'CONFIRMED')), + constraint fk_biz_inbound_order_test_batch_id foreign key (test_batch_id) references biz_test_batch(batch_id), constraint fk_biz_inbound_order_warehouse foreign key (warehouse_id) references biz_warehouse(warehouse_id), constraint fk_biz_inbound_order_confirmer foreign key (confirmed_by) references sys_user(user_id) ); create table biz_inbound_item ( inbound_item_id bigserial not null, + test_batch_id bigint null, inbound_id bigint not null, pallet_id bigint not null, create_by varchar(64) default '', @@ -1226,11 +1282,13 @@ create table biz_inbound_item ( primary key (inbound_item_id), constraint uq_biz_inbound_item_order_pallet unique (inbound_id, pallet_id), constraint fk_biz_inbound_item_order foreign key (inbound_id) references biz_inbound_order(inbound_id), - constraint fk_biz_inbound_item_pallet foreign key (pallet_id) references biz_pallet(pallet_id) + constraint fk_biz_inbound_item_pallet foreign key (pallet_id) references biz_pallet(pallet_id), + constraint fk_biz_inbound_item_test_batch_id foreign key (test_batch_id) references biz_test_batch(batch_id) ); create table biz_filling_record ( filling_id bigserial not null, + test_batch_id bigint null, filling_no varchar(64) not null, pallet_id bigint not null, warehouse_id bigint not null, @@ -1260,11 +1318,13 @@ create table biz_filling_record ( constraint fk_biz_filling_record_pallet foreign key (pallet_id) references biz_pallet(pallet_id), constraint fk_biz_filling_record_warehouse foreign key (warehouse_id) references biz_warehouse(warehouse_id), constraint fk_biz_filling_record_strain foreign key (strain_id) references biz_strain(strain_id), - constraint fk_biz_filling_record_operator foreign key (operator_id) references sys_user(user_id) + constraint fk_biz_filling_record_operator foreign key (operator_id) references sys_user(user_id), + constraint fk_biz_filling_record_test_batch_id foreign key (test_batch_id) references biz_test_batch(batch_id) ); create table biz_shipment_order ( shipment_id bigserial not null, + test_batch_id bigint null, shipment_no varchar(64) not null, source_warehouse_id bigint not null, target_site_id bigint not null, @@ -1284,11 +1344,13 @@ create table biz_shipment_order ( constraint ck_biz_shipment_order_status check (status in ('DRAFT', 'CONFIRMED')), constraint fk_biz_shipment_order_warehouse foreign key (source_warehouse_id) references biz_warehouse(warehouse_id), constraint fk_biz_shipment_order_site foreign key (target_site_id) references biz_customer_site(site_id), - constraint fk_biz_shipment_order_confirmer foreign key (confirmed_by) references sys_user(user_id) + constraint fk_biz_shipment_order_confirmer foreign key (confirmed_by) references sys_user(user_id), + constraint fk_biz_shipment_order_test_batch_id foreign key (test_batch_id) references biz_test_batch(batch_id) ); create table biz_shipment_item ( shipment_item_id bigserial not null, + test_batch_id bigint null, shipment_id bigint not null, pallet_id bigint not null, active_filling_id bigint, @@ -1300,11 +1362,13 @@ create table biz_shipment_item ( constraint ck_biz_shipment_item_package_qty check (package_qty is null or package_qty > 0), constraint fk_biz_shipment_item_order foreign key (shipment_id) references biz_shipment_order(shipment_id), constraint fk_biz_shipment_item_pallet foreign key (pallet_id) references biz_pallet(pallet_id), - constraint fk_biz_shipment_item_filling foreign key (active_filling_id) references biz_filling_record(filling_id) + constraint fk_biz_shipment_item_filling foreign key (active_filling_id) references biz_filling_record(filling_id), + constraint fk_biz_shipment_item_test_batch_id foreign key (test_batch_id) references biz_test_batch(batch_id) ); create table biz_usage_record ( usage_id bigserial not null, + test_batch_id bigint null, usage_no varchar(64) not null, pallet_id bigint not null, active_filling_id bigint not null, @@ -1325,11 +1389,13 @@ create table biz_usage_record ( constraint fk_biz_usage_record_filling foreign key (active_filling_id) references biz_filling_record(filling_id), constraint fk_biz_usage_record_site foreign key (site_id) references biz_customer_site(site_id), constraint fk_biz_usage_record_line foreign key (line_id) references biz_production_line(line_id), - constraint fk_biz_usage_record_operator foreign key (operator_id) references sys_user(user_id) + constraint fk_biz_usage_record_operator foreign key (operator_id) references sys_user(user_id), + constraint fk_biz_usage_record_test_batch_id foreign key (test_batch_id) references biz_test_batch(batch_id) ); create table biz_return_order ( return_id bigserial not null, + test_batch_id bigint null, return_no varchar(64) not null, source_site_id bigint not null, target_warehouse_id bigint not null, @@ -1355,11 +1421,13 @@ create table biz_return_order ( constraint fk_biz_return_order_site foreign key (source_site_id) references biz_customer_site(site_id), constraint fk_biz_return_order_warehouse foreign key (target_warehouse_id) references biz_warehouse(warehouse_id), constraint fk_biz_return_order_requester foreign key (requested_by) references sys_user(user_id), - constraint fk_biz_return_order_confirmer foreign key (confirmed_by) references sys_user(user_id) + constraint fk_biz_return_order_confirmer foreign key (confirmed_by) references sys_user(user_id), + constraint fk_biz_return_order_test_batch_id foreign key (test_batch_id) references biz_test_batch(batch_id) ); create table biz_return_item ( return_item_id bigserial not null, + test_batch_id bigint null, return_id bigint not null, pallet_id bigint not null, active_filling_id_snapshot bigint, @@ -1371,11 +1439,13 @@ create table biz_return_item ( constraint ck_biz_return_item_remaining_packages_snapshot check (remaining_packages_snapshot >= 0), constraint fk_biz_return_item_order foreign key (return_id) references biz_return_order(return_id), constraint fk_biz_return_item_pallet foreign key (pallet_id) references biz_pallet(pallet_id), - constraint fk_biz_return_item_filling foreign key (active_filling_id_snapshot) references biz_filling_record(filling_id) + constraint fk_biz_return_item_filling foreign key (active_filling_id_snapshot) references biz_filling_record(filling_id), + constraint fk_biz_return_item_test_batch_id foreign key (test_batch_id) references biz_test_batch(batch_id) ); create table biz_pallet_event ( event_id bigserial not null, + test_batch_id bigint null, pallet_id bigint not null, event_seq integer not null, event_type varchar(32) not null, @@ -1411,11 +1481,13 @@ create table biz_pallet_event ( constraint fk_biz_pallet_event_after_warehouse foreign key (after_warehouse_id) references biz_warehouse(warehouse_id), constraint fk_biz_pallet_event_after_site foreign key (after_site_id) references biz_customer_site(site_id), constraint fk_biz_pallet_event_after_line foreign key (after_line_id) references biz_production_line(line_id), - constraint fk_biz_pallet_event_operator foreign key (operator_id) references sys_user(user_id) + constraint fk_biz_pallet_event_operator foreign key (operator_id) references sys_user(user_id), + constraint fk_biz_pallet_event_test_batch_id foreign key (test_batch_id) references biz_test_batch(batch_id) ); create table biz_pallet_inventory ( pallet_id bigint not null, + test_batch_id bigint null, active_filling_id bigint, warehouse_id bigint, site_id bigint, @@ -1442,9 +1514,43 @@ create table biz_pallet_inventory ( constraint fk_biz_pallet_inventory_warehouse foreign key (warehouse_id) references biz_warehouse(warehouse_id), constraint fk_biz_pallet_inventory_site foreign key (site_id) references biz_customer_site(site_id), constraint fk_biz_pallet_inventory_line foreign key (current_line_id) references biz_production_line(line_id), - constraint fk_biz_pallet_inventory_event foreign key (last_event_id) references biz_pallet_event(event_id) + constraint fk_biz_pallet_inventory_event foreign key (last_event_id) references biz_pallet_event(event_id), + constraint fk_biz_pallet_inventory_test_batch_id foreign key (test_batch_id) references biz_test_batch(batch_id) ); +comment on column biz_strain.test_batch_id is '测试数据批次ID;NULL表示正式数据'; +create index idx_biz_strain_test_batch_id on biz_strain (test_batch_id); +comment on column biz_customer.test_batch_id is '测试数据批次ID;NULL表示正式数据'; +create index idx_biz_customer_test_batch_id on biz_customer (test_batch_id); +comment on column biz_customer_site.test_batch_id is '测试数据批次ID;NULL表示正式数据'; +create index idx_biz_customer_site_test_batch_id on biz_customer_site (test_batch_id); +comment on column biz_production_line.test_batch_id is '测试数据批次ID;NULL表示正式数据'; +create index idx_biz_production_line_test_batch_id on biz_production_line (test_batch_id); +comment on column biz_warehouse.test_batch_id is '测试数据批次ID;NULL表示正式数据'; +create index idx_biz_warehouse_test_batch_id on biz_warehouse (test_batch_id); +comment on column biz_pallet.test_batch_id is '测试数据批次ID;NULL表示正式数据'; +create index idx_biz_pallet_test_batch_id on biz_pallet (test_batch_id); +comment on column biz_inbound_order.test_batch_id is '测试数据批次ID;NULL表示正式数据'; +create index idx_biz_inbound_order_test_batch_id on biz_inbound_order (test_batch_id); +comment on column biz_inbound_item.test_batch_id is '测试数据批次ID;NULL表示正式数据'; +create index idx_biz_inbound_item_test_batch_id on biz_inbound_item (test_batch_id); +comment on column biz_filling_record.test_batch_id is '测试数据批次ID;NULL表示正式数据'; +create index idx_biz_filling_record_test_batch_id on biz_filling_record (test_batch_id); +comment on column biz_shipment_order.test_batch_id is '测试数据批次ID;NULL表示正式数据'; +create index idx_biz_shipment_order_test_batch_id on biz_shipment_order (test_batch_id); +comment on column biz_shipment_item.test_batch_id is '测试数据批次ID;NULL表示正式数据'; +create index idx_biz_shipment_item_test_batch_id on biz_shipment_item (test_batch_id); +comment on column biz_usage_record.test_batch_id is '测试数据批次ID;NULL表示正式数据'; +create index idx_biz_usage_record_test_batch_id on biz_usage_record (test_batch_id); +comment on column biz_return_order.test_batch_id is '测试数据批次ID;NULL表示正式数据'; +create index idx_biz_return_order_test_batch_id on biz_return_order (test_batch_id); +comment on column biz_return_item.test_batch_id is '测试数据批次ID;NULL表示正式数据'; +create index idx_biz_return_item_test_batch_id on biz_return_item (test_batch_id); +comment on column biz_pallet_inventory.test_batch_id is '测试数据批次ID;NULL表示正式数据'; +create index idx_biz_pallet_inventory_test_batch_id on biz_pallet_inventory (test_batch_id); +comment on column biz_pallet_event.test_batch_id is '测试数据批次ID;NULL表示正式数据'; +create index idx_biz_pallet_event_test_batch_id on biz_pallet_event (test_batch_id); + -- ---------------------------- -- 初始化-托盘业务一级菜单和权限 -- ---------------------------- diff --git a/shierfeng-fastapi-backend/sql/shierfeng-fastapi.sql b/shierfeng-fastapi-backend/sql/shierfeng-fastapi.sql index 90e2a45..a2451d2 100644 --- a/shierfeng-fastapi-backend/sql/shierfeng-fastapi.sql +++ b/shierfeng-fastapi-backend/sql/shierfeng-fastapi.sql @@ -19,6 +19,8 @@ drop table if exists biz_customer_site; drop table if exists biz_customer; drop table if exists biz_warehouse; drop table if exists biz_strain; +drop table if exists biz_test_batch_user; +drop table if exists biz_test_batch; -- ---------------------------- -- 1、部门表 @@ -811,9 +813,49 @@ create table gen_table_column ( -- ---------------------------- -- 20、托盘业务表(按依赖创建) -- ---------------------------- +-- 测试数据批次 +create table biz_test_batch ( + batch_id bigint(20) not null auto_increment comment '测试数据批次ID', + batch_code varchar(64) not null comment '批次编码', + batch_name varchar(100) not null comment '批次名称', + status varchar(16) not null default 'ACTIVE' comment '批次状态', + create_by varchar(64) not null default '' comment '创建者', + create_time datetime not null comment '创建时间', + update_by varchar(64) not null default '' comment '更新者', + update_time datetime not null comment '更新时间', + closed_by bigint(20) comment '关闭人ID', + closed_at datetime comment '关闭时间', + purged_by bigint(20) comment '清理人ID', + purged_at datetime comment '清理时间', + purge_summary text comment '清理摘要JSON', + preview_action varchar(16) comment '预览动作', + preview_token_hash char(64) comment '预览令牌摘要', + preview_scope_hash char(64) comment '预览范围摘要', + preview_expires_at datetime comment '预览过期时间', + remark varchar(500) comment '备注', + primary key (batch_id), + constraint uq_biz_test_batch_code unique (batch_code), + constraint ck_biz_test_batch_status check (status in ('ACTIVE', 'CLOSED', 'PURGED')), + constraint ck_biz_test_batch_preview_action check (preview_action is null or preview_action in ('ADOPT', 'PURGE')), + constraint fk_biz_test_batch_closed_by foreign key (closed_by) references sys_user (user_id), + constraint fk_biz_test_batch_purged_by foreign key (purged_by) references sys_user (user_id) +) engine=innodb comment = '测试数据批次'; + +-- 测试数据批次与用户关联 +create table biz_test_batch_user ( + batch_id bigint(20) not null comment '测试数据批次ID', + user_id bigint(20) not null comment '用户ID', + create_by varchar(64) not null default '' comment '创建者', + create_time datetime not null comment '创建时间', + primary key (batch_id, user_id), + constraint fk_biz_test_batch_user_batch foreign key (batch_id) references biz_test_batch (batch_id), + constraint fk_biz_test_batch_user_user foreign key (user_id) references sys_user (user_id) +) engine=innodb comment = '测试数据批次与用户关联'; + -- 菌种主数据表 create table biz_strain ( strain_id bigint(20) not null auto_increment comment '菌种ID', + test_batch_id bigint(20) null comment '测试数据批次ID;NULL表示正式数据', strain_code varchar(64) not null comment '菌种编码', strain_name varchar(100) not null comment '菌种名称', status char(1) not null default '0' comment '状态(0正常 1停用)', @@ -823,13 +865,16 @@ create table biz_strain ( update_time datetime comment '更新时间', remark varchar(500) default null comment '备注', primary key (strain_id), + key idx_biz_strain_test_batch_id (test_batch_id), constraint uq_biz_strain_code unique (strain_code), + constraint fk_biz_strain_test_batch_id foreign key (test_batch_id) references biz_test_batch (batch_id), constraint ck_biz_strain_status check (status in ('0', '1')) ) engine=innodb comment = '菌种主数据表'; -- 仓库主数据表 create table biz_warehouse ( warehouse_id bigint(20) not null auto_increment comment '仓库ID', + test_batch_id bigint(20) null comment '测试数据批次ID;NULL表示正式数据', warehouse_code varchar(64) not null comment '仓库编码', warehouse_name varchar(100) not null comment '仓库名称', address varchar(500) comment '仓库地址', @@ -840,13 +885,16 @@ create table biz_warehouse ( update_time datetime comment '更新时间', remark varchar(500) default null comment '备注', primary key (warehouse_id), + key idx_biz_warehouse_test_batch_id (test_batch_id), constraint uq_biz_warehouse_code unique (warehouse_code), + constraint fk_biz_warehouse_test_batch_id foreign key (test_batch_id) references biz_test_batch (batch_id), constraint ck_biz_warehouse_status check (status in ('0', '1')) ) engine=innodb comment = '仓库主数据表'; -- 客户主数据表 create table biz_customer ( customer_id bigint(20) not null auto_increment comment '客户ID', + test_batch_id bigint(20) null comment '测试数据批次ID;NULL表示正式数据', customer_code varchar(64) not null comment '客户编码', customer_name varchar(100) not null comment '客户名称', status char(1) not null default '0' comment '状态(0正常 1停用)', @@ -856,13 +904,16 @@ create table biz_customer ( update_time datetime comment '更新时间', remark varchar(500) default null comment '备注', primary key (customer_id), + key idx_biz_customer_test_batch_id (test_batch_id), constraint uq_biz_customer_code unique (customer_code), + constraint fk_biz_customer_test_batch_id foreign key (test_batch_id) references biz_test_batch (batch_id), constraint ck_biz_customer_status check (status in ('0', '1')) ) engine=innodb comment = '客户主数据表'; -- 客户现场主数据表 create table biz_customer_site ( site_id bigint(20) not null auto_increment comment '现场ID', + test_batch_id bigint(20) null comment '测试数据批次ID;NULL表示正式数据', customer_id bigint(20) not null comment '客户ID', site_code varchar(64) not null comment '现场编码', site_name varchar(100) not null comment '现场名称', @@ -874,7 +925,9 @@ create table biz_customer_site ( update_time datetime comment '更新时间', remark varchar(500) default null comment '备注', primary key (site_id), + key idx_biz_customer_site_test_batch_id (test_batch_id), constraint uq_biz_customer_site_customer_code unique (customer_id, site_code), + constraint fk_biz_customer_site_test_batch_id foreign key (test_batch_id) references biz_test_batch (batch_id), constraint fk_biz_customer_site_customer foreign key (customer_id) references biz_customer (customer_id), constraint ck_biz_customer_site_status check (status in ('0', '1')) ) engine=innodb comment = '客户现场主数据表'; @@ -882,6 +935,7 @@ create table biz_customer_site ( -- 客户现场产线主数据表 create table biz_production_line ( line_id bigint(20) not null auto_increment comment '产线ID', + test_batch_id bigint(20) null comment '测试数据批次ID;NULL表示正式数据', site_id bigint(20) not null comment '现场ID', line_code varchar(64) not null comment '产线编码', line_name varchar(100) not null comment '产线名称', @@ -892,7 +946,9 @@ create table biz_production_line ( update_time datetime comment '更新时间', remark varchar(500) default null comment '备注', primary key (line_id), + key idx_biz_production_line_test_batch_id (test_batch_id), constraint uq_biz_production_line_site_code unique (site_id, line_code), + constraint fk_biz_production_line_test_batch_id foreign key (test_batch_id) references biz_test_batch (batch_id), constraint fk_biz_production_line_site foreign key (site_id) references biz_customer_site (site_id), constraint ck_biz_production_line_status check (status in ('0', '1')) ) engine=innodb comment = '客户现场产线主数据表'; @@ -900,6 +956,7 @@ create table biz_production_line ( -- 托盘资产主数据表 create table biz_pallet ( pallet_id bigint(20) not null auto_increment comment '托盘ID', + test_batch_id bigint(20) null comment '测试数据批次ID;NULL表示正式数据', pallet_code varchar(64) not null comment '托盘编号', qr_token varchar(128) not null comment '二维码固定标识', capacity_packages int not null comment '容量包数', @@ -910,8 +967,10 @@ create table biz_pallet ( update_time datetime comment '更新时间', remark varchar(500) default null comment '备注', primary key (pallet_id), + key idx_biz_pallet_test_batch_id (test_batch_id), constraint uq_biz_pallet_code unique (pallet_code), constraint uq_biz_pallet_qr_token unique (qr_token), + constraint fk_biz_pallet_test_batch_id foreign key (test_batch_id) references biz_test_batch (batch_id), constraint ck_biz_pallet_capacity_packages check (capacity_packages > 0), constraint ck_biz_pallet_asset_status check (asset_status in ('0', '1')) ) engine=innodb comment = '托盘资产主数据表'; @@ -941,6 +1000,7 @@ create table biz_user_site ( -- 首次入库单 create table biz_inbound_order ( inbound_id bigint(20) not null auto_increment comment '入库单ID', + test_batch_id bigint(20) null comment '测试数据批次ID;NULL表示正式数据', inbound_no varchar(64) not null comment '入库单号', warehouse_id bigint(20) not null comment '仓库ID', status varchar(32) not null default 'DRAFT' comment '单据状态', @@ -954,29 +1014,35 @@ create table biz_inbound_order ( update_time datetime comment '更新时间', remark varchar(500) default null comment '备注', primary key (inbound_id), + key idx_biz_inbound_order_test_batch_id (test_batch_id), constraint uq_biz_inbound_order_no unique (inbound_no), constraint uq_biz_inbound_order_warehouse_confirm_idempotency unique (warehouse_id, confirm_idempotency_key), constraint fk_biz_inbound_order_warehouse foreign key (warehouse_id) references biz_warehouse (warehouse_id), constraint fk_biz_inbound_order_confirmed_by foreign key (confirmed_by) references sys_user (user_id), + constraint fk_biz_inbound_order_test_batch_id foreign key (test_batch_id) references biz_test_batch (batch_id), constraint ck_biz_inbound_order_status check (status in ('DRAFT', 'CONFIRMED')) ) engine=innodb comment = '首次入库单'; -- 首次入库单托盘明细 create table biz_inbound_item ( inbound_item_id bigint(20) not null auto_increment comment '入库明细ID', + test_batch_id bigint(20) null comment '测试数据批次ID;NULL表示正式数据', inbound_id bigint(20) not null comment '入库单ID', pallet_id bigint(20) not null comment '托盘ID', create_by varchar(64) default '' comment '创建者', create_time datetime comment '创建时间', primary key (inbound_item_id), + key idx_biz_inbound_item_test_batch_id (test_batch_id), constraint uq_biz_inbound_item_order_pallet unique (inbound_id, pallet_id), constraint fk_biz_inbound_item_order foreign key (inbound_id) references biz_inbound_order (inbound_id), - constraint fk_biz_inbound_item_pallet foreign key (pallet_id) references biz_pallet (pallet_id) + constraint fk_biz_inbound_item_pallet foreign key (pallet_id) references biz_pallet (pallet_id), + constraint fk_biz_inbound_item_test_batch_id foreign key (test_batch_id) references biz_test_batch (batch_id) ) engine=innodb comment = '首次入库单托盘明细'; -- 托盘装填记录 create table biz_filling_record ( filling_id bigint(20) not null auto_increment comment '装填记录ID', + test_batch_id bigint(20) null comment '测试数据批次ID;NULL表示正式数据', filling_no varchar(64) not null comment '装填单号', pallet_id bigint(20) not null comment '托盘ID', warehouse_id bigint(20) not null comment '仓库ID', @@ -998,6 +1064,7 @@ create table biz_filling_record ( update_time datetime comment '更新时间', remark varchar(500) default null comment '备注', primary key (filling_id), + key idx_biz_filling_record_test_batch_id (test_batch_id), constraint uq_biz_filling_record_no unique (filling_no), constraint uq_biz_filling_record_warehouse_idempotency unique (warehouse_id, idempotency_key), constraint uq_biz_filling_record_pallet_active_slot unique (pallet_id, active_slot), @@ -1005,6 +1072,7 @@ create table biz_filling_record ( constraint fk_biz_filling_record_warehouse foreign key (warehouse_id) references biz_warehouse (warehouse_id), constraint fk_biz_filling_record_strain foreign key (strain_id) references biz_strain (strain_id), constraint fk_biz_filling_record_operator foreign key (operator_id) references sys_user (user_id), + constraint fk_biz_filling_record_test_batch_id foreign key (test_batch_id) references biz_test_batch (batch_id), constraint ck_biz_filling_record_initial_packages check (initial_packages > 0), constraint ck_biz_filling_record_active_slot check (active_slot is null or active_slot = 'ACTIVE') ) engine=innodb comment = '托盘装填记录'; @@ -1012,6 +1080,7 @@ create table biz_filling_record ( -- 整托发货单 create table biz_shipment_order ( shipment_id bigint(20) not null auto_increment comment '发货单ID', + test_batch_id bigint(20) null comment '测试数据批次ID;NULL表示正式数据', shipment_no varchar(64) not null comment '发货单号', source_warehouse_id bigint(20) not null comment '来源仓库ID', target_site_id bigint(20) not null comment '目标现场ID', @@ -1026,17 +1095,20 @@ create table biz_shipment_order ( update_time datetime comment '更新时间', remark varchar(500) default null comment '备注', primary key (shipment_id), + key idx_biz_shipment_order_test_batch_id (test_batch_id), constraint uq_biz_shipment_order_no unique (shipment_no), constraint uq_biz_shipment_order_route_confirm_idempotency unique (source_warehouse_id, target_site_id, confirm_idempotency_key), constraint fk_biz_shipment_order_warehouse foreign key (source_warehouse_id) references biz_warehouse (warehouse_id), constraint fk_biz_shipment_order_site foreign key (target_site_id) references biz_customer_site (site_id), constraint fk_biz_shipment_order_confirmed_by foreign key (confirmed_by) references sys_user (user_id), + constraint fk_biz_shipment_order_test_batch_id foreign key (test_batch_id) references biz_test_batch (batch_id), constraint ck_biz_shipment_order_status check (status in ('DRAFT', 'CONFIRMED')) ) engine=innodb comment = '整托发货单'; -- 整托发货明细 create table biz_shipment_item ( shipment_item_id bigint(20) not null auto_increment comment '发货明细ID', + test_batch_id bigint(20) null comment '测试数据批次ID;NULL表示正式数据', shipment_id bigint(20) not null comment '发货单ID', pallet_id bigint(20) not null comment '托盘ID', active_filling_id bigint(20) comment '确认时活动装填记录ID', @@ -1044,16 +1116,19 @@ create table biz_shipment_item ( create_by varchar(64) default '' comment '创建者', create_time datetime comment '创建时间', primary key (shipment_item_id), + key idx_biz_shipment_item_test_batch_id (test_batch_id), constraint uq_biz_shipment_item_order_pallet unique (shipment_id, pallet_id), constraint fk_biz_shipment_item_order foreign key (shipment_id) references biz_shipment_order (shipment_id), constraint fk_biz_shipment_item_pallet foreign key (pallet_id) references biz_pallet (pallet_id), constraint fk_biz_shipment_item_filling foreign key (active_filling_id) references biz_filling_record (filling_id), + constraint fk_biz_shipment_item_test_batch_id foreign key (test_batch_id) references biz_test_batch (batch_id), constraint ck_biz_shipment_item_package_qty check (package_qty is null or package_qty > 0) ) engine=innodb comment = '整托发货明细'; -- 客户现场使用事实记录 create table biz_usage_record ( usage_id bigint(20) not null auto_increment comment '使用记录ID', + test_batch_id bigint(20) null comment '测试数据批次ID;NULL表示正式数据', usage_no varchar(64) not null comment '使用记录号', pallet_id bigint(20) not null comment '托盘ID', active_filling_id bigint(20) not null comment '活动装填记录ID', @@ -1066,6 +1141,7 @@ create table biz_usage_record ( operator_id bigint(20) not null comment '操作人ID', occurred_at datetime not null comment '发生时间', primary key (usage_id), + key idx_biz_usage_record_test_batch_id (test_batch_id), constraint uq_biz_usage_record_no unique (usage_no), constraint uq_biz_usage_record_site_idempotency unique (site_id, idempotency_key), constraint fk_biz_usage_record_pallet foreign key (pallet_id) references biz_pallet (pallet_id), @@ -1073,6 +1149,7 @@ create table biz_usage_record ( constraint fk_biz_usage_record_site foreign key (site_id) references biz_customer_site (site_id), constraint fk_biz_usage_record_line foreign key (line_id) references biz_production_line (line_id), constraint fk_biz_usage_record_operator foreign key (operator_id) references sys_user (user_id), + constraint fk_biz_usage_record_test_batch_id foreign key (test_batch_id) references biz_test_batch (batch_id), constraint ck_biz_usage_record_used_packages check (used_packages > 0), constraint ck_biz_usage_record_remaining_packages_after check (remaining_packages_after >= 0) ) engine=innodb comment = '客户现场使用事实记录'; @@ -1080,6 +1157,7 @@ create table biz_usage_record ( -- 托盘退回单 create table biz_return_order ( return_id bigint(20) not null auto_increment comment '退回单ID', + test_batch_id bigint(20) null comment '测试数据批次ID;NULL表示正式数据', return_no varchar(64) not null comment '退回单号', source_site_id bigint(20) not null comment '来源现场ID', target_warehouse_id bigint(20) not null comment '目标仓库ID', @@ -1098,6 +1176,7 @@ create table biz_return_order ( update_time datetime comment '更新时间', remark varchar(500) default null comment '备注', primary key (return_id), + key idx_biz_return_order_test_batch_id (test_batch_id), constraint uq_biz_return_order_no unique (return_no), constraint uq_biz_return_order_route_request_idempotency unique (source_site_id, target_warehouse_id, request_idempotency_key), constraint uq_biz_return_order_route_confirm_idempotency unique (source_site_id, target_warehouse_id, confirm_idempotency_key), @@ -1105,12 +1184,14 @@ create table biz_return_order ( constraint fk_biz_return_order_warehouse foreign key (target_warehouse_id) references biz_warehouse (warehouse_id), constraint fk_biz_return_order_requested_by foreign key (requested_by) references sys_user (user_id), constraint fk_biz_return_order_confirmed_by foreign key (confirmed_by) references sys_user (user_id), + constraint fk_biz_return_order_test_batch_id foreign key (test_batch_id) references biz_test_batch (batch_id), constraint ck_biz_return_order_status check (status in ('REQUESTED', 'CONFIRMED')) ) engine=innodb comment = '托盘退回单'; -- 托盘退回明细 create table biz_return_item ( return_item_id bigint(20) not null auto_increment comment '退回明细ID', + test_batch_id bigint(20) null comment '测试数据批次ID;NULL表示正式数据', return_id bigint(20) not null comment '退回单ID', pallet_id bigint(20) not null comment '托盘ID', active_filling_id_snapshot bigint(20) comment '申请时活动装填记录快照', @@ -1118,16 +1199,19 @@ create table biz_return_item ( create_by varchar(64) default '' comment '创建者', create_time datetime comment '创建时间', primary key (return_item_id), + key idx_biz_return_item_test_batch_id (test_batch_id), constraint uq_biz_return_item_order_pallet unique (return_id, pallet_id), constraint fk_biz_return_item_order foreign key (return_id) references biz_return_order (return_id), constraint fk_biz_return_item_pallet foreign key (pallet_id) references biz_pallet (pallet_id), constraint fk_biz_return_item_filling foreign key (active_filling_id_snapshot) references biz_filling_record (filling_id), + constraint fk_biz_return_item_test_batch_id foreign key (test_batch_id) references biz_test_batch (batch_id), constraint ck_biz_return_item_remaining_packages_snapshot check (remaining_packages_snapshot >= 0) ) engine=innodb comment = '托盘退回明细'; -- 托盘只追加流转事件;必须先于库存快照创建 create table biz_pallet_event ( event_id bigint(20) not null auto_increment comment '事件ID', + test_batch_id bigint(20) null comment '测试数据批次ID;NULL表示正式数据', pallet_id bigint(20) not null comment '托盘ID', event_seq int not null comment '托盘内事件序号', event_type varchar(32) not null comment '事件类型', @@ -1148,6 +1232,7 @@ create table biz_pallet_event ( idempotency_key varchar(64) not null comment '幂等键', request_hash varchar(64) not null comment '请求摘要', primary key (event_id), + key idx_biz_pallet_event_test_batch_id (test_batch_id), constraint uq_biz_pallet_event_pallet_seq unique (pallet_id, event_seq), constraint uq_biz_pallet_event_pallet_idempotency unique (pallet_id, idempotency_key), constraint fk_biz_pallet_event_pallet foreign key (pallet_id) references biz_pallet (pallet_id), @@ -1158,6 +1243,7 @@ create table biz_pallet_event ( constraint fk_biz_pallet_event_after_site foreign key (after_site_id) references biz_customer_site (site_id), constraint fk_biz_pallet_event_after_line foreign key (after_line_id) references biz_production_line (line_id), constraint fk_biz_pallet_event_operator foreign key (operator_id) references sys_user (user_id), + constraint fk_biz_pallet_event_test_batch_id foreign key (test_batch_id) references biz_test_batch (batch_id), constraint ck_biz_pallet_event_event_type check ( event_type in ( 'INBOUND_CONFIRMED', @@ -1173,6 +1259,7 @@ create table biz_pallet_event ( -- 托盘当前库存、位置和流转状态 create table biz_pallet_inventory ( pallet_id bigint(20) not null comment '托盘ID', + test_batch_id bigint(20) null comment '测试数据批次ID;NULL表示正式数据', active_filling_id bigint(20) comment '当前活动装填记录ID', warehouse_id bigint(20) comment '当前仓库ID', site_id bigint(20) comment '当前现场ID', @@ -1183,12 +1270,14 @@ create table biz_pallet_inventory ( version int not null default 0 comment '并发版本号', updated_at datetime not null comment '更新时间', primary key (pallet_id), + key idx_biz_pallet_inventory_test_batch_id (test_batch_id), constraint fk_biz_pallet_inventory_pallet foreign key (pallet_id) references biz_pallet (pallet_id), constraint fk_biz_pallet_inventory_filling foreign key (active_filling_id) references biz_filling_record (filling_id), constraint fk_biz_pallet_inventory_warehouse foreign key (warehouse_id) references biz_warehouse (warehouse_id), constraint fk_biz_pallet_inventory_site foreign key (site_id) references biz_customer_site (site_id), constraint fk_biz_pallet_inventory_line foreign key (current_line_id) references biz_production_line (line_id), constraint fk_biz_pallet_inventory_event foreign key (last_event_id) references biz_pallet_event (event_id), + constraint fk_biz_pallet_inventory_test_batch_id foreign key (test_batch_id) references biz_test_batch (batch_id), constraint ck_biz_pallet_inventory_flow_state check ( flow_state in ( 'UNSTOCKED', diff --git a/shierfeng-fastapi-backend/tests/module_business/test_model_contract.py b/shierfeng-fastapi-backend/tests/module_business/test_model_contract.py index 1419710..9d7311f 100644 --- a/shierfeng-fastapi-backend/tests/module_business/test_model_contract.py +++ b/shierfeng-fastapi-backend/tests/module_business/test_model_contract.py @@ -1,11 +1,32 @@ import unittest -from sqlalchemy import CHAR, BigInteger, CheckConstraint, DateTime, Integer, String, Table, UniqueConstraint +from sqlalchemy import CHAR, BigInteger, CheckConstraint, DateTime, Integer, String, Table, Text, UniqueConstraint from sqlalchemy.dialects import mysql, postgresql -from sqlalchemy.schema import CreateColumn +from sqlalchemy.schema import CreateColumn, CreateTable from config.database import Base -from module_business.entity.do import master_do, operation_do +from module_admin.entity.do import user_do +from module_business.entity.do import master_do, operation_do, test_batch_do + +EXPECTED_TEST_BATCH_TABLES = {'biz_test_batch', 'biz_test_batch_user'} +TEST_BATCH_OWNED_TABLES = { + 'biz_strain', + 'biz_customer', + 'biz_customer_site', + 'biz_production_line', + 'biz_warehouse', + 'biz_pallet', + 'biz_inbound_order', + 'biz_inbound_item', + 'biz_filling_record', + 'biz_shipment_order', + 'biz_shipment_item', + 'biz_usage_record', + 'biz_return_order', + 'biz_return_item', + 'biz_pallet_inventory', + 'biz_pallet_event', +} EXPECTED_COLUMNS = { 'biz_strain': { @@ -892,6 +913,136 @@ EXPECTED_UNIQUE_SETS = { }, } +for table_name in TEST_BATCH_OWNED_TABLES: + EXPECTED_COLUMNS[table_name].add('test_batch_id') + EXPECTED_COLUMN_TYPES[table_name]['test_batch_id'] = (BigInteger, None) + EXPECTED_NULLABILITY[table_name]['test_batch_id'] = True + EXPECTED_FOREIGN_KEYS[table_name].add(('test_batch_id', 'biz_test_batch.batch_id')) + +EXPECTED_COLUMNS.update( + { + 'biz_test_batch': { + 'batch_id', + 'batch_code', + 'batch_name', + 'status', + 'create_by', + 'create_time', + 'update_by', + 'update_time', + 'closed_by', + 'closed_at', + 'purged_by', + 'purged_at', + 'purge_summary', + 'preview_action', + 'preview_token_hash', + 'preview_scope_hash', + 'preview_expires_at', + 'remark', + }, + 'biz_test_batch_user': {'batch_id', 'user_id', 'create_by', 'create_time'}, + } +) +EXPECTED_COLUMN_TYPES.update( + { + 'biz_test_batch': { + 'batch_id': (BigInteger, None), + 'batch_code': (String, 64), + 'batch_name': (String, 100), + 'status': (String, 16), + 'create_by': (String, 64), + 'create_time': (DateTime, None), + 'update_by': (String, 64), + 'update_time': (DateTime, None), + 'closed_by': (BigInteger, None), + 'closed_at': (DateTime, None), + 'purged_by': (BigInteger, None), + 'purged_at': (DateTime, None), + 'purge_summary': (Text, None), + 'preview_action': (String, 16), + 'preview_token_hash': (CHAR, 64), + 'preview_scope_hash': (CHAR, 64), + 'preview_expires_at': (DateTime, None), + 'remark': (String, 500), + }, + 'biz_test_batch_user': { + 'batch_id': (BigInteger, None), + 'user_id': (BigInteger, None), + 'create_by': (String, 64), + 'create_time': (DateTime, None), + }, + } +) +EXPECTED_NULLABILITY.update( + { + 'biz_test_batch': { + 'batch_id': False, + 'batch_code': False, + 'batch_name': False, + 'status': False, + 'create_by': False, + 'create_time': False, + 'update_by': False, + 'update_time': False, + 'closed_by': True, + 'closed_at': True, + 'purged_by': True, + 'purged_at': True, + 'purge_summary': True, + 'preview_action': True, + 'preview_token_hash': True, + 'preview_scope_hash': True, + 'preview_expires_at': True, + 'remark': True, + }, + 'biz_test_batch_user': { + 'batch_id': False, + 'user_id': False, + 'create_by': False, + 'create_time': False, + }, + } +) +EXPECTED_CALLABLE_DATETIME_DEFAULTS.update( + { + ('biz_test_batch', 'create_time'), + ('biz_test_batch', 'update_time'), + ('biz_test_batch_user', 'create_time'), + } +) +EXPECTED_EMPTY_AUDIT_SERVER_DEFAULTS.update( + { + ('biz_test_batch', 'create_by'), + ('biz_test_batch', 'update_by'), + ('biz_test_batch_user', 'create_by'), + } +) +EXPECTED_PRIMARY_KEYS.update( + { + 'biz_test_batch': ('batch_id',), + 'biz_test_batch_user': ('batch_id', 'user_id'), + } +) +EXPECTED_FOREIGN_KEYS.update( + { + 'biz_test_batch': { + ('closed_by', 'sys_user.user_id'), + ('purged_by', 'sys_user.user_id'), + }, + 'biz_test_batch_user': { + ('batch_id', 'biz_test_batch.batch_id'), + ('user_id', 'sys_user.user_id'), + }, + } +) +EXPECTED_UNIQUE_SETS.update( + { + 'biz_test_batch': {frozenset({'batch_code'})}, + 'biz_test_batch_user': set(), + } +) + def _unique_column_sets(table: Table) -> set[frozenset[str]]: return { @@ -910,6 +1061,16 @@ def _normalized_check_sql(table: Table) -> dict[str, str]: class BusinessModelContractTest(unittest.TestCase): + def test_batch_related_tables_compile_for_mysql_and_postgresql(self) -> None: + self.assertIs(Base.metadata.tables['sys_user'], user_do.SysUser.__table__) + batch_tables = EXPECTED_TEST_BATCH_TABLES | TEST_BATCH_OWNED_TABLES + self.assertEqual(18, len(batch_tables)) + for table_name in batch_tables: + for dialect_name, dialect in (('mysql', mysql.dialect()), ('postgresql', postgresql.dialect())): + with self.subTest(table_name=table_name, dialect=dialect_name): + compiled = str(CreateTable(Base.metadata.tables[table_name]).compile(dialect=dialect)) + self.assertIn(table_name, compiled) + def test_exact_business_tables_are_registered(self) -> None: expected = { 'biz_strain', @@ -930,7 +1091,7 @@ class BusinessModelContractTest(unittest.TestCase): 'biz_return_item', 'biz_pallet_inventory', 'biz_pallet_event', - } + } | EXPECTED_TEST_BATCH_TABLES actual = {name for name in Base.metadata.tables if name.startswith('biz_')} self.assertEqual(expected, actual) @@ -954,6 +1115,8 @@ class BusinessModelContractTest(unittest.TestCase): operation_do.BizReturnItem: 'biz_return_item', operation_do.BizPalletInventory: 'biz_pallet_inventory', operation_do.BizPalletEvent: 'biz_pallet_event', + test_batch_do.BizTestBatch: 'biz_test_batch', + test_batch_do.BizTestBatchUser: 'biz_test_batch_user', } self.assertEqual(set(EXPECTED_COLUMNS), set(models.values())) for model, table_name in models.items(): @@ -1032,6 +1195,7 @@ class BusinessModelContractTest(unittest.TestCase): _, separator, compiled_default = compiled_column.partition(' DEFAULT ') if separator: compiled_default = compiled_default.split(' COMMENT ', maxsplit=1)[0].strip() + compiled_default = compiled_default.removesuffix(' NOT NULL') if compiled_default != "''": violations.append((dialect_name, table_name, column_name, compiled_default)) self.assertEqual([], violations) @@ -1058,6 +1222,26 @@ class BusinessModelContractTest(unittest.TestCase): with self.subTest(table_name=table_name): self.assertEqual(expected_unique_sets, _unique_column_sets(Base.metadata.tables[table_name])) + def test_all_owned_models_have_nullable_indexed_batch_fk(self) -> None: + for table_name in TEST_BATCH_OWNED_TABLES: + table = Base.metadata.tables[table_name] + column = table.c.test_batch_id + self.assertTrue(column.nullable, table_name) + self.assertTrue(column.index, table_name) + fk = next(iter(column.foreign_keys)) + self.assertEqual('biz_test_batch.batch_id', fk.target_fullname) + self.assertIsNone(fk.ondelete) + + def test_test_batch_model_defaults_and_comments_match_contract(self) -> None: + table = Base.metadata.tables['biz_test_batch'] + self.assertEqual('测试数据批次', table.comment) + self.assertEqual('ACTIVE', str(table.c.status.server_default.arg)) + for table_name in TEST_BATCH_OWNED_TABLES: + self.assertEqual( + '测试数据批次ID;NULL表示正式数据', + Base.metadata.tables[table_name].c.test_batch_id.comment, + ) + def test_pallet_identifiers_are_unique(self) -> None: table = Base.metadata.tables['biz_pallet'] unique_columns = {column.name for column in table.columns if column.unique} @@ -1065,6 +1249,10 @@ class BusinessModelContractTest(unittest.TestCase): def test_checks_lock_quantities_states_active_slot_and_location(self) -> None: expected_checks = { + 'biz_test_batch': { + 'ck_biz_test_batch_status': "statusIN('ACTIVE','CLOSED','PURGED')", + 'ck_biz_test_batch_preview_action': "preview_actionISNULLORpreview_actionIN('ADOPT','PURGE')", + }, 'biz_strain': {'ck_biz_strain_status': "statusIN('0','1')"}, 'biz_warehouse': {'ck_biz_warehouse_status': "statusIN('0','1')"}, 'biz_customer': {'ck_biz_customer_status': "statusIN('0','1')"}, diff --git a/shierfeng-fastapi-backend/tests/module_business/test_sql_contract.py b/shierfeng-fastapi-backend/tests/module_business/test_sql_contract.py index a5d14fc..184d46a 100644 --- a/shierfeng-fastapi-backend/tests/module_business/test_sql_contract.py +++ b/shierfeng-fastapi-backend/tests/module_business/test_sql_contract.py @@ -9,11 +9,33 @@ INITIALIZERS = ('shierfeng-fastapi.sql', 'shierfeng-fastapi-pg.sql') PATCH_PATH = SQL_ROOT / 'patches/20260724_pallet_workflow_mysql.sql' MENU_PROMOTION_PATCH_PATH = SQL_ROOT / 'patches/20260727_promote_business_menus_mysql.sql' BUSINESS_MENU_ICON_PATCH_PATH = SQL_ROOT / 'patches/20260728_add_business_menu_icons_mysql.sql' +TEST_BATCH_MYSQL_PATCH_PATH = SQL_ROOT / 'patches/20260922_test_data_batch_mysql.sql' +TEST_BATCH_PG_PATCH_PATH = SQL_ROOT / 'patches/20260922_test_data_batch_pg.sql' SYS_MENU_VALUE_COUNT = 20 SYS_MENU_PERMS_INDEX = 13 SYS_MENU_ICON_INDEX = 14 REMOVED_AI_DICT_CODES = set(range(33, 70)) +EXPECTED_TEST_BATCH_TABLES = {'biz_test_batch', 'biz_test_batch_user'} +TEST_BATCH_OWNED_TABLES = { + 'biz_strain', + 'biz_customer', + 'biz_customer_site', + 'biz_production_line', + 'biz_warehouse', + 'biz_pallet', + 'biz_inbound_order', + 'biz_inbound_item', + 'biz_filling_record', + 'biz_shipment_order', + 'biz_shipment_item', + 'biz_usage_record', + 'biz_return_order', + 'biz_return_item', + 'biz_pallet_inventory', + 'biz_pallet_event', +} + EXPECTED_TABLES = { 'biz_strain', 'biz_warehouse', @@ -346,6 +368,83 @@ EXPECTED_CHECKS = { }, } +LEGACY_EXPECTED_TABLES = set(EXPECTED_TABLES) +LEGACY_EXPECTED_COLUMNS = {table: set(columns) for table, columns in EXPECTED_COLUMNS.items()} +LEGACY_EXPECTED_PRIMARY_KEYS = dict(EXPECTED_PRIMARY_KEYS) +LEGACY_EXPECTED_UNIQUE_KEYS = {table: set(keys) for table, keys in EXPECTED_UNIQUE_KEYS.items()} +LEGACY_EXPECTED_FOREIGN_KEYS = {table: set(keys) for table, keys in EXPECTED_FOREIGN_KEYS.items()} +LEGACY_EXPECTED_CHECKS = {table: set(checks) for table, checks in EXPECTED_CHECKS.items()} + +EXPECTED_TABLES |= EXPECTED_TEST_BATCH_TABLES +for table_name in TEST_BATCH_OWNED_TABLES: + EXPECTED_COLUMNS[table_name].add('test_batch_id') + EXPECTED_FOREIGN_KEYS[table_name].add(('test_batch_id', 'biz_test_batch.batch_id')) + +EXPECTED_COLUMNS.update( + { + 'biz_test_batch': { + 'batch_id', + 'batch_code', + 'batch_name', + 'status', + 'create_by', + 'create_time', + 'update_by', + 'update_time', + 'closed_by', + 'closed_at', + 'purged_by', + 'purged_at', + 'purge_summary', + 'preview_action', + 'preview_token_hash', + 'preview_scope_hash', + 'preview_expires_at', + 'remark', + }, + 'biz_test_batch_user': {'batch_id', 'user_id', 'create_by', 'create_time'}, + } +) +EXPECTED_DATETIME_COLUMNS.update( + { + 'biz_test_batch': {'create_time', 'update_time', 'closed_at', 'purged_at', 'preview_expires_at'}, + 'biz_test_batch_user': {'create_time'}, + } +) +EXPECTED_PRIMARY_KEYS.update( + { + 'biz_test_batch': ('batch_id',), + 'biz_test_batch_user': ('batch_id', 'user_id'), + } +) +EXPECTED_UNIQUE_KEYS.update( + { + 'biz_test_batch': {('batch_code',)}, + 'biz_test_batch_user': set(), + } +) +EXPECTED_FOREIGN_KEYS.update( + { + 'biz_test_batch': { + ('closed_by', 'sys_user.user_id'), + ('purged_by', 'sys_user.user_id'), + }, + 'biz_test_batch_user': { + ('batch_id', 'biz_test_batch.batch_id'), + ('user_id', 'sys_user.user_id'), + }, + } +) +EXPECTED_CHECKS.update( + { + 'biz_test_batch': { + "statusin('active','closed','purged')", + "preview_actionisnullorpreview_actionin('adopt','purge')", + }, + 'biz_test_batch_user': set(), + } +) + EXPECTED_PAGES = { 502: ('菌种资料', 121, 1, 'strain', 'master/strain/index', 'master:strain:list'), 503: ('客户资料', 121, 2, 'customer', 'master/customer/index', 'master:customer:list'), @@ -873,6 +972,74 @@ def _is_postgres_naive_timestamp_with_default_precision(definition: str) -> bool ) +def _assert_test_batch_sql_contract( + test_case: unittest.TestCase, + sql: str, + *, + dialect: str, + is_patch: bool, +) -> None: + clean_sql = _strip_sql_comments(sql).replace('`', '') + normalized = re.sub(r'\s+', ' ', clean_sql).strip().lower() + tables = _create_table_bodies(sql) + + test_case.assertNotRegex(normalized, r'\bon\s+delete\s+cascade\b') + if is_patch: + test_case.assertNotRegex(normalized, r'\bupdate\b') + + for table_name in EXPECTED_TEST_BATCH_TABLES: + test_case.assertIn(table_name, tables) + columns, primary_key, unique_keys, foreign_keys, checks = _table_contract(tables[table_name]) + test_case.assertEqual(EXPECTED_COLUMNS[table_name], columns, table_name) + test_case.assertEqual(EXPECTED_PRIMARY_KEYS[table_name], primary_key, table_name) + test_case.assertEqual(EXPECTED_UNIQUE_KEYS[table_name], unique_keys, table_name) + test_case.assertEqual(EXPECTED_FOREIGN_KEYS[table_name], foreign_keys, table_name) + test_case.assertEqual(EXPECTED_CHECKS[table_name], checks, table_name) + + create_batch = normalized.index('create table biz_test_batch (') + create_batch_user = normalized.index('create table biz_test_batch_user (') + owned_marker = 'alter table' if is_patch else 'create table' + first_owned = min(normalized.index(f'{owned_marker} {table_name} ') for table_name in TEST_BATCH_OWNED_TABLES) + test_case.assertLess(create_batch, create_batch_user) + test_case.assertLess(create_batch_user, first_owned) + + for table_name in TEST_BATCH_OWNED_TABLES: + with test_case.subTest(dialect=dialect, patch=is_patch, table=table_name): + if is_patch: + test_case.assertRegex( + normalized, + rf'\balter table {table_name}\s+add column test_batch_id bigint null\b', + ) + else: + definitions = _column_definitions(tables[table_name]) + test_case.assertIn('test_batch_id', definitions) + test_case.assertRegex(definitions['test_batch_id'], r'\bbigint\b') + test_case.assertNotRegex(definitions['test_batch_id'], r'\bnot\s+null\b') + + index_name = f'idx_{table_name}_test_batch_id' + if dialect == 'mysql': + index_pattern = rf'\b(?:add\s+)?(?:index|key)\s+{index_name}\s*\(\s*test_batch_id\s*\)' + test_case.assertRegex(normalized, index_pattern) + test_case.assertIn("comment '测试数据批次id;null表示正式数据'", normalized) + else: + index_pattern = ( + rf'\bcreate\s+index\s+{index_name}\s+on\s+{table_name}\s*\(\s*test_batch_id\s*\)' + ) + test_case.assertRegex(normalized, index_pattern) + test_case.assertRegex( + normalized, + rf"comment\s+on\s+column\s+{table_name}\.test_batch_id\s+is\s+" + r"'测试数据批次id;null表示正式数据'", + ) + + constraint_name = f'fk_{table_name}_test_batch_id' + test_case.assertRegex( + normalized, + rf'\bconstraint\s+{constraint_name}\s+foreign\s+key\s*\(\s*test_batch_id\s*\)\s*' + r'references\s+biz_test_batch\s*\(\s*batch_id\s*\)', + ) + + def _menu_rows(sql: str, menu_ids: set[int]) -> dict[int, list]: rows = _insert_rows(sql, 'sys_menu') selected = [row for row in rows if row and str(row[0]).isdigit() and int(row[0]) in menu_ids] @@ -1064,9 +1231,7 @@ def _assert_business_menu_icon_guard_primary_key(test_case: unittest.TestCase, s def _assert_business_menu_icon_guard_structure(test_case: unittest.TestCase, sql: str) -> tuple[str, str]: normalized = [_normalized_sql_statement(statement) for statement in _sql_statements(sql)] - guards = [ - statement for statement in normalized if statement.startswith('insert into business_menu_icon_guard ') - ] + guards = [statement for statement in normalized if statement.startswith('insert into business_menu_icon_guard ')] test_case.assertEqual(2, len(guards)) pre_guard, post_guard = guards identity_fields = ( @@ -1128,6 +1293,23 @@ def _assert_business_menu_icon_guard_structure(test_case: unittest.TestCase, sql class SqlContractTest(unittest.TestCase): + def test_test_batch_initializers_and_patches_match_contract(self) -> None: + scripts = ( + (SQL_ROOT / 'shierfeng-fastapi.sql', 'mysql', False), + (SQL_ROOT / 'shierfeng-fastapi-pg.sql', 'postgresql', False), + (TEST_BATCH_MYSQL_PATCH_PATH, 'mysql', True), + (TEST_BATCH_PG_PATCH_PATH, 'postgresql', True), + ) + for path, dialect, is_patch in scripts: + with self.subTest(path=path.name): + self.assertTrue(path.is_file(), f'missing SQL: {path}') + _assert_test_batch_sql_contract( + self, + path.read_text(encoding='utf-8'), + dialect=dialect, + is_patch=is_patch, + ) + def test_initializers_define_exact_business_table_set(self) -> None: for filename in INITIALIZERS: with self.subTest(filename=filename): @@ -1149,7 +1331,7 @@ class SqlContractTest(unittest.TestCase): self.assertEqual(EXPECTED_CHECKS[table], checks) def test_postgres_business_datetimes_preserve_default_microsecond_precision(self) -> None: - self.assertEqual(34, sum(len(columns) for columns in EXPECTED_DATETIME_COLUMNS.values())) + self.assertEqual(40, sum(len(columns) for columns in EXPECTED_DATETIME_COLUMNS.values())) tables = _create_table_bodies((SQL_ROOT / 'shierfeng-fastapi-pg.sql').read_text(encoding='utf-8')) invalid_columns = [] for table, expected_columns in EXPECTED_DATETIME_COLUMNS.items(): @@ -1290,9 +1472,7 @@ class SqlContractTest(unittest.TestCase): self.assertEqual(1, normalized.count('start transaction')) self.assertEqual(1, normalized.count('commit')) transaction_body = normalized[start_index + 1 : commit_index] - lock_statement = ( - 'select menu_id from sys_menu where menu_id in (121, 122, 123) order by menu_id for update' - ) + lock_statement = 'select menu_id from sys_menu where menu_id in (121, 122, 123) order by menu_id for update' expected_update = ( "update sys_menu set icon = case menu_id when 121 then 'documentation' when 122 then 'build' " "when 123 then 'chart' else icon end where menu_id in (121, 122, 123)" @@ -1476,6 +1656,8 @@ class SqlContractTest(unittest.TestCase): 'biz_customer', 'biz_warehouse', 'biz_strain', + 'biz_test_batch_user', + 'biz_test_batch', ) for filename in INITIALIZERS: with self.subTest(filename=filename): @@ -1777,15 +1959,15 @@ class SqlContractTest(unittest.TestCase): self.assertLess(lower.index('ddl'), lower.index('dml')) self.assertEqual(18, len(re.findall(r'create\s+table\s+if\s+not\s+exists\s+`?biz_', lower))) tables = _create_table_bodies(raw) - self.assertEqual(EXPECTED_TABLES, {table for table in tables if table.startswith('biz_')}) - for table in EXPECTED_TABLES: + self.assertEqual(LEGACY_EXPECTED_TABLES, {table for table in tables if table.startswith('biz_')}) + for table in LEGACY_EXPECTED_TABLES: with self.subTest(table=table): columns, primary_key, unique_keys, foreign_keys, checks = _table_contract(tables[table]) - self.assertEqual(EXPECTED_COLUMNS[table], columns) - self.assertEqual(EXPECTED_PRIMARY_KEYS[table], primary_key) - self.assertEqual(EXPECTED_UNIQUE_KEYS[table], unique_keys) - self.assertEqual(EXPECTED_FOREIGN_KEYS[table], foreign_keys) - self.assertEqual(EXPECTED_CHECKS[table], checks) + self.assertEqual(LEGACY_EXPECTED_COLUMNS[table], columns) + self.assertEqual(LEGACY_EXPECTED_PRIMARY_KEYS[table], primary_key) + self.assertEqual(LEGACY_EXPECTED_UNIQUE_KEYS[table], unique_keys) + self.assertEqual(LEGACY_EXPECTED_FOREIGN_KEYS[table], foreign_keys) + self.assertEqual(LEGACY_EXPECTED_CHECKS[table], checks) _assert_business_menu_fields(self, raw, LEGACY_PATCH_BUSINESS_MENU_FIELDS) _assert_business_permission_inventory(self, raw) patch_rows = _business_menu_rows(raw) @@ -1793,9 +1975,7 @@ class SqlContractTest(unittest.TestCase): compared_indexes = (0, 1, *range(4, 15)) expected_legacy_rows = { menu_id: tuple( - '#' - if index == SYS_MENU_ICON_INDEX and menu_id in EXPECTED_BUSINESS_ROOT_ICONS - else row[index] + '#' if index == SYS_MENU_ICON_INDEX and menu_id in EXPECTED_BUSINESS_ROOT_ICONS else row[index] for index in compared_indexes ) for menu_id, row in mysql_rows.items() diff --git a/shierfeng-fastapi-backend/tests/module_business/test_vo_contract.py b/shierfeng-fastapi-backend/tests/module_business/test_vo_contract.py index 08eb29f..a994f02 100644 --- a/shierfeng-fastapi-backend/tests/module_business/test_vo_contract.py +++ b/shierfeng-fastapi-backend/tests/module_business/test_vo_contract.py @@ -1,4 +1,5 @@ import unittest +from datetime import datetime from pydantic import ValidationError from pydantic.alias_generators import to_camel @@ -69,6 +70,21 @@ from module_business.entity.vo.report_vo import ( ShipmentReportModel, ShipmentReportQueryModel, ) +from module_business.entity.vo.test_batch_vo import ( + AdoptionExecuteRequestModel, + AdoptionPreviewRequestModel, + AdoptionRootModel, + BatchImpactModel, + PurgeExecuteRequestModel, + PurgePreviewModel, + TestBatchCreateModel, + TestBatchDetailModel, + TestBatchEditModel, + TestBatchModel, + TestBatchOptionModel, + TestBatchPreviewModel, + TestBatchUserAssignModel, +) MASTER_MODEL_FIELDS = { StrainModel: { @@ -549,6 +565,71 @@ REPORT_MODEL_FIELDS = { }, } +TEST_BATCH_MODEL_FIELDS = { + TestBatchCreateModel: {'batch_name', 'remark'}, + TestBatchEditModel: {'remark'}, + TestBatchUserAssignModel: {'user_ids'}, + TestBatchModel: { + 'batch_id', + 'batch_code', + 'batch_name', + 'status', + 'remark', + 'create_by', + 'create_time', + 'update_by', + 'update_time', + 'closed_by', + 'closed_at', + 'purged_by', + 'purged_at', + 'purge_summary', + }, + TestBatchOptionModel: {'batch_id', 'batch_code', 'batch_name', 'status'}, + BatchImpactModel: {'table_name', 'label', 'count', 'keys'}, + TestBatchDetailModel: { + 'batch_id', + 'batch_code', + 'batch_name', + 'status', + 'remark', + 'create_by', + 'create_time', + 'update_by', + 'update_time', + 'closed_by', + 'closed_at', + 'purged_by', + 'purged_at', + 'purge_summary', + 'user_ids', + 'data_summary', + }, + TestBatchPreviewModel: { + 'batch_id', + 'batch_name', + 'preview_token', + 'expires_at', + 'total_count', + 'impacts', + }, + PurgePreviewModel: { + 'batch_id', + 'batch_name', + 'preview_token', + 'expires_at', + 'total_count', + 'impacts', + 'online_purge_allowed', + 'online_purge_limit', + 'blocking_reason', + }, + AdoptionRootModel: {'resource_type', 'resource_ids'}, + AdoptionPreviewRequestModel: {'roots'}, + AdoptionExecuteRequestModel: {'roots', 'batch_name', 'preview_token'}, + PurgeExecuteRequestModel: {'batch_name', 'preview_token'}, +} + WRITE_MODELS = ( StrainModel, DeleteStrainModel, @@ -761,7 +842,7 @@ PACKAGE_MODEL_PAYLOADS = { class BusinessVoContractTest(unittest.TestCase): def test_all_models_have_exact_fields_and_camel_case_aliases(self) -> None: - expected_models = MASTER_MODEL_FIELDS | OPERATION_MODEL_FIELDS | REPORT_MODEL_FIELDS + expected_models = MASTER_MODEL_FIELDS | OPERATION_MODEL_FIELDS | REPORT_MODEL_FIELDS | TEST_BATCH_MODEL_FIELDS for model_class, expected_fields in expected_models.items(): with self.subTest(model=model_class.__name__): @@ -777,6 +858,44 @@ class BusinessVoContractTest(unittest.TestCase): with self.subTest(model=model_class.__name__): self.assertEqual('forbid', model_class.model_config.get('extra')) + def test_all_test_batch_models_forbid_extra_fields(self) -> None: + for model_class in TEST_BATCH_MODEL_FIELDS: + with self.subTest(model=model_class.__name__): + self.assertEqual('forbid', model_class.model_config.get('extra')) + + def test_test_batch_models_accept_snake_case_and_camel_case(self) -> None: + camel_case = TestBatchCreateModel(batchName='验收批次', remark='camel') + snake_case = TestBatchCreateModel(batch_name='验收批次', remark='snake') + self.assertEqual('验收批次', camel_case.batch_name) + self.assertEqual('验收批次', snake_case.batch_name) + + def test_test_batch_edit_forbids_immutable_fields(self) -> None: + for field_name, value in (('batchName', '新名称'), ('status', 'CLOSED')): + with self.subTest(field=field_name), self.assertRaises(ValidationError): + TestBatchEditModel.model_validate({'remark': None, field_name: value}) + + def test_test_batch_positive_ids_and_non_empty_roots_are_validated(self) -> None: + with self.assertRaises(ValidationError): + TestBatchUserAssignModel(userIds=[0]) + with self.assertRaises(ValidationError): + AdoptionRootModel(resourceType='strain', resourceIds=[]) + with self.assertRaises(ValidationError): + AdoptionPreviewRequestModel(roots=[]) + + def test_test_batch_status_rejects_unknown_value(self) -> None: + with self.assertRaises(ValidationError) as context: + TestBatchModel( + batchId=1, + batchCode='TB-1', + batchName='验收批次', + status='UNKNOWN', + createBy='admin', + createTime=datetime(2026, 9, 22), + updateBy='admin', + updateTime=datetime(2026, 9, 22), + ) + self.assertIn(('status',), {tuple(error['loc']) for error in context.exception.errors()}) + def test_page_queries_have_positive_defaults(self) -> None: for model_class in PAGE_QUERY_MODELS: with self.subTest(model=model_class.__name__):