This commit is contained in:
zlwu committed 2026-05-19 12:03:08 +08:00
1 parent f269a994f4
commit 93cd506a90
71 files changed
+8297 -2207

No files matched your search

@@ -0,0 +1,21 @@
-- 细胞存储采集协议 ⇄ 科研捐赠 转移关联(一对一)
CREATE TABLE IF NOT EXISTS `biz_protocol_donation_convert` (
`id` varchar(64) NOT NULL COMMENT '主键ID',
`convert_type` varchar(32) NOT NULL COMMENT 'storage_to_research/research_to_storage',
`protocol_id` varchar(64) NOT NULL COMMENT '采集协议单ID',
`research_donation_id` varchar(64) NOT NULL COMMENT '科研捐赠单ID',
`biz_no` varchar(64) NOT NULL COMMENT '转移完成时共用编号快照',
`tenant_no` varchar(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号',
`order_num` int(11) DEFAULT 0 COMMENT '排序号',
`remark` varchar(512) DEFAULT NULL COMMENT '备注',
`create_by` varchar(64) DEFAULT NULL COMMENT '创建人id',
`create_dept_id` varchar(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT NULL COMMENT '更新人id',
`update_time` datetime NOT NULL COMMENT '更新时间',
`del_flag` tinyint(4) NOT NULL DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (`id`),
KEY `idx_pdc_protocol` (`protocol_id`, `del_flag`),
KEY `idx_pdc_donation` (`research_donation_id`, `del_flag`),
KEY `idx_pdc_biz_no` (`tenant_no`, `biz_no`, `del_flag`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='细胞存储协议与科研捐赠转移关联';
+8
View File
@@ -0,0 +1,8 @@
-- 细胞存储管理二级菜单:重命名 + 排序(已执行 zkxw.sql 的环境可单独运行)
-- 顺序:采集协议单录入、采集协议单管理、缴费信息、缴费确认(财务)、退费确认(财务)
UPDATE `sys_menu` SET `menu_name` = '采集协议单录入', `order_num` = 1 WHERE `id` = '50000000000000000000000000000002';
UPDATE `sys_menu` SET `menu_name` = '采集协议单管理', `order_num` = 2 WHERE `id` = '50000000000000000000000000000003';
UPDATE `sys_menu` SET `order_num` = 3 WHERE `id` = '904b5173519411f1944300163e3bdd96';
UPDATE `sys_menu` SET `order_num` = 4 WHERE `id` = '50000000000000000000000000000004';
UPDATE `sys_menu` SET `order_num` = 5 WHERE `id` = '50000000000000000000000000000005';
+13 -10
View File
@@ -11,6 +11,7 @@ CREATE TABLE IF NOT EXISTS `biz_consumable` (
`consumable_name` varchar(100) NOT NULL COMMENT '耗材名称',
`spec_model` varchar(200) DEFAULT NULL COMMENT '规格型号',
`standard_usage` varchar(100) DEFAULT NULL COMMENT '标准用量',
`unit` varchar(64) DEFAULT NULL COMMENT '单位',
`description` varchar(500) DEFAULT NULL COMMENT '描述',
`tenant_no` varchar(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号',
`order_num` int(11) DEFAULT NULL COMMENT '排序号',
@@ -144,6 +145,7 @@ CREATE TABLE IF NOT EXISTS `biz_consumable_pack_item` (
`consumable_name` varchar(100) DEFAULT NULL COMMENT '耗材名称',
`spec_model` varchar(200) DEFAULT NULL COMMENT '耗材规格型号',
`standard_usage` varchar(100) DEFAULT NULL COMMENT '标准用量',
`unit` varchar(64) DEFAULT NULL COMMENT '单位',
`remark` varchar(512) DEFAULT NULL COMMENT '备注',
`tenant_no` varchar(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号',
`order_num` int(11) DEFAULT NULL COMMENT '排序号',
@@ -219,6 +221,7 @@ CREATE TABLE IF NOT EXISTS `biz_process_consumable` (
`consumable_name` varchar(100) DEFAULT NULL COMMENT '耗材名称',
`spec_model` varchar(200) DEFAULT NULL COMMENT '耗材规格型号',
`standard_usage` varchar(100) DEFAULT NULL COMMENT '标准用量',
`unit` varchar(64) DEFAULT NULL COMMENT '单位',
`is_required` char(1) DEFAULT '1' COMMENT '是否必需(1-必须 0-可选)',
`remark` varchar(512) DEFAULT NULL COMMENT '备注',
`tenant_no` varchar(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号',
@@ -269,13 +272,13 @@ INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `co
INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES
('90000000000000000000000000000002', '耗材管理', '90000000000000000000000000000001', 'C', 'consumable', '/production/consumable/index', 'production:consumable:list', 1, '1', '1', 'zkxw', 1, NULL, NULL, '', '2026-05-18 00:00:00', NULL, '2026-05-18 00:00:00', 0);
-- 耗材包管理
INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES
('90000000000000000000000000000007', '耗材包管理', '90000000000000000000000000000001', 'C', 'consumable-pack', '/production/consumable-pack/index', 'production:consumable-pack:list', 1, '1', '1', 'zkxw', 2, NULL, NULL, '', '2026-05-18 00:00:00', NULL, '2026-05-18 00:00:00', 0);
-- 试剂管理
INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES
('90000000000000000000000000000003', '试剂管理', '90000000000000000000000000000001', 'C', 'reagent', '/production/reagent/index', 'production:reagent:list', 1, '1', '1', 'zkxw', 2, NULL, NULL, '', '2026-05-18 00:00:00', NULL, '2026-05-18 00:00:00', 0);
-- SOP文档
INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES
('90000000000000000000000000000004', 'SOP文档', '90000000000000000000000000000001', 'C', 'sop', '/production/sop/index', 'production:sop:list', 1, '1', '1', 'zkxw', 3, NULL, NULL, '', '2026-05-18 00:00:00', NULL, '2026-05-18 00:00:00', 0);
('90000000000000000000000000000003', '试剂管理', '90000000000000000000000000000001', 'C', 'reagent', '/production/reagent/index', 'production:reagent:list', 1, '1', '1', 'zkxw', 3, NULL, NULL, '', '2026-05-18 00:00:00', NULL, '2026-05-18 00:00:00', 0);
-- 工艺工序分类
INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES
@@ -285,10 +288,10 @@ INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `co
INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES
('90000000000000000000000000000006', '工艺产出物类型', '90000000000000000000000000000001', 'C', 'output-type', '/production/output-type/index', 'production:output-type:list', 1, '1', '1', 'zkxw', 5, NULL, NULL, '', '2026-05-18 00:00:00', NULL, '2026-05-18 00:00:00', 0);
-- 耗材包管理
INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES
('90000000000000000000000000000007', '耗材包管理', '90000000000000000000000000000001', 'C', 'consumable-pack', '/production/consumable-pack/index', 'production:consumable-pack:list', 1, '1', '1', 'zkxw', 6, NULL, NULL, '', '2026-05-18 00:00:00', NULL, '2026-05-18 00:00:00', 0);
-- 工艺库管理
INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES
('90000000000000000000000000000008', '工艺库管理', '90000000000000000000000000000001', 'C', 'process', '/production/process/index', 'production:process:list', 1, '1', '1', 'zkxw', 7, NULL, NULL, '', '2026-05-18 00:00:00', NULL, '2026-05-18 00:00:00', 0);
('90000000000000000000000000000008', '工艺库管理', '90000000000000000000000000000001', 'C', 'process', '/production/process/index', 'production:process:list', 1, '1', '1', 'zkxw', 6, NULL, NULL, '', '2026-05-18 00:00:00', NULL, '2026-05-18 00:00:00', 0);
-- SOP文档
INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES
('90000000000000000000000000000004', 'SOP文档', '90000000000000000000000000000001', 'C', 'sop', '/production/sop/index', 'production:sop:list', 1, '1', '1', 'zkxw', 7, NULL, NULL, '', '2026-05-18 00:00:00', NULL, '2026-05-18 00:00:00', 0);
@@ -0,0 +1,9 @@
-- 生产耗材相关表增加「单位」字段(与试剂 unit 一致,数据来自物品单位管理)
ALTER TABLE `biz_consumable`
ADD COLUMN `unit` varchar(64) DEFAULT NULL COMMENT '单位' AFTER `standard_usage`;
ALTER TABLE `biz_consumable_pack_item`
ADD COLUMN `unit` varchar(64) DEFAULT NULL COMMENT '单位' AFTER `standard_usage`;
ALTER TABLE `biz_process_consumable`
ADD COLUMN `unit` varchar(64) DEFAULT NULL COMMENT '单位' AFTER `standard_usage`;
+10
View File
@@ -0,0 +1,10 @@
-- 生产管理二级菜单排序调整(已执行 production.sql 的环境可单独运行)
-- 顺序:耗材管理、耗材包管理、试剂管理、工艺工序分类、工艺产出物类型、工艺库管理、SOP文档
UPDATE `sys_menu` SET `order_num` = 1 WHERE `id` = '90000000000000000000000000000002';
UPDATE `sys_menu` SET `order_num` = 2 WHERE `id` = '90000000000000000000000000000007';
UPDATE `sys_menu` SET `order_num` = 3 WHERE `id` = '90000000000000000000000000000003';
UPDATE `sys_menu` SET `order_num` = 4 WHERE `id` = '90000000000000000000000000000005';
UPDATE `sys_menu` SET `order_num` = 5 WHERE `id` = '90000000000000000000000000000006';
UPDATE `sys_menu` SET `order_num` = 6 WHERE `id` = '90000000000000000000000000000008';
UPDATE `sys_menu` SET `order_num` = 7 WHERE `id` = '90000000000000000000000000000004';
@@ -0,0 +1,23 @@
-- ---------------------------------------------------------------------------
-- 工艺数据采集项 biz_process_data_collection
-- 用途:在工艺库中配置该工艺需要采集的数据信息(名称、单位)
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS `biz_process_data_collection` (
`id` varchar(64) NOT NULL COMMENT '主键ID',
`process_id` varchar(64) NOT NULL COMMENT '工艺id',
`collection_name` varchar(200) NOT NULL COMMENT '采集项名称',
`unit` varchar(64) DEFAULT NULL COMMENT '单位',
`tenant_no` varchar(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号',
`order_num` int(11) DEFAULT NULL COMMENT '排序号',
`remark` varchar(512) DEFAULT NULL COMMENT '备注',
`create_by` varchar(64) DEFAULT NULL COMMENT '创建人id',
`create_dept_id` varchar(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` varchar(64) DEFAULT NULL COMMENT '更新人id',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`del_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记:0-正常 1-已删除',
PRIMARY KEY (`id`),
KEY `idx_pdc_process` (`process_id`, `del_flag`),
KEY `idx_pdc_tenant` (`tenant_no`, `del_flag`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='工艺数据采集项';
@@ -0,0 +1,6 @@
-- 科研捐赠管理二级菜单名称调整(已执行 research_donation_tables.sql 的环境可单独运行)
-- 捐赠协议单 → 捐赠协议单录入
-- 捐赠协议审核 → 捐赠协议单管理
UPDATE `sys_menu` SET `menu_name` = '捐赠协议单录入', `update_time` = NOW() WHERE `id` = '60000000000000000000000000000002' AND `del_flag` = 0;
UPDATE `sys_menu` SET `menu_name` = '捐赠协议单管理', `update_time` = NOW() WHERE `id` = '60000000000000000000000000000003' AND `del_flag` = 0;
+2 -2
View File
@@ -171,6 +171,6 @@ CREATE TABLE IF NOT EXISTS `biz_research_donation_sample_transport` (
INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('60000000000000000000000000000001', '科研捐赠管理', '2', 'M', '/research-donation', '/index/index', NULL, 1, '1', '1', 'zkxw', 5, NULL, NULL, '', NOW(), NULL, NOW(), 0);
INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('60000000000000000000000000000002', '捐赠协议单', '60000000000000000000000000000001', 'C', 'list', '/research-donation/list/index', 'biz:researchDonation:list', 1, '1', '1', 'zkxw', 1, NULL, NULL, '', NOW(), NULL, NOW(), 0);
INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('60000000000000000000000000000002', '捐赠协议单录入', '60000000000000000000000000000001', 'C', 'list', '/research-donation/list/index', 'biz:researchDonation:list', 1, '1', '1', 'zkxw', 1, NULL, NULL, '', NOW(), NULL, NOW(), 0);
INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('60000000000000000000000000000003', '捐赠协议审核', '60000000000000000000000000000001', 'C', 'audit', '/research-donation/audit/index', 'biz:researchDonationAudit:list', 1, '1', '1', 'zkxw', 2, NULL, NULL, '', NOW(), NULL, NOW(), 0);
INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('60000000000000000000000000000003', '捐赠协议单管理', '60000000000000000000000000000001', 'C', 'audit', '/research-donation/audit/index', 'biz:researchDonationAudit:list', 1, '1', '1', 'zkxw', 2, NULL, NULL, '', NOW(), NULL, NOW(), 0);
+155
View File
@@ -0,0 +1,155 @@
-- ============================================================
-- 生产管理模块基础测试数据
-- 涵盖:耗材 → 试剂 → 工序分类 → 产出类型 → 耗材包(含明细)
-- 租户编号:zkxw
-- 执行前若需重复导入,可先执行文末「清理脚本」
-- ============================================================
-- ----------------------------
-- 1. 生产耗材(8 条)
-- ----------------------------
INSERT INTO `biz_consumable`
(`id`, `consumable_code`, `consumable_name`, `spec_model`, `standard_usage`, `unit`, `description`,
`tenant_no`, `order_num`, `remark`,
`create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`)
VALUES
('prod_cons_001', 'HC001', 'T25 细胞培养瓶', '25cm²', '1', '个', '贴壁细胞常规培养',
'zkxw', 1, NULL, NULL, '', NOW(), NULL, NOW(), 0),
('prod_cons_002', 'HC002', '15mL 离心管', '无菌', '2', '支', '细胞传代离心分装',
'zkxw', 2, NULL, NULL, '', NOW(), NULL, NOW(), 0),
('prod_cons_003', 'HC003', '10mL 移液管', '无菌独立包装', '1', '支', '培养基与试剂移液',
'zkxw', 3, NULL, NULL, '', NOW(), NULL, NOW(), 0),
('prod_cons_004', 'HC004', '0.22μm 针头滤器', '33mm', '1', '个', '培养基除菌过滤',
'zkxw', 4, NULL, NULL, '', NOW(), NULL, NOW(), 0),
('prod_cons_005', 'HC005', '细胞刮刀', '18cm', '1', '把', '贴壁细胞消化前刮取',
'zkxw', 5, NULL, NULL, '', NOW(), NULL, NOW(), 0),
('prod_cons_006', 'HC006', '冻存管', '2mL 外旋盖', '6', '支', '细胞冻存分装',
'zkxw', 6, NULL, NULL, '', NOW(), NULL, NOW(), 0),
('prod_cons_007', 'HC007', '细胞计数板', '血球计数板', '1', '块', '台盼蓝计数用',
'zkxw', 7, NULL, NULL, '', NOW(), NULL, NOW(), 0),
('prod_cons_008', 'HC008', '一次性无菌手套', 'M 码', '1', '双', '操作防护',
'zkxw', 8, NULL, NULL, '', NOW(), NULL, NOW(), 0);
-- ----------------------------
-- 2. 试剂(6 条)
-- ----------------------------
INSERT INTO `biz_reagent`
(`id`, `reagent_code`, `reagent_name`, `spec`, `standard_usage`, `unit`, `description`,
`tenant_no`, `order_num`, `remark`,
`create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`)
VALUES
('prod_reag_001', 'SJ001', 'DMEM 高糖培养基', '500mL', '10', 'mL', '基础培养基',
'zkxw', 1, NULL, NULL, '', NOW(), NULL, NOW(), 0),
('prod_reag_002', 'SJ002', '胎牛血清 FBS', '500mL', '1', 'mL', '培养基添加剂',
'zkxw', 2, NULL, NULL, '', NOW(), NULL, NOW(), 0),
('prod_reag_003', 'SJ003', '0.25% 胰酶-EDTA', '100mL', '2', 'mL', '细胞消化',
'zkxw', 3, NULL, NULL, '', NOW(), NULL, NOW(), 0),
('prod_reag_004', 'SJ004', 'PBS 缓冲液', '500mL', '5', 'mL', '细胞洗涤',
'zkxw', 4, NULL, NULL, '', NOW(), NULL, NOW(), 0),
('prod_reag_005', 'SJ005', '台盼蓝染液', '10mL', '0.1', 'mL', '死活细胞染色计数',
'zkxw', 5, NULL, NULL, '', NOW(), NULL, NOW(), 0),
('prod_reag_006', 'SJ006', 'DMSO 细胞冻存液', '100mL', '0.5', 'mL', '冻存保护剂',
'zkxw', 6, NULL, NULL, '', NOW(), NULL, NOW(), 0);
-- ----------------------------
-- 3. 工艺工序分类(5 条)
-- ----------------------------
INSERT INTO `biz_process_category`
(`id`, `category_code`, `category_name`,
`tenant_no`, `order_num`, `remark`,
`create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`)
VALUES
('prod_pcat_001', 'GXFL001', '细胞复苏', 'zkxw', 1, '从液氮取出至接种前', NULL, '', NOW(), NULL, NOW(), 0),
('prod_pcat_002', 'GXFL002', '细胞传代', 'zkxw', 2, '贴壁细胞常规扩增', NULL, '', NOW(), NULL, NOW(), 0),
('prod_pcat_003', 'GXFL003', '细胞冻存', 'zkxw', 3, '液氮冻存入库', NULL, '', NOW(), NULL, NOW(), 0),
('prod_pcat_004', 'GXFL004', '收获与制剂', 'zkxw', 4, '上清收获或细胞收集', NULL, '', NOW(), NULL, NOW(), 0),
('prod_pcat_005', 'GXFL005', '质检取样', 'zkxw', 5, '留样与送检取样', NULL, '', NOW(), NULL, NOW(), 0);
-- ----------------------------
-- 4. 工艺产出物类型(5 条)
-- ----------------------------
INSERT INTO `biz_output_type`
(`id`, `type_code`, `type_name`,
`tenant_no`, `order_num`, `remark`,
`create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`)
VALUES
('prod_otype_001', 'CCLX001', '活细胞', 'zkxw', 1, '传代或扩增后的细胞悬液', NULL, '', NOW(), NULL, NOW(), 0),
('prod_otype_002', 'CCLX002', '冻存细胞', 'zkxw', 2, '液氮冻存管', NULL, '', NOW(), NULL, NOW(), 0),
('prod_otype_003', 'CCLX003', '上清液', 'zkxw', 3, '培养上清产物', NULL, '', NOW(), NULL, NOW(), 0),
('prod_otype_004', 'CCLX004', '留样', 'zkxw', 4, '质检留样管', NULL, '', NOW(), NULL, NOW(), 0),
('prod_otype_005', 'CCLX005', '废料', 'zkxw', 5, '废弃培养物', NULL, '', NOW(), NULL, NOW(), 0);
-- ----------------------------
-- 5. 耗材包(4 条:2 标准 + 2 自定义)
-- ----------------------------
INSERT INTO `biz_consumable_pack`
(`id`, `pack_code`, `pack_name`, `usage_scene`, `pack_type`, `status`,
`tenant_no`, `order_num`, `remark`,
`create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`)
VALUES
('prod_pack_001', 'HCB001', '细胞传代标准包', '贴壁细胞 T25 传代', 'bzhcb', '1',
'zkxw', 1, '含培养瓶、离心管、移液管等', NULL, '', NOW(), NULL, NOW(), 0),
('prod_pack_002', 'HCB002', '细胞冻存标准包', '液氮冻存分装', 'bzhcb', '1',
'zkxw', 2, NULL, NULL, '', NOW(), NULL, NOW(), 0),
('prod_pack_003', 'HCB003', '复苏接种自定义包', '复苏后首次接种', 'zdyhcb', '1',
'zkxw', 3, '按项目定制', NULL, '', NOW(), NULL, NOW(), 0),
('prod_pack_004', 'HCB004', '质检取样包(停用示例)', '留样分装', 'zdyhcb', '0',
'zkxw', 4, '测试停用状态', NULL, '', NOW(), NULL, NOW(), 0);
-- ----------------------------
-- 6. 耗材包明细(关联上述耗材包与耗材编码)
-- ----------------------------
INSERT INTO `biz_consumable_pack_item`
(`id`, `pack_id`, `consumable_code`, `consumable_name`, `spec_model`, `standard_usage`, `unit`, `remark`,
`tenant_no`, `order_num`,
`create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`)
VALUES
-- 细胞传代标准包 HCB001
('prod_cpi_001', 'prod_pack_001', 'HC001', 'T25 细胞培养瓶', '25cm²', '1', '个', NULL,
'zkxw', 1, NULL, '', NOW(), NULL, NOW(), 0),
('prod_cpi_002', 'prod_pack_001', 'HC002', '15mL 离心管', '无菌', '2', '支', NULL,
'zkxw', 2, NULL, '', NOW(), NULL, NOW(), 0),
('prod_cpi_003', 'prod_pack_001', 'HC003', '10mL 移液管', '无菌独立包装', '1', '支', NULL,
'zkxw', 3, NULL, '', NOW(), NULL, NOW(), 0),
('prod_cpi_004', 'prod_pack_001', 'HC005', '细胞刮刀', '18cm', '1', '把', NULL,
'zkxw', 4, NULL, '', NOW(), NULL, NOW(), 0),
('prod_cpi_005', 'prod_pack_001', 'HC008', '一次性无菌手套', 'M 码', '1', '双', NULL,
'zkxw', 5, NULL, '', NOW(), NULL, NOW(), 0),
-- 细胞冻存标准包 HCB002
('prod_cpi_006', 'prod_pack_002', 'HC002', '15mL 离心管', '无菌', '2', '支', NULL,
'zkxw', 1, NULL, '', NOW(), NULL, NOW(), 0),
('prod_cpi_007', 'prod_pack_002', 'HC006', '冻存管', '2mL 外旋盖', '6', '支', NULL,
'zkxw', 2, NULL, '', NOW(), NULL, NOW(), 0),
('prod_cpi_008', 'prod_pack_002', 'HC008', '一次性无菌手套', 'M 码', '1', '双', NULL,
'zkxw', 3, NULL, '', NOW(), NULL, NOW(), 0),
-- 复苏接种自定义包 HCB003
('prod_cpi_009', 'prod_pack_003', 'HC001', 'T25 细胞培养瓶', '25cm²', '2', '个', '双瓶接种',
'zkxw', 1, NULL, '', NOW(), NULL, NOW(), 0),
('prod_cpi_010', 'prod_pack_003', 'HC004', '0.22μm 针头滤器', '33mm', '1', '个', NULL,
'zkxw', 2, NULL, '', NOW(), NULL, NOW(), 0),
('prod_cpi_011', 'prod_pack_003', 'HC007', '细胞计数板', '血球计数板', '1', '块', NULL,
'zkxw', 3, NULL, '', NOW(), NULL, NOW(), 0),
-- 质检取样包 HCB004(停用包,明细仍保留供查看)
('prod_cpi_012', 'prod_pack_004', 'HC002', '15mL 离心管', '无菌', '3', '支', NULL,
'zkxw', 1, NULL, '', NOW(), NULL, NOW(), 0),
('prod_cpi_013', 'prod_pack_004', 'HC006', '冻存管', '2mL 外旋盖', '2', '支', '留样管',
'zkxw', 2, NULL, '', NOW(), NULL, NOW(), 0);
-- ============================================================
-- 清理脚本(重复导入前可选执行,按 id 前缀删除)
-- ============================================================
-- DELETE FROM `biz_consumable_pack_item` WHERE `id` LIKE 'prod_cpi_%';
-- DELETE FROM `biz_consumable_pack` WHERE `id` LIKE 'prod_pack_%';
-- DELETE FROM `biz_output_type` WHERE `id` LIKE 'prod_otype_%';
-- DELETE FROM `biz_process_category` WHERE `id` LIKE 'prod_pcat_%';
-- DELETE FROM `biz_reagent` WHERE `id` LIKE 'prod_reag_%';
-- DELETE FROM `biz_consumable` WHERE `id` LIKE 'prod_cons_%';
+5 -5
View File
@@ -4872,10 +4872,10 @@ INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `co
INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('30000000000000000000000000000001', '工作流管理', '2', 'M', '/workflow', '/index/index', NULL, 1, '1', '1', 'zkxw', 5, NULL, NULL, '', '2026-05-14 00:00:00', NULL, '2026-05-14 00:00:00', 0);
INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('4760DB762DA842DABA41C0687D6F418F', '2', 'A68CEECA9A7543B29DF9FCF6BE0801A2', 'M', '12', NULL, NULL, 1, '1', '1', 'zkxw', 0, '12', 'd3000000000000000000000000000001', '', '2026-05-14 15:27:55', 'd3000000000000000000000000000001', '2026-05-14 15:27:59', 1);
INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('50000000000000000000000000000001', '细胞存储管理', '2', 'M', '/cell-storage', '/index/index', NULL, 1, '1', '1', 'zkxw', 4, NULL, NULL, '', '2026-05-14 00:00:00', NULL, '2026-05-14 00:00:00', 0);
INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('50000000000000000000000000000002', '采集协议单', '50000000000000000000000000000001', 'C', 'protocol', '/cell-storage/protocol/index', 'biz:cellStorageProtocol:list', 1, '1', '1', 'zkxw', 1, NULL, NULL, '', '2026-05-14 00:00:00', NULL, '2026-05-14 00:00:00', 0);
INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('50000000000000000000000000000003', '采集协议单审核', '50000000000000000000000000000001', 'C', 'protocol-audit', '/cell-storage/protocol-audit/index', 'biz:cellStorageProtocolAudit:list', 1, '1', '1', 'zkxw', 2, NULL, NULL, '', '2026-05-14 00:00:00', NULL, '2026-05-14 00:00:00', 0);
INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('50000000000000000000000000000004', '缴费确认(财务)', '50000000000000000000000000000001', 'C', 'payment-confirm', '/cell-storage/payment-confirm/index', 'biz:cellStoragePaymentConfirm:list', 1, '1', '1', 'zkxw', 3, NULL, NULL, '', '2026-05-14 00:00:00', NULL, '2026-05-17 11:23:39', 0);
INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('50000000000000000000000000000005', '退费确认(财务)', '50000000000000000000000000000001', 'C', 'refund-confirm', '/cell-storage/refund-confirm/index', 'biz:cellStorageRefundConfirm:list', 1, '1', '1', 'zkxw', 4, NULL, NULL, '', '2026-05-14 00:00:00', NULL, '2026-05-17 11:23:39', 0);
INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('50000000000000000000000000000002', '采集协议单录入', '50000000000000000000000000000001', 'C', 'protocol', '/cell-storage/protocol/index', 'biz:cellStorageProtocol:list', 1, '1', '1', 'zkxw', 1, NULL, NULL, '', '2026-05-14 00:00:00', NULL, '2026-05-14 00:00:00', 0);
INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('50000000000000000000000000000003', '采集协议单管理', '50000000000000000000000000000001', 'C', 'protocol-audit', '/cell-storage/protocol-audit/index', 'biz:cellStorageProtocolAudit:list', 1, '1', '1', 'zkxw', 2, NULL, NULL, '', '2026-05-14 00:00:00', NULL, '2026-05-14 00:00:00', 0);
INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('50000000000000000000000000000004', '缴费确认(财务)', '50000000000000000000000000000001', 'C', 'payment-confirm', '/cell-storage/payment-confirm/index', 'biz:cellStoragePaymentConfirm:list', 1, '1', '1', 'zkxw', 4, NULL, NULL, '', '2026-05-14 00:00:00', NULL, '2026-05-17 11:23:39', 0);
INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('50000000000000000000000000000005', '退费确认(财务)', '50000000000000000000000000000001', 'C', 'refund-confirm', '/cell-storage/refund-confirm/index', 'biz:cellStorageRefundConfirm:list', 1, '1', '1', 'zkxw', 5, NULL, NULL, '', '2026-05-14 00:00:00', NULL, '2026-05-17 11:23:39', 0);
INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('50000000000000000000000000000006', '分期缴费预警', '50000000000000000000000000000001', 'C', 'installment-warning', '/cell-storage/installment-warning/index', 'biz:cellStorageInstallmentWarning:list', 1, '1', '1', 'zkxw', 5, NULL, NULL, '', '2026-05-14 00:00:00', NULL, '2026-05-17 12:07:03', 1);
INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('70000000000000000000000000000001', '质量检测', '2', 'M', '/quality-inspection', '/index/index', NULL, 1, '1', '1', 'zkxw', 4, NULL, NULL, '', '2026-05-17 16:22:32', NULL, '2026-05-17 16:22:32', 0);
INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('70000000000000000000000000000002', '样本接收', '70000000000000000000000000000001', 'C', 'sample-reception', '/quality-inspection/sample-reception/index', 'biz:sampleReception:list', 1, '1', '1', 'zkxw', 1, NULL, NULL, '', '2026-05-17 16:22:32', NULL, '2026-05-17 16:22:32', 0);
@@ -4884,7 +4884,7 @@ INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `co
INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('70000000000000000000000000000006', '实验室自检项目', '70000000000000000000000000000001', 'C', 'lab-test-item', '/quality-inspection/lab-test-item/index', 'biz:qcLabTestItem:list', 1, '1', '1', 'zkxw', 5, NULL, NULL, '', '2026-05-17 19:37:18', NULL, '2026-05-17 19:37:18', 0);
INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('70000000000000000000000000000007', '质检工单', '70000000000000000000000000000001', 'C', 'qc-work-order', '/quality-inspection/qc-work-order/index', 'biz:qcWorkOrder:list', 1, '1', '1', 'zkxw', 6, NULL, NULL, '', '2026-05-17 19:37:18', NULL, '2026-05-17 19:37:18', 0);
INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('70000000000000000000000000000008', '质检任务', '70000000000000000000000000000001', 'C', 'qc-task', '/quality-inspection/qc-task/index', 'biz:qcTask:list', 1, '1', '1', 'zkxw', 7, NULL, NULL, '', '2026-05-17 19:37:18', NULL, '2026-05-17 19:37:18', 0);
INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('904b5173519411f1944300163e3bdd96', '缴费信息', '50000000000000000000000000000001', 'C', 'payment-info', '/cell-storage/payment-info/index', 'cellStorage:paymentInfo:list', 1, '1', '1', 'zkxw', 2, NULL, NULL, '', '2026-05-17 10:03:14', NULL, '2026-05-17 10:03:14', 0);
INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('904b5173519411f1944300163e3bdd96', '缴费信息', '50000000000000000000000000000001', 'C', 'payment-info', '/cell-storage/payment-info/index', 'cellStorage:paymentInfo:list', 1, '1', '1', 'zkxw', 3, NULL, NULL, '', '2026-05-17 10:03:14', NULL, '2026-05-17 10:03:14', 0);
INSERT INTO `sys_menu` (`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`, `is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`) VALUES ('A68CEECA9A7543B29DF9FCF6BE0801A2', '1', '2', 'M', '1', NULL, NULL, 1, '1', '1', 'zkxw', 0, '1', 'd3000000000000000000000000000001', '', '2026-05-14 15:27:46', 'd3000000000000000000000000000001', '2026-05-14 15:28:01', 1);
COMMIT;
@@ -0,0 +1,322 @@
# 工艺路线管理模块设计规格
## 概述
实现需求文档 2.3.8 节「工艺路线配置」。在生产管理菜单下的「工艺库管理」之后新增「工艺路线管理」菜单。
核心功能:维护工艺路线基础信息(路线编码、名称、产品类型、版本号),支持同编码多版本管理(轻量版:is_current 标记当前版本),并通过拖拽式三栏弹窗配置路线中的工艺步骤和子表数据。
## 设计决策
| 决策项 | 选择 | 理由 |
|--------|------|------|
| 版本号 | 同编码多版本 + is_current 标记当前版本 | 轻量版:同 route_code 可有多条记录,仅一个可标记为当前版本。无发布/草稿状态机 |
| 子表范围 | 完整复刻4张子表(试剂/耗材/产出物/数据采集) | 从工艺库拖入时克隆默认数据,用户可覆盖编辑 |
| 排列模式 | 线性排列(A→B→C→D) | 卡片从上到下,无需分支逻辑 |
| 可编辑范围 | 全部字段可编辑(基础信息+4张子表) | 右侧面板展示完整工艺信息供修改 |
| 保存方式 | 实时保存,拖入即创建后端记录 | 每步操作即时持久化,弹窗关闭无需额外保存 |
| UI方案 | 方案A:单弹窗 + vue-draggable-plus | 项目中已有3处成熟用法,跨栏拖拽通过HTML5 Drag API组合实现 |
| 步骤删除 | 支持删除,软删除步骤+级联软删除子表 | 中栏卡片上有删除按钮,确认后删除 |
## 依赖
| 依赖模块 | 用途 | 状态 |
|----------|------|------|
| 细胞产品类型管理(biz_cell_product_type) | 产品类型下拉 | 已实现 |
| 工艺工序分类(biz_process_category) | 工艺分组展示 | 已实现 |
| 工艺库(biz_process + 4张子表) | 拖入时的数据源和克隆来源 | 已实现 |
| SOP文档(biz_sop_document) | 步骤关联SOP | 已实现 |
| vue-draggable-plus@0.6.0 | 卡片排序拖拽 | 已安装 |
| sortablejs@1.15.7 | vue-draggable-plus 底层引擎 | 已安装 |
## SQL 表(6 张新建)
所有表遵循项目公共字段规范:`id` VARCHAR(64) UUID → 业务字段 → `tenant_no` → `order_num` → `remark` → `create_by` → `create_dept_id` → `create_time` → `update_by` → `update_time` → `del_flag`。
### 1. biz_process_route — 工艺路线主表
| 字段 | 类型 | 说明 |
|------|------|------|
| id | VARCHAR(64) PK | UUID |
| route_code | VARCHAR(64) NOT NULL | 路线编码 |
| route_name | VARCHAR(100) NOT NULL | 路线名称 |
| product_type_id | VARCHAR(64) DEFAULT NULL | 细胞产品类型ID(引用 biz_cell_product_type) |
| version | VARCHAR(50) DEFAULT NULL | 版本号(用户手填文本,如 V1.0、V2.0) |
| is_current | CHAR(1) DEFAULT '0' | 是否当前版本(0-否 1-是)。同 route_code 下仅一条记录 is_current=1 |
| + 公共字段 | | |
索引:`idx_route_tenant(tenant_no, del_flag)`
### 2. biz_process_route_step — 路线工序步骤
| 字段 | 类型 | 说明 |
|------|------|------|
| id | VARCHAR(64) PK | UUID |
| route_id | VARCHAR(64) NOT NULL | 所属路线ID |
| process_id | VARCHAR(64) DEFAULT NULL | 来源工艺库工艺ID(工艺被删时可null) |
| step_order | INT DEFAULT NULL | 步骤顺序(拖拽排序时自动计算) |
| process_code | VARCHAR(64) DEFAULT NULL | 工序代码(从工艺库克隆,可编辑) |
| category_id | VARCHAR(64) DEFAULT NULL | 工序分类ID(可编辑) |
| process_name | VARCHAR(100) DEFAULT NULL | 工序名称(可编辑) |
| description | VARCHAR(500) DEFAULT NULL | 描述(可编辑) |
| equipment_name | VARCHAR(200) DEFAULT NULL | 预计使用设备(可编辑) |
| estimated_duration | VARCHAR(100) DEFAULT NULL | 预估耗时(可编辑) |
| sop_ids | VARCHAR(1000) DEFAULT NULL | 关联SOP文档ID(可编辑,逗号分隔) |
| + 公共字段 | | |
索引:`idx_prs_route(route_id, del_flag)`、`idx_prs_tenant(tenant_no, del_flag)`
### 3. biz_process_route_step_reagent — 步骤试剂
字段完全镜像 `biz_process_reagent`,但外键改为 `step_id`(关联 biz_process_route_step.id):
| 字段 | 类型 | 说明 |
|------|------|------|
| id | VARCHAR(64) PK | UUID |
| step_id | VARCHAR(64) NOT NULL | 所属步骤ID |
| reagent_code | VARCHAR(64) DEFAULT NULL | 试剂编码 |
| reagent_name | VARCHAR(100) DEFAULT NULL | 试剂名称 |
| spec | VARCHAR(200) DEFAULT NULL | 规格 |
| standard_usage | VARCHAR(100) DEFAULT NULL | 标准用量 |
| unit | VARCHAR(64) DEFAULT NULL | 单位 |
| is_key_material | CHAR(1) DEFAULT '0' | 是否关键物料(0-否 1-是) |
| + 公共字段 | | |
索引:`idx_prsr_step(step_id, del_flag)`
### 4. biz_process_route_step_consumable — 步骤耗材
字段完全镜像 `biz_process_consumable`,外键改为 `step_id`:
| 字段 | 类型 | 说明 |
|------|------|------|
| id | VARCHAR(64) PK | UUID |
| step_id | VARCHAR(64) NOT NULL | 所属步骤ID |
| consumable_code | VARCHAR(64) DEFAULT NULL | 耗材编码 |
| consumable_name | VARCHAR(100) DEFAULT NULL | 耗材名称 |
| spec_model | VARCHAR(200) DEFAULT NULL | 规格型号 |
| standard_usage | VARCHAR(100) DEFAULT NULL | 标准用量 |
| unit | VARCHAR(64) DEFAULT NULL | 单位 |
| is_required | CHAR(1) DEFAULT '1' | 是否必需(1-必须 0-可选) |
| remark | VARCHAR(512) DEFAULT NULL | 备注 |
| + 公共字段(不含 remark) | | |
索引:`idx_prsc_step(step_id, del_flag)`
### 5. biz_process_route_step_output — 步骤产出物
字段完全镜像 `biz_process_output`,外键改为 `step_id`:
| 字段 | 类型 | 说明 |
|------|------|------|
| id | VARCHAR(64) PK | UUID |
| step_id | VARCHAR(64) NOT NULL | 所属步骤ID |
| output_type_id | VARCHAR(64) DEFAULT NULL | 产出类型ID |
| output_name | VARCHAR(200) DEFAULT NULL | 产出物名称 |
| output_barcode | VARCHAR(200) DEFAULT NULL | 产出物条码 |
| + 公共字段 | | |
索引:`idx_prso_step(step_id, del_flag)`
### 6. biz_process_route_step_data_collection — 步骤数据采集
字段完全镜像 `biz_process_data_collection`,外键改为 `step_id`:
| 字段 | 类型 | 说明 |
|------|------|------|
| id | VARCHAR(64) PK | UUID |
| step_id | VARCHAR(64) NOT NULL | 所属步骤ID |
| collection_name | VARCHAR(100) DEFAULT NULL | 采集信息名称 |
| unit | VARCHAR(64) DEFAULT NULL | 单位 |
| + 公共字段 | | |
索引:`idx_prsdc_step(step_id, del_flag)`
### 菜单数据
在「工艺库管理」(order_num=6)之后、「SOP文档」(order_num=7)之前插入,SOP 文档 order_num 改为 8:
```sql
INSERT INTO sys_menu (id, menu_name, parent_id, menu_type, path, component, perms, is_frame, visible, status, tenant_no, order_num, remark, create_by, create_dept_id, create_time, update_by, update_time, del_flag)
VALUES ('90000000000000000000000000000010', '工艺路线管理', '90000000000000000000000000000001', 'C', 'process-route', '/production/process-route/index', 'production:process-route:list', 1, '1', '1', 'zkxw', 7, NULL, NULL, '', '2026-05-19 00:00:00', NULL, '2026-05-19 00:00:00', 0);
-- SOP文档 order_num 从 7 改为 8
UPDATE sys_menu SET order_num = 8 WHERE id = '90000000000000000000000000000004';
```
## 后端 API
路由前缀:`/api/admin/production/route/`
### 路线主表 CRUD
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/list` | 路线分页列表。支持筛选:route_name(模糊)、route_code(精确)、product_type_id。排序:order_num ASC, create_time DESC。带 tenant_no 隔离。 |
| POST | `/create` | 创建路线主表。必填:route_code, route_name。自动填充 tenant_no。默认 is_current=0。 |
| PUT | `/update` | 更新路线主表信息(编码、名称、产品类型、版本号、备注)。 |
| PUT | `/set-current` | 设置指定路线为当前版本。逻辑:先将同 route_code + tenant_no 下所有记录 is_current 置 0,再将目标记录 is_current 置 1。 |
| DELETE | `/delete/{id}` | 软删除路线(del_flag=1)+ 级联软删除所有步骤及步骤子表。 |
| GET | `/detail/{id}` | 获取路线详情:主表 + 步骤列表(按 step_order ASC)+ 每个步骤的4张子表数据。 |
### 步骤操作(实时保存)
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/step/add` | 从工艺库拖入创建步骤。入参:route_id, process_id, step_order。后端读取 biz_process 基础信息克隆到 step 表,读取4张子表数据批量克隆到 route_step_* 子表。返回完整 step 对象(含子表)。 |
| PUT | `/step/update` | 更新步骤基础信息(右侧面板字段修改时触发)。 |
| PUT | `/step/reorder` | 批量重排步骤顺序。入参:route_id, step_ids[](新顺序数组)。遍历更新 step_order。 |
| DELETE | `/step/delete/{id}` | 软删除步骤(del_flag=1)+ 级联软删除该步骤的4张子表数据。 |
| PUT | `/step/reagent/update` | 更新步骤试剂子表。入参:step_id, items[](完整替换)。 |
| PUT | `/step/consumable/update` | 更新步骤耗材子表。入参:step_id, items[](完整替换)。 |
| PUT | `/step/output/update` | 更新步骤产出子表。入参:step_id, items[](完整替换)。 |
| PUT | `/step/data-collection/update` | 更新步骤数据采集子表。入参:step_id, items[](完整替换)。 |
### 关键后端逻辑
**拖入克隆(step/add)**:
1. 校验 route_id 存在且属于当前租户
2. 从 `biz_process` 读取工艺基础信息
3. 插入 `biz_process_route_step`(克隆字段:process_code, category_id, process_name, description, equipment_name, estimated_duration, sop_ids)
4. 从 `biz_process_reagent` 等4张子表批量读取并插入对应 `biz_process_route_step_*`
5. 返回完整 step 对象
**级联软删除**:
- 删除路线 → 遍历所有步骤设 del_flag=1 → 遍历每步子表设 del_flag=1
- 删除步骤 → 遍历该步骤子表设 del_flag=1
**子表更新(完整替换策略)**:
- 接收 items[] 后,将现有记录 del_flag=1,然后批量插入新记录
- 避免逐条 diff,简化逻辑
## 前端设计
### 文件清单
| 文件 | 操作 | 说明 |
|------|------|------|
| `web-admin/src/views/production/process-route/index.vue` | 新建 | 路线列表页 + 路线图配置弹窗 |
| `web-admin/src/api/production.ts` | 追加 | route 相关 API 函数 |
| `web-admin/src/router/modules/sqxp.ts` | 追加 | process-route 路由条目 |
### 列表页
遵循标准列表模板(参考 `src/views/system/unit/index.vue`):
- **搜索栏**:路线名称(模糊)、路线编码(精确)、产品类型(下拉,数据来自细胞产品类型管理列表 API)
- **工具栏**:新增按钮(单一 primary)
- **表格列**:路线名称、路线编码、产品类型名称、版本号、当前版本(is_current=1 时显示绿色 `el-tag` 标签)、备注、创建时间
- **操作列**(fixed="right", min-width ≥ 260px):路线图配置、编辑、设为当前版本(仅 is_current=0 时显示)、删除
- **新增/编辑弹窗**:标准表单(label-position="top"),字段:路线编码、路线名称、产品类型(下拉)、版本号(文本)、备注
### 路线图配置弹窗
**弹窗属性**:`width="90vw"`,`destroy-on-close`,标题显示路线名称和版本号
**三栏布局**(flex):
```
┌──────────────────────────────────────────────────────────────┐
│ 路线图配置 - {路线名称} v{版本号} [关闭] │
├────────────┬──────────────────────┬──────────────────────────┤
│ 工艺库 │ 工艺流程布局 │ 工艺详情 │
│ (240px) │ (flex:1) │ (360px) │
│ │ │ │
│ [分类折叠] │ ┌────────────┐ │ 工序名称: [___] │
│ > 细胞培养 │ │ 步骤1: xxx │ ←拖 │ 工序分类: [下拉] │
│ [卡片] │ └────────────┘ │ 描述: [___] │
│ [卡片] │ ┌────────────┐ │ 设备: [___] │
│ > 病毒转染 │ │ 步骤2: xxx │ │ 预估耗时: [___] │
│ [卡片] │ └────────────┘ │ SOP: [选择] │
│ > ... │ │ ────────────── │
│ │ [+ 拖入工艺卡片] │ [试剂][耗材][产出][采集] │
│ │ │ (子表tab切换编辑) │
└────────────┴──────────────────────┴──────────────────────────┘
```
**左栏 — 工艺库面板(240px 固定宽度)**:
- 使用 `el-collapse` 按工序分类(biz_process_category)分组展示
- 每组内展示工艺小卡片:工序名称 + 编码
- 卡片使用 HTML5 `draggable="true"` + `@dragstart` 传递 `process_id`
- 无分类的工艺归入"未分类"组
- 仅展示当前租户未删除的工艺
**中栏 — 流程布局区(flex:1 自适应)**:
- 使用 `vue-draggable-plus`(VueDraggable 组件)包裹步骤卡片列表
- 每张步骤卡片显示:步骤序号(圆角标签)+ 工艺名称 + 操作按钮(删除图标)
- 支持内部排序拖拽(handle 拖拽手柄,使用 `<el-icon><Rank /></el-icon>`)
- 空白区域通过 `@dragover.prevent` + `@drop` 接收从左栏拖入的工艺卡片
- 拖入时调用 `POST /step/add`,即时创建后端记录
- 排序变化时调用 `PUT /step/reorder`
- 点击卡片 → 选中态(高亮边框)→ 触发右栏加载详情
- 卡片上的删除按钮 → 确认弹窗 → `DELETE /step/delete/{id}` → 从列表移除
**右栏 — 工艺详情编辑面板(360px 固定宽度)**:
- **顶部**:工艺基础信息表单
- 工序名称(el-input)
- 工序分类(el-select,下拉数据来自工艺工序分类)
- 描述(el-input textarea)
- 设备名称(el-input)
- 预估耗时(el-input)
- SOP文档(el-select multiple,数据来自 SOP 文档列表)
- 字段修改时 `@change` 触发 `PUT /step/update` 实时保存
- **底部**:4 个 el-tab-pane 切换子表编辑
- **试剂 tab**:表格列(试剂编码、名称、规格、标准用量、单位、是否关键物料),底部"添加"按钮新增行,每行有删除按钮。修改后实时保存 `PUT /step/reagent/update`
- **耗材 tab**:表格列(耗材编码、名称、规格型号、标准用量、单位、是否必需、备注),同上交互
- **产出 tab**:表格列(产出类型[下拉选择产出物类型]、名称、条码),同上交互
- **数据采集 tab**:表格列(采集名称、单位),同上交互
- 未选中任何步骤时显示空状态提示:"请从左侧工艺库拖入工艺,或点击已添加的步骤查看详情"
### 数据流
```
拖入流程:
左栏卡片 dragstart(process_id)
→ 中栏 drop 事件
→ POST /step/add {route_id, process_id, step_order: list.length + 1}
→ 后端克隆工艺+子表 → 返回完整 step
→ 前端追加到步骤列表
排序流程:
中栏内 vue-draggable-plus 拖拽排序
→ @end 回调
→ PUT /step/reorder {route_id, step_ids: [...]}
编辑流程:
右栏字段 @change
→ PUT /step/update {step_id, ...fields}
右栏子表增删改
→ PUT /step/{sub-table}/update {step_id, items: [...]}
删除步骤:
卡片删除按钮 → ElMessageBox.confirm
→ DELETE /step/delete/{step_id}
→ 前端从列表移除
点击步骤:
中栏卡片 click
→ 设置 selectedStepId
→ 右栏加载该步骤详情(从列表数据中取,无需额外 API)
```
## 实现文件清单
| 层 | 文件 | 操作 |
|----|------|------|
| SQL | `docs/sql/production_process_route.sql` | 新建:6张表DDL + 菜单INSERT + SOP order_num UPDATE |
| Model | `backend/app/models/models.py` | 追加 6 个 SQLAlchemy Model 类 |
| Model | `backend/app/models/__init__.py` | 追加 6 个 export |
| CRUD | `backend/app/crud/process_route.py` | 新建:路线CRUD + 步骤CRUD + 子表CRUD + 克隆逻辑 |
| API | `backend/app/api/production.py` | 追加 route 子路由(挂载到 production router 下) |
| Schema | `backend/app/schemas/process_route.py` | 新建:Pydantic v2 schemas |
| 前端 API | `web-admin/src/api/production.ts` | 追加 route 相关函数(~15个 API 函数) |
| 前端页面 | `web-admin/src/views/production/process-route/index.vue` | 新建:列表页 + 三栏拖拽配置弹窗 |
| 路由 | `web-admin/src/router/modules/sqxp.ts` | 追加 process-route 路由条目 |
## 风险与注意事项
1. **跨容器拖拽**:左栏(HTML5 Drag)→ 中栏(vue-draggable-plus)的跨容器拖入是核心难点。需在 drop 事件中正确获取 process_id 并调用 API。参考项目中 FlowDesigner.vue 的 HTML5 drag 实现。
2. **大弹窗性能**:90vw 弹窗内三栏 + 多个子表可能数据量大。步骤列表应限制每页展示(暂无分页需求,路线步骤通常 < 50)。
3. **工艺库删除影响**:步骤的 process_id 引用工艺库,但工艺被删除后步骤保留(process_id 可 null)。右栏编辑时不依赖 process_id 回查,所有数据已克隆到步骤中。
4. **子表更新策略**:使用完整替换(del旧 + insert新),避免复杂 diff。注意 UUID 生成和 tenant_no 继承。
@@ -0,0 +1,339 @@
# 细胞存储 ⇄ 科研捐赠 转移改造方案
> 日期:2026-05-19
> 状态:已实施(2026-05-19)
> 关联模块:采集协议单管理、捐赠协议单管理
---
## 1. 背景与问题
### 1.1 业务场景
- **协议转科研**:细胞存储采集协议(`sxz`)→ 新建科研捐赠单,源协议状态 `zky`。
- **转细胞存储**:科研捐赠单(`sxz`)→ 新建采集协议单,源捐赠状态 `zckc`。
产品约定:
1. 转移时 **协议号与样本编码与源单一致**(同一业务在不同模块各一张单)。
2. 转移后用户 **可能单独调整** 采集协议号或捐赠协议号(两边不必长期同号)。
3. 二次编辑保存时不应误报「协议号已存在」「样本编码已被占用」。
### 1.2 现状问题
| 问题 | 原因 |
|------|------|
| 转移后协议号为 `KY…` / `XC…` 临时串 | `convert_from_storage` / `convert_to_storage` 临时生成编号 |
| 二次保存报样本编码已占用 | 全局唯一校验未识别「转移配对单」 |
| 同号需求与跨表唯一校验冲突 | `protocol_no` 与 `donation_no` 互斥,未对配对单豁免 |
---
## 2. 改造目标
| # | 目标 |
|---|------|
| G1 | 转移瞬间:新单 `protocol_no` / `donation_no` = 源单展示号;样本 `sample_code` / `sample_code_id` 原样拷贝 |
| G2 | 建立 **转移关联** 持久化记录(中间表),供保存/改号校验查询 |
| G3 | 样本编码、协议号唯一性:对 **配对单据 ID** 豁免,不依赖编号字符串是否相同 |
| G4 | 转移后允许用户 **独立修改** 本单协议号/捐赠号,**不自动同步** 对端 |
| G5 | 非配对单据仍严格执行全局唯一,不影响手工新建/审核等流程 |
---
## 3. 方案概述
```mermaid
flowchart TB
subgraph transfer [转移事务]
A[源单状态变更 zky/zckc]
B[新建目标单 同号同码]
C[写入关联表 biz_protocol_donation_convert]
end
subgraph save [编辑保存]
D[根据当前单 ID 查配对 protocol_id / donation_id]
E[协议号校验 排除配对单]
F[样本编码校验 排除配对单样本行]
end
transfer --> save
```
**核心原则**:配对关系由 **关联表 + 双方主键 ID** 锁定;`biz_no` 仅记录转移完成时的初始编号(审计),**不参与** 豁免判断。
---
## 4. 数据库设计
### 4.1 新建表 `biz_protocol_donation_convert`
脚本路径:`docs/sql/biz_protocol_donation_convert.sql`
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | VARCHAR(64) | 主键 UUID |
| `convert_type` | VARCHAR(32) | `storage_to_research` / `research_to_storage` |
| `protocol_id` | VARCHAR(64) | `biz_collection_protocol.id` |
| `research_donation_id` | VARCHAR(64) | `biz_research_donation.id` |
| `biz_no` | VARCHAR(64) | **转移完成时** 的共用编号快照(不随改号自动更新) |
| `tenant_no` | VARCHAR(64) | 租户 |
| 公共字段 | — | `order_num`, `remark`, `create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag` |
**索引与约束(建议)**
- `UNIQUE (protocol_id)`:同一采集协议仅允许一条有效转移关联(应用层保证 `del_flag=0` 唯一)。
- `UNIQUE (research_donation_id)`:同一科研捐赠仅允许一条有效转移关联。
- `KEY (tenant_no, biz_no, del_flag)`:按初始编号排查。
**不新建** 主表字段为一期必选项;科研侧已有 `source_type`、`source_protocol_id` 可继续用于列表筛选与展示。
### 4.2 与现有表关系
```
biz_collection_protocol (1) ──< biz_protocol_donation_convert >── (1) biz_research_donation
│ │
├── biz_protocol_sample ├── biz_research_donation_sample
```
一对一:一次转移产生一对主单 + 一条关联记录。
---
## 5. 转移业务规则
### 5.1 协议转科研(`convert_from_storage`)
**前置**
- 源协议存在、`tenant_no` 匹配、`del_flag=0`。
- 源协议状态 = `sxz`(生效中)。
- 该 `protocol_id` 尚未存在有效关联记录。
**事务内操作**
| 步骤 | 操作 |
|------|------|
| 1 | 新建 `biz_research_donation`:`donation_no = proto.protocol_no`,`donation_status=dsx`,`source_type=from_storage`,`source_protocol_id=protocol_id` |
| 2 | 复制 party / collection / samples(+transport),样本编码与源一致 |
| 3 | 源协议 `protocol_status=zky`,写 `to_research_time` / `to_research_desc` |
| 4 | 插入关联表:`convert_type=storage_to_research`,`biz_no=proto.protocol_no` |
**删除**:`KY` + 时间戳 + UUID 临时号逻辑。
### 5.2 转细胞存储(`convert_to_storage`)
**前置**
- 源捐赠存在、`sxz`、无有效关联。
**事务内操作**
| 步骤 | 操作 |
|------|------|
| 1 | 新建 `biz_collection_protocol`:`protocol_no = donation.donation_no`,`protocol_status=dsx`;`protocol_no_id` 可按 `biz_no` 反查号池(可选) |
| 2 | 复制子表,样本同码;价格字段置空 |
| 3 | 源捐赠 `donation_status=zckc`,写 `to_storage_time` / `to_storage_desc` |
| 4 | 插入关联表:`convert_type=research_to_storage`,`biz_no=donation.donation_no` |
**删除**:`XC` + 时间戳临时号逻辑。
### 5.3 转移后改号(用户可能调整)
| 规则 | 说明 |
|------|------|
| 允许改号 | 采集协议可改 `protocol_no`;科研捐赠可改 `donation_no`(需确认前端是否已暴露字段,后端须支持校验) |
| 不同步对端 | 改 A 单号 **不自动修改** B 单号(方案 A) |
| 关联表 `biz_no` | **保留转移快照**,不随改号更新 |
| 可选 UI 提示 | 改号成功后提示「配对单据当前编号为 xxx」(二期) |
---
## 6. 校验规则改造
### 6.1 公共能力(新建)
位置建议:`backend/app/crud/protocol_donation_convert.py`(或合入 `research_donation.py`)
| 函数 | 说明 |
|------|------|
| `create_convert_link(db, ...)` | 转移事务内写入关联 |
| `get_convert_partner(db, tenant_no, *, protocol_id=None, donation_id=None)` | 返回 `{ protocol_id, research_donation_id, biz_no, convert_type }` 或 `None` |
| `get_partner_sample_codes(db, tenant_no, protocol_id, donation_id)` | 配对单上所有 `del_flag=0` 的 `sample_code` 集合 |
### 6.2 样本编码唯一性
改造:`collection_protocol.validate_sample_codes_not_used`
```text
占用集合 = 全租户 采集样本表 ∪ 科研捐赠样本表(del_flag=0)
占用集合 -= 配对 protocol_id 下样本编码
占用集合 -= 配对 research_donation_id 下样本编码
若 待保存编码 ∩ 占用集合 非空 → 报错
```
**调用点**
- `collection_protocol.save_samples`:传入当前 `collection_protocol_id`,查 partner 后排除。
- `research_donation._create_samples`:传入当前 `research_donation_id`,查 partner 后排除。
> 本单 upsert 会先软删旧样本行,排除逻辑必须基于 **配对单 ID**,与双方编号是否一致无关。
### 6.3 协议号 / 捐赠号跨表唯一性
改造:`create_collection_protocol`、`update_collection_protocol`、`create_donation`、`update_donation`(若支持改 `donation_no`)。
**判断「编号是否冲突」时:**
```text
冲突对象 = 其他采集协议.protocol_no(排除当前 protocol_id)
∪ 其他科研捐赠.donation_no(排除当前 donation_id)
若存在 convert 配对:
- 更新采集协议时:再排除 配对 research_donation_id 对应行的 donation_no
- 更新科研捐赠时:再排除 配对 protocol_id 对应行的 protocol_no
仅当 新编号 落在「冲突对象」中时报错
```
**说明**:配对单占用 **整行单据** 豁免,不要求新号与对端当前号相同。例如协议改为 A001、捐赠仍为 A002,只要无第三方占用,均应通过。
### 6.4 非转移场景
- 手工新建、无关联记录:保持现有严格唯一。
- 批量转移失败策略:维持现有「逐条调用、部分成功」行为。
---
## 7. 后端改动清单
| 序号 | 文件 | 改动要点 |
|------|------|----------|
| 1 | `docs/sql/biz_protocol_donation_convert.sql` | 建表 |
| 2 | `backend/app/models/research_donation.py` | `BizProtocolDonationConvert` |
| 3 | `backend/app/models/__init__.py` | 导出 Model |
| 4 | `backend/app/crud/protocol_donation_convert.py` | 关联 CRUD + 查询(新建) |
| 5 | `backend/app/crud/research_donation.py` | `convert_*` 同号同码 + 写关联;`_create_samples` 传排除参数;`update_donation` 支持 `donation_no` 改号校验 |
| 6 | `backend/app/crud/collection_protocol.py` | `validate_sample_codes_not_used`、`save_samples`、create/update 协议号校验 |
| 7 | `backend/app/api/research_donation.py` | 无路由变更(可选详情返回 `convertPartner`) |
**API**:不新增对外接口;现有:
- `POST /web/admin/research-donations/convert-from-storage`
- `POST /web/admin/research-donations/{id}/convert-to-storage`
---
## 8. 前端改动清单
### 8.1 一期(随然后端联调)
| 项 | 改动 |
|----|------|
| 转移确认文案 | 说明「将创建新单,**初始**协议号与样本编码与源单一致」 |
| 编辑表单 | 转移生成的新单:**协议号/捐赠号保持可编辑**(勿做只读) |
| 保存逻辑 | 无改动,依赖后端豁免 |
### 8.2 二期(体验增强)
| 项 | 改动 |
|----|------|
| 查看弹窗 | 展示「转移关联」:对端单据类型、编号、跳转 |
| 改号提示 | 保存后若与对端编号不一致,轻提示对端当前号 |
---
## 9. 历史数据
| 类型 | 处理 |
|------|------|
| 已用 `KY…`/`XC…` 生成的转移单 | 脚本按 `source_protocol_id`、`to_research_desc`、`remark` 尝试配对并补关联表;编号是否改为源单同号需 **业务确认后** 再批量更新 |
| 无法配对 | 保持现状,仅新转移走新逻辑 |
脚本建议:`backend/scripts/backfill_protocol_donation_convert.py`(可选)。
---
## 10. 实施步骤
| 阶段 | 内容 | 产出 |
|------|------|------|
| P1 | SQL + Model + 关联 CRUD | 表可建、可查询配对 |
| P2 | 改造 `convert_from_storage` / `convert_to_storage` | 转移同号同码 + 写关联 |
| P3 | 样本编码校验 + `save_samples` / `_create_samples` | 二次编辑样本保存通过 |
| P4 | 协议号/捐赠号 create & update 豁免 | 改号不误报、可独立改号 |
| P5 | 联调 + 回归 + 确认文案 | 测试清单通过 |
| P6 | 历史数据脚本(可选) | 补关联记录 |
---
## 11. 测试用例
### 11.1 转移
| # | 步骤 | 预期 |
|---|------|------|
| T1 | `sxz` 协议转科研 | 捐赠号=协议号;样本码一致;关联表 1 条;源单 `zky` |
| T2 | `sxz` 捐赠转存储 | 协议号=捐赠号;样本码一致;关联表 1 条;源单 `zckc` |
| T3 | 非 `sxz` 转移 | 400 业务错误 |
| T4 | 已转移协议再次转科研 | 失败 |
### 11.2 二次保存(样本)
| # | 步骤 | 预期 |
|---|------|------|
| T5 | 转移后不改编码,直接保存 | 成功 |
| T6 | 修改非编码字段后保存 | 成功 |
### 11.3 改号
| # | 步骤 | 预期 |
|---|------|------|
| T7 | 只改采集协议号 | 成功;捐赠号可不变 |
| T8 | 只改捐赠号 | 成功 |
| T9 | 改为第三方已占用号 | 失败 |
| T10 | 改为配对单当前持有的号 | 成功(豁免) |
### 11.4 回归
| # | 步骤 | 预期 |
|---|------|------|
| T11 | 无关联手工新建协议+样本 | 重复编码仍失败 |
| T12 | 审核、状态变更、删除 | 行为不变 |
---
## 12. 风险与待定项
| 项 | 说明 | 建议 |
|----|------|------|
| 号池下发状态 | 转移同号后 `biz_collection_protocol_no` 是否标记已下发 | 二期与基础数据模块对齐 |
| `protocol_no_id` | 转存储时是否回填号池 ID | 可选:按 `biz_no` 查池 |
| 链式转移 | 同一业务多次互转 | 当前按 **一对一** 设计;若需支持需单独立项 |
| 软删一方 | 另一方编辑是否仍豁免 | 建议:**仍豁免**(关联保留,占用仅计 `del_flag=0`) |
---
## 13. 已确认产品决策
| 决策 | 结论 |
|------|------|
| 转移初始编号 | 与源单相同 |
| 转移初始样本编码 | 与源单相同 |
| 转移后改号 | 允许,两边可不一致 |
| 改号是否同步对端 | **否** |
| 关联表 `biz_no` | 转移快照,不随改号更新 |
| 豁免依据 | 关联表 **单据 ID**,非编号字符串 |
---
## 14. 参考代码位置(改造前)
| 逻辑 | 文件 |
|------|------|
| 转移(临时号) | `backend/app/crud/research_donation.py` → `convert_from_storage` / `convert_to_storage` |
| 样本编码校验 | `backend/app/crud/collection_protocol.py` → `validate_sample_codes_not_used` |
| 协议号互斥 | `create_collection_protocol` / `update_collection_protocol` / `create_donation` |
| 前端转移调用 | `web-admin/src/api/research-donation.ts` |
---
*文档结束*