fix: 子表保存改为就地同步,避免整批换 id 打断外键。

协议样本、生产产出和盘点明细按行更新并保留原 id;补齐存储证明质检预填、离职人员显示名,以及样本采集通知模板字段。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zlwuandCursor committed 2026-10-28 10:51:53 +08:00
1 parent d66e55af65
commit 5e563a6669
139 files changed
+59986 -49048

No files matched your search

@@ -1,73 +0,0 @@
-- =============================================================================
-- 预签约订单菜单:插入 sys_menu,并授权给已有「采集协议单」的角色
-- 测试库 zkxw-sc 已确认原先没有该菜单行。
-- 菜单位置:采集协议单 之下、缴费信息/样本信息管理 之上
-- id: 50000000000000000000000000000020
-- perms: cell-storage:presign-order:list
-- =============================================================================
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
)
SELECT
'50000000000000000000000000000020',
'预签约订单',
'CEL00000000000000000000000000001',
'C',
'presign-order',
'/cell-storage/presign-order/index',
'cell-storage:presign-order:list',
1,
'1',
'1',
'zkxw',
3,
'细胞存储:预签约订单',
NULL,
'',
NOW(),
NULL,
NOW(),
0
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM sys_menu m
WHERE m.del_flag = 0
AND (
m.id = '50000000000000000000000000000020'
OR m.perms = 'cell-storage:presign-order:list'
)
);
-- 已有采集协议单权限的角色补授权(幂等)
INSERT INTO sys_role_menu (
id, role_id, menu_id, tenant_no, order_num, remark,
create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT
MD5(CONCAT(r.id, '|50000000000000000000000000000020')),
r.id,
'50000000000000000000000000000020',
IFNULL(NULLIF(r.tenant_no, ''), 'zkxw'),
0,
'grant presign-order to protocol-audit roles',
NULL,
'',
NOW(),
NULL,
NOW(),
0
FROM sys_role r
INNER JOIN sys_role_menu rm
ON rm.role_id = r.id
AND rm.menu_id = '50000000000000000000000000000003'
AND rm.del_flag = 0
WHERE r.del_flag = 0
AND NOT EXISTS (
SELECT 1 FROM sys_role_menu x
WHERE x.role_id = r.id
AND x.menu_id = '50000000000000000000000000000020'
AND x.del_flag = 0
);
@@ -1,8 +0,0 @@
-- 储存证 / 满月保险轻量办理字段(2026-10-05)
ALTER TABLE `biz_protocol_storage_cert`
ADD COLUMN `handle_time` datetime DEFAULT NULL COMMENT '办理时间' AFTER `make_date`,
ADD COLUMN `handler_user_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '办理人用户ID' AFTER `handle_time`;
ALTER TABLE `biz_protocol_fullmoon_todo`
ADD COLUMN `handle_time` datetime DEFAULT NULL COMMENT '办理时间' AFTER `handle_date`,
ADD COLUMN `handler_user_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '办理人用户ID' AFTER `handle_time`;
@@ -1,119 +0,0 @@
-- =============================================================================
-- 缴费信息独立菜单:显示与排序(2026-10-05)
-- =============================================================================
--
-- 【主路径 — 前端固定菜单】
-- 当前 Web 管理端使用 VITE_ACCESS_MODE=frontend,菜单树由以下两处维护,Task 4 已更新:
-- - backend/app/api/menu.py → CellStoragePaymentInfo(id 904b5173519411f1944300163e3bdd96)
-- - web-admin/src/router/modules/sqxp.ts → 预签约订单与样本信息管理之间,已取消 isHide
-- 前端模式环境 **无需执行** 本脚本;保留本文件供 DB 菜单模式或角色授权同步参考。
--
-- 【可选 — DB 菜单模式】
-- sys_menu 列名以 backend/app/models/models.py :: SysMenu 为准(docs/sql/zkxw.sql 无 sys_menu 段)。
-- visible: '1'=显示, '0'=隐藏(与 menu.py meta.isHide 映射一致,见 reorder_web_menus.py)
--
-- 细胞存储目录 parent_id: CEL00000000000000000000000000001
-- 预签约订单 id: 50000000000000000000000000000020
-- 缴费信息 id: 904b5173519411f1944300163e3bdd96 path=payment-info perms=cellStorage:paymentInfo:list
-- 样本信息 id: 50000000000000000000000000000013
-- =============================================================================
-- 1) 若库中已有该菜单(按 id 或 perms 匹配):取消隐藏并调整排序至预签约与样本信息之间
UPDATE sys_menu
SET del_flag = 0,
visible = '1',
status = '1',
order_num = (
SELECT mid_order FROM (
SELECT COALESCE(MAX(m2.order_num), 0) AS mid_order
FROM sys_menu m2
WHERE m2.del_flag = 0
AND (
m2.id = '50000000000000000000000000000020'
OR m2.perms = 'cell-storage:presign-order:list'
OR m2.path = 'presign-order'
)
) t
) + 1,
update_time = NOW()
WHERE del_flag = 0
AND (
id = '904b5173519411f1944300163e3bdd96'
OR perms = 'cellStorage:paymentInfo:list'
OR (path = 'payment-info' AND parent_id = 'CEL00000000000000000000000000001')
);
-- 2) 若不存在则幂等插入(仅 DB 菜单模式需要;前端模式可跳过)
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
)
SELECT
'904b5173519411f1944300163e3bdd96',
'缴费信息',
'CEL00000000000000000000000000001',
'C',
'payment-info',
'/cell-storage/payment-info/index',
'cellStorage:paymentInfo:list',
1,
'1',
'1',
'zkxw',
COALESCE((
SELECT MAX(m2.order_num) + 1
FROM sys_menu m2
WHERE m2.del_flag = 0
AND (
m2.id = '50000000000000000000000000000020'
OR m2.perms = 'cell-storage:presign-order:list'
)
), 1),
'细胞存储:缴费信息(流程看板 + 按样本),独立于采集协议单',
NULL,
'',
NOW(),
NULL,
NOW(),
0
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM sys_menu m
WHERE m.del_flag = 0
AND (
m.id = '904b5173519411f1944300163e3bdd96'
OR m.perms = 'cellStorage:paymentInfo:list'
)
);
-- 3) 已有「采集协议单」权限的角色补授权(幂等)
INSERT INTO sys_role_menu (
id, role_id, menu_id, tenant_no, order_num, remark,
create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT
MD5(CONCAT(r.id, '|904b5173519411f1944300163e3bdd96')),
r.id,
'904b5173519411f1944300163e3bdd96',
IFNULL(NULLIF(r.tenant_no, ''), 'zkxw'),
0,
'grant payment-info to protocol-audit roles',
NULL,
'',
NOW(),
NULL,
NOW(),
0
FROM sys_role r
INNER JOIN sys_role_menu rm
ON rm.role_id = r.id
AND rm.menu_id = '50000000000000000000000000000003'
AND rm.del_flag = 0
WHERE r.del_flag = 0
AND NOT EXISTS (
SELECT 1 FROM sys_role_menu x
WHERE x.role_id = r.id
AND x.menu_id = '904b5173519411f1944300163e3bdd96'
AND x.del_flag = 0
);
@@ -1,29 +0,0 @@
-- =============================================================================
-- 下线「科研捐赠 → 样本信息管理」侧栏菜单(2026-10-06)
-- =============================================================================
--
-- 【主路径 — 前端固定菜单】
-- 当前 Web 管理端使用 VITE_ACCESS_MODE=frontend,菜单树已从以下两处移除该子项:
-- - web-admin/src/router/modules/sqxp.ts
-- - backend/app/api/menu.py
-- 样本信息仍从「细胞存储 → 样本信息管理」进入(统一页含科研捐赠 Tab)。
-- 前端模式环境 **无需执行** 本脚本;保留供 DB 菜单模式同步。
--
-- 科研捐赠目录 parent_id: RSC00000000000000000000000000001
-- 样本信息 id: 60000000000000000000000000000004
-- perms: research-donation:sample-info:list
-- visible: '0'=隐藏(权限码保留,统一页 Tab / 接口授权仍可用)
-- =============================================================================
UPDATE sys_menu
SET visible = '0',
update_time = NOW()
WHERE del_flag = 0
AND (
id = '60000000000000000000000000000004'
OR perms = 'research-donation:sample-info:list'
OR (
path = 'sample-info'
AND parent_id = 'RSC00000000000000000000000000001'
)
);
@@ -1,39 +0,0 @@
-- =============================================================================
-- 下线「科研捐赠」协议单侧栏菜单(含一级空目录)(2026-10-07)
-- =============================================================================
--
-- 【主路径 — 前端固定菜单】
-- VITE_ACCESS_MODE=frontend 时已在以下文件隐藏:
-- - web-admin/src/router/modules/sqxp.ts
-- - backend/app/api/menu.py
-- 协议单从「细胞存储 → 采集协议单」进入(统一页含科研捐赠 Tab)。
-- 前端模式环境 **无需执行** 本脚本;保留供 DB 菜单模式 / 生产环境同步。
--
-- 科研捐赠目录 parent_id / id: RSC00000000000000000000000000001
-- 捐赠协议单录入 id: 60000000000000000000000000000002
-- perms: biz:researchDonation:list
-- 捐赠协议单 id: 60000000000000000000000000000003
-- perms: biz:researchDonationAudit:list
-- visible: '0'=隐藏(权限码可保留;统一页不依赖捐赠菜单控 Tab)
-- 注意:不要隐藏样本信息菜单 id 6000...0004(已由 2026-10-06 脚本单独处理)
-- =============================================================================
UPDATE sys_menu
SET visible = '0',
update_time = NOW()
WHERE del_flag = 0
AND (
id IN (
'RSC00000000000000000000000000001',
'60000000000000000000000000000002',
'60000000000000000000000000000003'
)
OR perms IN (
'biz:researchDonation:list',
'biz:researchDonationAudit:list'
)
OR (
parent_id = 'RSC00000000000000000000000000001'
AND path IN ('list', 'audit')
)
);
@@ -1,9 +0,0 @@
-- 采集协议当事人:父亲/子女客户 ID(母亲沿用 customer_id)
ALTER TABLE biz_protocol_party_customer
ADD COLUMN father_customer_id VARCHAR(64) DEFAULT NULL COMMENT '父亲客户ID' AFTER customer_id,
ADD COLUMN newborn_customer_id VARCHAR(64) DEFAULT NULL COMMENT '子女客户ID' AFTER father_customer_id;
-- 科研捐赠当事人:同上
ALTER TABLE biz_research_donation_party
ADD COLUMN father_customer_id VARCHAR(64) DEFAULT NULL COMMENT '父亲客户ID' AFTER customer_id,
ADD COLUMN newborn_customer_id VARCHAR(64) DEFAULT NULL COMMENT '子女客户ID' AFTER father_customer_id;
@@ -1,77 +0,0 @@
-- 样本运费批次 + 关联 + 运输方式字典预置(租户 zkxw;其他租户首次用时由后端按需补齐预置也可只插 zkxw)
CREATE TABLE IF NOT EXISTS biz_sample_freight (
id VARCHAR(64) NOT NULL COMMENT '主键ID',
transport_mode VARCHAR(100) NOT NULL DEFAULT '' COMMENT '运输方式字典值 sample_transport_mode',
freight_amount DECIMAL(18,2) NOT NULL DEFAULT 0.00 COMMENT '运费(元)',
tenant_no VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号',
order_num INT 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 COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT NULL COMMENT '更新人id',
update_time DATETIME NOT NULL COMMENT '更新时间',
del_flag SMALLINT NOT NULL DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (id),
KEY idx_sample_freight_tenant_ctime (tenant_no, create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='样本运费批次';
CREATE TABLE IF NOT EXISTS biz_sample_freight_item (
id VARCHAR(64) NOT NULL COMMENT '主键ID',
freight_id VARCHAR(64) NOT NULL COMMENT '运费批次ID',
sample_source VARCHAR(32) NOT NULL COMMENT 'cell_storage|research_donation',
sample_id VARCHAR(64) NOT NULL COMMENT '样本ID',
sample_code VARCHAR(32) DEFAULT NULL COMMENT '样本编码快照',
protocol_id VARCHAR(64) DEFAULT NULL COMMENT '协议单ID',
tenant_no VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号',
order_num INT 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 COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT NULL COMMENT '更新人id',
update_time DATETIME NOT NULL COMMENT '更新时间',
del_flag SMALLINT NOT NULL DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (id),
KEY idx_freight_item_freight (freight_id, del_flag),
KEY idx_freight_item_sample (tenant_no, sample_source, sample_id, del_flag)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='样本运费关联';
-- 字典类型(若不存在)
INSERT INTO sys_dict_type (
id, dict_name, dict_type, tenant_no, order_num, remark,
create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT
REPLACE(UUID(),'-',''), '样本运输方式', 'sample_transport_mode', 'zkxw', 0, '协议单运费批次运输方式',
NULL, '', NOW(), NULL, NOW(), 0
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM sys_dict_type
WHERE dict_type = 'sample_transport_mode' AND tenant_no = 'zkxw' AND del_flag = 0
);
INSERT INTO sys_dict_data (
id, dict_label, dict_value, dict_type, tenant_no, order_num, remark,
create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT REPLACE(UUID(),'-',''), '顺丰快递', 'sfkd', 'sample_transport_mode', 'zkxw', 1, NULL,
NULL, '', NOW(), NULL, NOW(), 0
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM sys_dict_data
WHERE dict_type = 'sample_transport_mode' AND tenant_no = 'zkxw' AND dict_value = 'sfkd' AND del_flag = 0
);
INSERT INTO sys_dict_data (
id, dict_label, dict_value, dict_type, tenant_no, order_num, remark,
create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT REPLACE(UUID(),'-',''), '专线车', 'zxc', 'sample_transport_mode', 'zkxw', 2, NULL,
NULL, '', NOW(), NULL, NOW(), 0
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM sys_dict_data
WHERE dict_type = 'sample_transport_mode' AND tenant_no = 'zkxw' AND dict_value = 'zxc' AND del_flag = 0
);
@@ -1,10 +0,0 @@
-- 客户主档:通用个人档案字段
-- Spec: docs/superpowers/specs/2026-10-08-customer-profile-expand-sync-design.md
SET NAMES utf8mb4;
ALTER TABLE `biz_customer`
ADD COLUMN `birth_ym` char(6) DEFAULT NULL COMMENT '出生年月YYYYMM' AFTER `age`,
ADD COLUMN `ethnic` varchar(32) DEFAULT NULL COMMENT '民族' AFTER `birth_ym`,
ADD COLUMN `email` varchar(128) DEFAULT NULL COMMENT '邮箱' AFTER `ethnic`,
ADD COLUMN `address` varchar(512) DEFAULT NULL COMMENT '家庭住址' AFTER `email`;
@@ -1,10 +0,0 @@
-- 采集协议 / 科研捐赠:地区经理用户ID
-- Spec: docs/superpowers/specs/2026-10-08-protocol-market-specialist-region-manager-design.md
SET NAMES utf8mb4;
ALTER TABLE `biz_protocol_collection_info`
ADD COLUMN `region_manager_user_id` varchar(64) DEFAULT NULL COMMENT '地区经理用户ID' AFTER `sales_user_id`;
ALTER TABLE `biz_research_donation_collection`
ADD COLUMN `region_manager_user_id` varchar(64) DEFAULT NULL COMMENT '地区经理用户ID' AFTER `sales_user_id`;
@@ -1,21 +0,0 @@
-- =============================================================================
-- 侧栏二级菜单「细胞存储」更名为「存储服务」
-- id: CEL00000000000000000000000000001
-- 原因:该目录下统一承载细胞存储与科研捐赠业务入口
--
-- 【前端固定菜单】同步修改:
-- - web-admin/src/router/modules/sqxp.ts
-- - backend/app/api/menu.py
-- =============================================================================
UPDATE sys_menu
SET menu_name = '存储服务',
remark = CASE
WHEN remark IS NULL OR TRIM(remark) = '' THEN '存储服务:含细胞存储与科研捐赠统一业务入口'
WHEN remark LIKE '%细胞存储%' THEN REPLACE(remark, '细胞存储', '存储服务')
ELSE remark
END,
update_time = NOW()
WHERE del_flag = 0
AND id = 'CEL00000000000000000000000000001'
AND menu_name = '细胞存储';
@@ -1,72 +0,0 @@
-- =============================================================================
-- 运费信息菜单:插入 sys_menu,并授权给已有「样本信息管理」的角色
-- 菜单位置:样本信息管理 之下、缴费财务确认及开票 之上
-- id: 50000000000000000000000000000021
-- perms: cell-storage:sample-freight:list
-- =============================================================================
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
)
SELECT
'50000000000000000000000000000021',
'运费信息',
'CEL00000000000000000000000000001',
'C',
'sample-freight',
'/cell-storage/sample-freight/index',
'cell-storage:sample-freight:list',
1,
'1',
'1',
'zkxw',
(SELECT order_num + 1 FROM sys_menu WHERE id = '50000000000000000000000000000013' AND del_flag = 0 LIMIT 1),
'细胞存储:运费信息',
NULL,
'',
NOW(),
NULL,
NOW(),
0
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM sys_menu m
WHERE m.del_flag = 0
AND (
m.id = '50000000000000000000000000000021'
OR m.perms = 'cell-storage:sample-freight:list'
)
);
-- 已有样本信息管理权限的角色补授权(幂等)
INSERT INTO sys_role_menu (
id, role_id, menu_id, tenant_no, order_num, remark,
create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT
MD5(CONCAT(r.id, '|50000000000000000000000000000021')),
r.id,
'50000000000000000000000000000021',
IFNULL(NULLIF(r.tenant_no, ''), 'zkxw'),
0,
'grant sample-freight to sample-info roles',
NULL,
'',
NOW(),
NULL,
NOW(),
0
FROM sys_role r
INNER JOIN sys_role_menu rm
ON rm.role_id = r.id
AND rm.menu_id = '50000000000000000000000000000013'
AND rm.del_flag = 0
WHERE r.del_flag = 0
AND NOT EXISTS (
SELECT 1 FROM sys_role_menu x
WHERE x.role_id = r.id
AND x.menu_id = '50000000000000000000000000000021'
AND x.del_flag = 0
);
@@ -1,30 +0,0 @@
-- 若已按旧规则 YF+YYYY-MM-DD+序号 回填,纠正为 YF+YYYYMMDD+001~999
SET @grp := '';
SET @seq := 0;
UPDATE biz_sample_freight f
INNER JOIN (
SELECT
id,
CONCAT(
'YF',
DATE_FORMAT(create_time, '%Y%m%d'),
LPAD(
@seq := IF(
@grp = CONCAT(tenant_no, '|', DATE_FORMAT(create_time, '%Y%m%d')),
@seq + 1,
1
),
3,
'0'
)
) AS new_no,
@grp := CONCAT(tenant_no, '|', DATE_FORMAT(create_time, '%Y%m%d')) AS _grp
FROM biz_sample_freight
ORDER BY tenant_no ASC, create_time ASC, id ASC
) t ON f.id = t.id
SET f.freight_no = t.new_no;
ALTER TABLE biz_sample_freight
MODIFY COLUMN freight_no VARCHAR(32) NOT NULL DEFAULT '' COMMENT '运费批次号 YF+YYYYMMDD+序号';
@@ -1,35 +0,0 @@
-- 运费批次号:YF + YYYYMMDD + 001~999(按租户、按日递增)
ALTER TABLE biz_sample_freight
ADD COLUMN freight_no VARCHAR(32) NOT NULL DEFAULT '' COMMENT '运费批次号 YF+YYYYMMDD+序号' AFTER id;
-- 按租户 + 创建日回填历史数据
SET @grp := '';
SET @seq := 0;
UPDATE biz_sample_freight f
INNER JOIN (
SELECT
id,
CONCAT(
'YF',
DATE_FORMAT(create_time, '%Y%m%d'),
LPAD(
@seq := IF(
@grp = CONCAT(tenant_no, '|', DATE_FORMAT(create_time, '%Y%m%d')),
@seq + 1,
1
),
3,
'0'
)
) AS new_no,
@grp := CONCAT(tenant_no, '|', DATE_FORMAT(create_time, '%Y%m%d')) AS _grp
FROM biz_sample_freight
ORDER BY tenant_no ASC, create_time ASC, id ASC
) t ON f.id = t.id
SET f.freight_no = t.new_no
WHERE f.freight_no = '' OR f.freight_no IS NULL;
ALTER TABLE biz_sample_freight
ADD UNIQUE KEY uk_sample_freight_tenant_no (tenant_no, freight_no);
@@ -1,74 +0,0 @@
-- =============================================================================
-- 催缴信息菜单:插在缴费信息之下、样本信息管理之上
-- id: 50000000000000000000000000000022
-- perms / menuCode: cellStorage:paymentChase:list
-- =============================================================================
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
)
SELECT
'50000000000000000000000000000022',
'催缴信息',
'CEL00000000000000000000000000001',
'C',
'payment-chase',
'/cell-storage/payment-chase/index',
'cellStorage:paymentChase:list',
1,
'1',
'1',
'zkxw',
5,
'细胞存储:催缴信息(逾期∪近30天)',
NULL,
'',
NOW(),
NULL,
NOW(),
0
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM sys_menu m
WHERE m.del_flag = 0
AND (
m.id = '50000000000000000000000000000022'
OR m.perms = 'cellStorage:paymentChase:list'
)
);
-- 已有缴费信息权限的角色补授权(幂等)
INSERT INTO sys_role_menu (
id, role_id, menu_id, tenant_no, order_num, remark,
create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT
MD5(CONCAT(r.id, '|50000000000000000000000000000022')),
r.id,
'50000000000000000000000000000022',
IFNULL(NULLIF(r.tenant_no, ''), 'zkxw'),
0,
'grant payment-chase to payment-info roles',
NULL,
'',
NOW(),
NULL,
NOW(),
0
FROM sys_role r
INNER JOIN sys_role_menu rm
ON rm.role_id = r.id
AND rm.del_flag = 0
INNER JOIN sys_menu m
ON m.id = rm.menu_id
AND m.del_flag = 0
AND m.perms = 'cellStorage:paymentInfo:list'
WHERE r.del_flag = 0
AND NOT EXISTS (
SELECT 1 FROM sys_role_menu x
WHERE x.del_flag = 0
AND x.role_id = r.id
AND x.menu_id = '50000000000000000000000000000022'
);
@@ -1,24 +0,0 @@
-- 样本级储存证质检信息(2026-10-09)
-- 对齐 docs/superpowers/specs/2026-10-09-storage-cert-qc-info-design.md
CREATE TABLE IF NOT EXISTS biz_sample_storage_cert_qc (
id VARCHAR(64) NOT NULL COMMENT '主键ID',
sample_code VARCHAR(32) NOT NULL COMMENT '样本编码',
collection_protocol_id VARCHAR(64) DEFAULT NULL COMMENT '采集协议单ID冗余',
qc_work_order_id VARCHAR(64) DEFAULT NULL COMMENT '最近填写来源工单ID',
sample_type_key VARCHAR(32) NOT NULL COMMENT 'perinatal_hsc|uc_msc',
payload TEXT NOT NULL COMMENT '业务字段 JSON',
filled_flag CHAR(1) NOT NULL DEFAULT '0' COMMENT '是否已保存过 0否1是',
tenant_no VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号',
order_num INT 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 COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT NULL COMMENT '更新人id',
update_time DATETIME NOT NULL COMMENT '更新时间',
del_flag SMALLINT NOT NULL DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (id),
UNIQUE KEY uk_storage_cert_qc_tenant_sample (tenant_no, sample_code),
KEY idx_storage_cert_qc_protocol (tenant_no, collection_protocol_id, del_flag)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='样本储存证质检信息';
@@ -1,125 +0,0 @@
-- 移动市场现场 scope 查询复合索引
-- MySQL 5.7;通过 information_schema + PREPARE 保证重复执行安全。
SET NAMES utf8mb4;
SET @db := DATABASE();
SET @idx_exists := (
SELECT COUNT(*) FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'biz_collection_region_staff'
AND INDEX_NAME = 'idx_crs_market_leader_scope'
);
SET @sql := IF(@idx_exists = 0,
'ALTER TABLE `biz_collection_region_staff` ADD INDEX `idx_crs_market_leader_scope` (`tenant_no`,`user_id`,`is_leader`,`del_flag`,`region_id`)',
'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @idx_exists := (
SELECT COUNT(*) FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'biz_presign_order'
AND INDEX_NAME = 'idx_po_market_sales_scope'
);
SET @sql := IF(@idx_exists = 0,
'ALTER TABLE `biz_presign_order` ADD INDEX `idx_po_market_sales_scope` (`tenant_no`,`del_flag`,`sales_user_id`)',
'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @idx_exists := (
SELECT COUNT(*) FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'biz_presign_order'
AND INDEX_NAME = 'idx_po_market_region_scope'
);
SET @sql := IF(@idx_exists = 0,
'ALTER TABLE `biz_presign_order` ADD INDEX `idx_po_market_region_scope` (`tenant_no`,`del_flag`,`region_id`)',
'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @idx_exists := (
SELECT COUNT(*) FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'biz_presign_order'
AND INDEX_NAME = 'idx_po_market_manager_scope'
);
SET @sql := IF(@idx_exists = 0,
'ALTER TABLE `biz_presign_order` ADD INDEX `idx_po_market_manager_scope` (`tenant_no`,`del_flag`,`region_manager_user_id`)',
'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @idx_exists := (
SELECT COUNT(*) FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'biz_protocol_collection_info'
AND INDEX_NAME = 'idx_pci_market_parent_scope'
);
SET @sql := IF(@idx_exists = 0,
'ALTER TABLE `biz_protocol_collection_info` ADD INDEX `idx_pci_market_parent_scope` (`tenant_no`,`del_flag`,`collection_protocol_id`)',
'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @idx_exists := (
SELECT COUNT(*) FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'biz_protocol_collection_info'
AND INDEX_NAME = 'idx_pci_market_sales_scope'
);
SET @sql := IF(@idx_exists = 0,
'ALTER TABLE `biz_protocol_collection_info` ADD INDEX `idx_pci_market_sales_scope` (`tenant_no`,`del_flag`,`sales_user_id`,`collection_protocol_id`)',
'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @idx_exists := (
SELECT COUNT(*) FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'biz_protocol_collection_info'
AND INDEX_NAME = 'idx_pci_market_region_scope'
);
SET @sql := IF(@idx_exists = 0,
'ALTER TABLE `biz_protocol_collection_info` ADD INDEX `idx_pci_market_region_scope` (`tenant_no`,`del_flag`,`region_id`,`collection_protocol_id`)',
'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @idx_exists := (
SELECT COUNT(*) FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'biz_protocol_collection_info'
AND INDEX_NAME = 'idx_pci_market_manager_scope'
);
SET @sql := IF(@idx_exists = 0,
'ALTER TABLE `biz_protocol_collection_info` ADD INDEX `idx_pci_market_manager_scope` (`tenant_no`,`del_flag`,`region_manager_user_id`,`collection_protocol_id`)',
'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @idx_exists := (
SELECT COUNT(*) FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'biz_research_donation_collection'
AND INDEX_NAME = 'idx_rdc_market_parent_scope'
);
SET @sql := IF(@idx_exists = 0,
'ALTER TABLE `biz_research_donation_collection` ADD INDEX `idx_rdc_market_parent_scope` (`tenant_no`,`del_flag`,`research_donation_id`)',
'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @idx_exists := (
SELECT COUNT(*) FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'biz_research_donation_collection'
AND INDEX_NAME = 'idx_rdc_market_sales_scope'
);
SET @sql := IF(@idx_exists = 0,
'ALTER TABLE `biz_research_donation_collection` ADD INDEX `idx_rdc_market_sales_scope` (`tenant_no`,`del_flag`,`sales_user_id`,`research_donation_id`)',
'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @idx_exists := (
SELECT COUNT(*) FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'biz_research_donation_collection'
AND INDEX_NAME = 'idx_rdc_market_region_scope'
);
SET @sql := IF(@idx_exists = 0,
'ALTER TABLE `biz_research_donation_collection` ADD INDEX `idx_rdc_market_region_scope` (`tenant_no`,`del_flag`,`region_id`,`research_donation_id`)',
'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @idx_exists := (
SELECT COUNT(*) FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'biz_research_donation_collection'
AND INDEX_NAME = 'idx_rdc_market_manager_scope'
);
SET @sql := IF(@idx_exists = 0,
'ALTER TABLE `biz_research_donation_collection` ADD INDEX `idx_rdc_market_manager_scope` (`tenant_no`,`del_flag`,`region_manager_user_id`,`research_donation_id`)',
'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
@@ -1,235 +0,0 @@
-- =============================================================================
-- 小程序市场现场菜单:市场专员 / 地区经理两套目录与十二个业务叶子
--
-- 说明:
-- 1. 面向每个租户已有的「移动端菜单」根生成菜单,不写死租户编号。
-- 2. 目录仅用于角色授权分组,path 为空;首页只展示叶子。
-- 3. 不在此处写入 sys_role_menu;由管理员在 Web 角色管理中手工勾选。
-- 4. MySQL 5.7 可执行;所有 INSERT 均以 NOT EXISTS 保证幂等。
-- =============================================================================
-- 市场专员目录
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
)
SELECT
MD5(CONCAT(
'mobile-market-directory|', root.tenant_no, '|', root.id, '|M||市场专员'
)),
'市场专员',
root.id,
'M',
'',
NULL,
NULL,
1,
'1',
'1',
root.tenant_no,
10,
'小程序市场现场:市场专员菜单目录',
NULL,
'',
NOW(),
NULL,
NOW(),
0
FROM sys_menu root
WHERE root.parent_id = '0'
AND root.menu_name = '移动端菜单'
AND root.del_flag = 0
AND NOT EXISTS (
SELECT 1
FROM sys_menu existing
WHERE existing.tenant_no = root.tenant_no
AND existing.parent_id = root.id
AND existing.menu_name = '市场专员'
AND existing.menu_type = 'M'
AND existing.path = ''
AND existing.del_flag = 0
)
ON DUPLICATE KEY UPDATE
tenant_no = VALUES(tenant_no),
parent_id = VALUES(parent_id),
menu_name = VALUES(menu_name),
menu_type = VALUES(menu_type),
path = VALUES(path),
component = VALUES(component),
perms = VALUES(perms),
is_frame = VALUES(is_frame),
visible = VALUES(visible),
status = VALUES(status),
order_num = VALUES(order_num),
remark = VALUES(remark),
update_time = VALUES(update_time),
del_flag = VALUES(del_flag);
-- 地区经理目录
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
)
SELECT
MD5(CONCAT(
'mobile-market-directory|', root.tenant_no, '|', root.id, '|M||地区经理'
)),
'地区经理',
root.id,
'M',
'',
NULL,
NULL,
1,
'1',
'1',
root.tenant_no,
20,
'小程序市场现场:地区经理菜单目录',
NULL,
'',
NOW(),
NULL,
NOW(),
0
FROM sys_menu root
WHERE root.parent_id = '0'
AND root.menu_name = '移动端菜单'
AND root.del_flag = 0
AND NOT EXISTS (
SELECT 1
FROM sys_menu existing
WHERE existing.tenant_no = root.tenant_no
AND existing.parent_id = root.id
AND existing.menu_name = '地区经理'
AND existing.menu_type = 'M'
AND existing.path = ''
AND existing.del_flag = 0
)
ON DUPLICATE KEY UPDATE
tenant_no = VALUES(tenant_no),
parent_id = VALUES(parent_id),
menu_name = VALUES(menu_name),
menu_type = VALUES(menu_type),
path = VALUES(path),
component = VALUES(component),
perms = VALUES(perms),
is_frame = VALUES(is_frame),
visible = VALUES(visible),
status = VALUES(status),
order_num = VALUES(order_num),
remark = VALUES(remark),
update_time = VALUES(update_time),
del_flag = VALUES(del_flag);
-- 十二个业务叶子:每个角色目录各六个,均进入小程序受控 web-view。
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
)
SELECT
MD5(CONCAT(
'mobile-market-leaf|', root.tenant_no, '|', root.id, '|', directory.id,
'|', entry.path, '|', entry.perms
)),
entry.menu_name,
directory.id,
'C',
entry.path,
NULL,
entry.perms,
1,
'1',
'1',
root.tenant_no,
entry.order_num,
CONCAT('小程序市场现场:', entry.role_name, ' / ', entry.menu_name),
NULL,
'',
NOW(),
NULL,
NOW(),
0
FROM sys_menu root
INNER JOIN (
SELECT 'specialist' AS role_code, '市场专员' AS role_name,
'presign' AS module_code, '预签约订单' AS menu_name,
'/pages/market/webview/index?entry=specialist-presign' AS path,
'mini:market:specialist:presign' AS perms, 10 AS order_num
UNION ALL
SELECT 'specialist', '市场专员', 'protocol', '协议单',
'/pages/market/webview/index?entry=specialist-protocol',
'mini:market:specialist:protocol', 20
UNION ALL
SELECT 'specialist', '市场专员', 'payment-chase', '催缴信息',
'/pages/market/webview/index?entry=specialist-payment-chase',
'mini:market:specialist:payment-chase', 30
UNION ALL
SELECT 'specialist', '市场专员', 'payment', '缴费信息',
'/pages/market/webview/index?entry=specialist-payment',
'mini:market:specialist:payment', 40
UNION ALL
SELECT 'specialist', '市场专员', 'freight', '运费信息',
'/pages/market/webview/index?entry=specialist-freight',
'mini:market:specialist:freight', 50
UNION ALL
SELECT 'manager', '地区经理', 'presign', '预签约订单',
'/pages/market/webview/index?entry=manager-presign',
'mini:market:manager:presign', 10
UNION ALL
SELECT 'manager', '地区经理', 'protocol', '协议单',
'/pages/market/webview/index?entry=manager-protocol',
'mini:market:manager:protocol', 20
UNION ALL
SELECT 'manager', '地区经理', 'payment-chase', '催缴信息',
'/pages/market/webview/index?entry=manager-payment-chase',
'mini:market:manager:payment-chase', 30
UNION ALL
SELECT 'manager', '地区经理', 'payment', '缴费信息',
'/pages/market/webview/index?entry=manager-payment',
'mini:market:manager:payment', 40
UNION ALL
SELECT 'manager', '地区经理', 'freight', '运费信息',
'/pages/market/webview/index?entry=manager-freight',
'mini:market:manager:freight', 50
) entry
ON 1 = 1
INNER JOIN sys_menu directory
ON directory.tenant_no = root.tenant_no
AND directory.parent_id = root.id
AND directory.menu_name = entry.role_name
AND directory.menu_type = 'M'
AND directory.path = ''
AND directory.del_flag = 0
WHERE root.parent_id = '0'
AND root.menu_name = '移动端菜单'
AND root.del_flag = 0
AND NOT EXISTS (
SELECT 1
FROM sys_menu existing
WHERE existing.tenant_no = root.tenant_no
AND existing.parent_id = directory.id
AND existing.menu_name = entry.menu_name
AND existing.perms = entry.perms
AND existing.path = entry.path
AND existing.menu_type = 'C'
AND existing.del_flag = 0
)
ON DUPLICATE KEY UPDATE
tenant_no = VALUES(tenant_no),
parent_id = VALUES(parent_id),
menu_name = VALUES(menu_name),
menu_type = VALUES(menu_type),
path = VALUES(path),
component = VALUES(component),
perms = VALUES(perms),
is_frame = VALUES(is_frame),
visible = VALUES(visible),
status = VALUES(status),
order_num = VALUES(order_num),
remark = VALUES(remark),
update_time = VALUES(update_time),
del_flag = VALUES(del_flag);
@@ -1,56 +0,0 @@
-- 预签约订单转正式协议唯一约束
-- MySQL 5.7;重复执行安全。历史重复或错误同名索引会给出明确修复提示。
SET NAMES utf8mb4;
DELIMITER $$
DROP PROCEDURE IF EXISTS `ensure_presign_convert_unique`$$
CREATE PROCEDURE `ensure_presign_convert_unique`()
BEGIN
DECLARE duplicate_sources INT DEFAULT 0;
DECLARE idx_entries INT DEFAULT 0;
DECLARE idx_non_unique INT DEFAULT 1;
DECLARE idx_columns VARCHAR(512) DEFAULT '';
SELECT COUNT(*) INTO duplicate_sources
FROM (
SELECT `tenant_no`, `source_presign_order_id`
FROM `biz_collection_protocol`
WHERE `source_presign_order_id` IS NOT NULL
AND `source_presign_order_id` <> ''
GROUP BY `tenant_no`, `source_presign_order_id`
HAVING COUNT(*) > 1
) duplicate_rows;
IF duplicate_sources > 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'duplicate presign conversions found; remediation: reconcile duplicate formal protocols before rerunning migration';
END IF;
SELECT
COUNT(*),
COALESCE(MIN(NON_UNIQUE), 1),
COALESCE(GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX SEPARATOR ','), '')
INTO idx_entries, idx_non_unique, idx_columns
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'biz_collection_protocol'
AND INDEX_NAME = 'uk_collection_protocol_presign_source';
IF idx_entries = 0 THEN
ALTER TABLE `biz_collection_protocol`
ADD UNIQUE INDEX `uk_collection_protocol_presign_source`
(`tenant_no`, `source_presign_order_id`);
ELSEIF idx_entries <> 2
OR idx_non_unique <> 0
OR idx_columns <> 'tenant_no,source_presign_order_id' THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'wrong index uk_collection_protocol_presign_source; remediation: drop or rename it, then rerun migration';
END IF;
END$$
CALL `ensure_presign_convert_unique`()$$
DROP PROCEDURE `ensure_presign_convert_unique`$$
DELIMITER ;
@@ -1,8 +0,0 @@
-- 意向金不落协议侧快照表:正式协议通过 biz_collection_protocol.source_presign_order_id
-- 关联预签订单,读写同一份 biz_presign_transfer,保证财务审批/调整两边一致。
--
-- 若此前已执行过「创建 biz_protocol_deposit_transfer」的旧脚本,执行下方 DROP 清理。
SET NAMES utf8mb4;
DROP TABLE IF EXISTS `biz_protocol_deposit_transfer`;
@@ -1,192 +0,0 @@
-- 市场移动端运费批次查询与并发关联约束(MySQL 5.7,幂等)。
-- 若存量数据冲突或同名对象定义错误则明确中止,不静默移动/删除数据。
SET NAMES utf8mb4;
DELIMITER $$
DROP PROCEDURE IF EXISTS `ensure_market_freight_indexes`$$
CREATE PROCEDURE `ensure_market_freight_indexes`()
BEGIN
DECLARE duplicate_rows INT DEFAULT 0;
DECLARE object_count INT DEFAULT 0;
DECLARE index_non_unique INT DEFAULT 1;
DECLARE index_columns VARCHAR(512) DEFAULT '';
DECLARE index_prefix_count INT DEFAULT 0;
DECLARE index_type_count INT DEFAULT 0;
DECLARE index_type_name VARCHAR(32) DEFAULT '';
DECLARE generated_extra VARCHAR(255) DEFAULT '';
DECLARE generated_expression TEXT;
DECLARE normalized_generation_expression TEXT;
DECLARE generated_data_type VARCHAR(64) DEFAULT '';
DECLARE generated_length BIGINT DEFAULT 0;
DECLARE generated_nullable VARCHAR(3) DEFAULT '';
DECLARE exact_freight_unique_indexes INT DEFAULT 0;
SELECT COUNT(*) INTO duplicate_rows
FROM (
SELECT tenant_no, freight_no
FROM biz_sample_freight
GROUP BY tenant_no, freight_no
HAVING COUNT(*) > 1
) duplicate_freights;
IF duplicate_rows > 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'duplicate freight numbers found; reconcile them before rerunning migration';
END IF;
SELECT COUNT(*) INTO duplicate_rows
FROM (
SELECT tenant_no, sample_source, sample_id
FROM biz_sample_freight_item
WHERE del_flag = 0
GROUP BY tenant_no, sample_source, sample_id
HAVING COUNT(*) > 1
) duplicate_active_links;
IF duplicate_rows > 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'duplicate active sample freight links found; reconcile them before rerunning migration';
END IF;
SELECT COUNT(*) INTO object_count
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'biz_sample_freight_item'
AND COLUMN_NAME = 'active_sample_key';
IF object_count = 0 THEN
ALTER TABLE biz_sample_freight_item
ADD COLUMN active_sample_key VARCHAR(170)
GENERATED ALWAYS AS (
IF(del_flag = 0, CONCAT(tenant_no, '#', sample_source, '#', sample_id), NULL)
) STORED;
ELSE
SELECT EXTRA, GENERATION_EXPRESSION, DATA_TYPE,
CHARACTER_MAXIMUM_LENGTH, IS_NULLABLE
INTO generated_extra, generated_expression, generated_data_type,
generated_length, generated_nullable
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'biz_sample_freight_item'
AND COLUMN_NAME = 'active_sample_key';
SET normalized_generation_expression = LOWER(
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
COALESCE(generated_expression, ''),
'`', ''), ' ', ''), CHAR(9), ''), CHAR(10), ''), '(', ''), ')', '')
);
IF UPPER(COALESCE(generated_extra, '')) NOT LIKE '%STORED GENERATED%'
OR LOWER(COALESCE(generated_data_type, '')) <> 'varchar'
OR COALESCE(generated_length, 0) <> 170
OR UPPER(COALESCE(generated_nullable, '')) <> 'YES'
OR normalized_generation_expression <>
'ifdel_flag=0,concattenant_no,''#'',sample_source,''#'',sample_id,null' THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'wrong active_sample_key type/expression; drop column and dependent index, then rerun migration';
END IF;
END IF;
SELECT COUNT(*), COALESCE(MIN(NON_UNIQUE), 1),
COALESCE(GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX SEPARATOR ','), ''),
SUM(CASE WHEN SUB_PART IS NOT NULL THEN 1 ELSE 0 END),
COUNT(DISTINCT INDEX_TYPE), COALESCE(MIN(INDEX_TYPE), '')
INTO object_count, index_non_unique, index_columns,
index_prefix_count, index_type_count, index_type_name
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'biz_sample_freight_item'
AND INDEX_NAME = 'uk_bsfi_active_sample';
IF object_count = 0 THEN
ALTER TABLE biz_sample_freight_item
ADD UNIQUE INDEX uk_bsfi_active_sample (active_sample_key);
ELSEIF object_count <> 1 OR index_non_unique <> 0
OR index_columns <> 'active_sample_key' OR index_prefix_count <> 0
OR index_type_count <> 1 OR index_type_name <> 'BTREE' THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'wrong index uk_bsfi_active_sample; drop it and rerun migration';
END IF;
-- 旧迁移已创建 uk_sample_freight_tenant_no;任何名称的等价精确唯一索引均复用。
-- 但若本迁移预留名称已存在且定义错误,必须先修复,不能静默绕过。
SELECT COUNT(*), COALESCE(MIN(NON_UNIQUE), 1),
COALESCE(GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX SEPARATOR ','), ''),
SUM(CASE WHEN SUB_PART IS NOT NULL THEN 1 ELSE 0 END),
COUNT(DISTINCT INDEX_TYPE), COALESCE(MIN(INDEX_TYPE), '')
INTO object_count, index_non_unique, index_columns,
index_prefix_count, index_type_count, index_type_name
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'biz_sample_freight'
AND INDEX_NAME = 'uk_bsf_tenant_freight_no';
IF object_count > 0 AND (object_count <> 2 OR index_non_unique <> 0
OR index_columns <> 'tenant_no,freight_no' OR index_prefix_count <> 0
OR index_type_count <> 1 OR index_type_name <> 'BTREE') THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'wrong index uk_bsf_tenant_freight_no; drop it and rerun migration';
END IF;
SELECT COUNT(*) INTO exact_freight_unique_indexes
FROM (
SELECT INDEX_NAME
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'biz_sample_freight'
GROUP BY INDEX_NAME
HAVING MIN(NON_UNIQUE) = 0
AND MAX(NON_UNIQUE) = 0
AND GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX SEPARATOR ',') =
'tenant_no,freight_no'
AND SUM(CASE WHEN SUB_PART IS NOT NULL THEN 1 ELSE 0 END) = 0
AND COUNT(DISTINCT INDEX_TYPE) = 1
AND MIN(INDEX_TYPE) = 'BTREE'
) exact_freight_unique_index_definitions;
IF exact_freight_unique_indexes = 0 THEN
ALTER TABLE biz_sample_freight
ADD UNIQUE INDEX uk_bsf_tenant_freight_no (tenant_no, freight_no);
END IF;
SELECT COUNT(*), COALESCE(MIN(NON_UNIQUE), 1),
COALESCE(GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX SEPARATOR ','), ''),
SUM(CASE WHEN SUB_PART IS NOT NULL THEN 1 ELSE 0 END),
COUNT(DISTINCT INDEX_TYPE), COALESCE(MIN(INDEX_TYPE), '')
INTO object_count, index_non_unique, index_columns,
index_prefix_count, index_type_count, index_type_name
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'biz_sample_freight'
AND INDEX_NAME = 'idx_bsf_market_list';
IF object_count = 0 THEN
ALTER TABLE biz_sample_freight
ADD INDEX idx_bsf_market_list (tenant_no, del_flag, create_time, id);
ELSEIF object_count <> 4 OR index_non_unique <> 1
OR index_columns <> 'tenant_no,del_flag,create_time,id'
OR index_prefix_count <> 0 OR index_type_count <> 1
OR index_type_name <> 'BTREE' THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'wrong index idx_bsf_market_list; drop it and rerun migration';
END IF;
SELECT COUNT(*), COALESCE(MIN(NON_UNIQUE), 1),
COALESCE(GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX SEPARATOR ','), ''),
SUM(CASE WHEN SUB_PART IS NOT NULL THEN 1 ELSE 0 END),
COUNT(DISTINCT INDEX_TYPE), COALESCE(MIN(INDEX_TYPE), '')
INTO object_count, index_non_unique, index_columns,
index_prefix_count, index_type_count, index_type_name
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'biz_sample_freight_item'
AND INDEX_NAME = 'idx_bsfi_market_batch';
IF object_count = 0 THEN
ALTER TABLE biz_sample_freight_item
ADD INDEX idx_bsfi_market_batch (tenant_no, freight_id, del_flag, create_time, id);
ELSEIF object_count <> 5 OR index_non_unique <> 1
OR index_columns <> 'tenant_no,freight_id,del_flag,create_time,id'
OR index_prefix_count <> 0 OR index_type_count <> 1
OR index_type_name <> 'BTREE' THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'wrong index idx_bsfi_market_batch; drop it and rerun migration';
END IF;
END$$
CALL `ensure_market_freight_indexes`()$$
DROP PROCEDURE `ensure_market_freight_indexes`$$
DELIMITER ;
@@ -1,70 +0,0 @@
-- 市场移动端催缴/缴费查询索引(MySQL 5.7)。
-- 同名索引定义不一致时重建,重复执行保持幂等。
SET @schema_name = DATABASE();
SET @index_name = 'idx_biz_protocol_payment_schedule_market_scope';
SELECT GROUP_CONCAT(column_name ORDER BY seq_in_index SEPARATOR ','),
MIN(non_unique),
MAX(non_unique),
SUM(CASE WHEN sub_part IS NOT NULL THEN 1 ELSE 0 END),
COUNT(DISTINCT index_type),
MIN(index_type)
INTO @index_columns,
@index_non_unique_min,
@index_non_unique_max,
@index_prefix_columns,
@index_type_count,
@index_type
FROM information_schema.statistics
WHERE table_schema = @schema_name
AND table_name = 'biz_protocol_payment_schedule'
AND index_name = @index_name;
SET @ddl = CASE
WHEN @index_columns = 'tenant_no,sample_payment_id,del_flag'
AND @index_non_unique_min = 1
AND @index_non_unique_max = 1
AND @index_prefix_columns = 0
AND @index_type_count = 1
AND @index_type = 'BTREE' THEN 'SELECT 1'
WHEN @index_columns IS NULL THEN
'ALTER TABLE biz_protocol_payment_schedule ADD INDEX idx_biz_protocol_payment_schedule_market_scope (tenant_no, sample_payment_id, del_flag)'
ELSE
'ALTER TABLE biz_protocol_payment_schedule DROP INDEX idx_biz_protocol_payment_schedule_market_scope, ADD INDEX idx_biz_protocol_payment_schedule_market_scope (tenant_no, sample_payment_id, del_flag)'
END;
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @index_name = 'idx_biz_protocol_payment_transfer_market_scope';
SELECT GROUP_CONCAT(column_name ORDER BY seq_in_index SEPARATOR ','),
MIN(non_unique),
MAX(non_unique),
SUM(CASE WHEN sub_part IS NOT NULL THEN 1 ELSE 0 END),
COUNT(DISTINCT index_type),
MIN(index_type)
INTO @index_columns,
@index_non_unique_min,
@index_non_unique_max,
@index_prefix_columns,
@index_type_count,
@index_type
FROM information_schema.statistics
WHERE table_schema = @schema_name
AND table_name = 'biz_protocol_payment_transfer'
AND index_name = @index_name;
SET @ddl = CASE
WHEN @index_columns = 'tenant_no,schedule_id,del_flag'
AND @index_non_unique_min = 1
AND @index_non_unique_max = 1
AND @index_prefix_columns = 0
AND @index_type_count = 1
AND @index_type = 'BTREE' THEN 'SELECT 1'
WHEN @index_columns IS NULL THEN
'ALTER TABLE biz_protocol_payment_transfer ADD INDEX idx_biz_protocol_payment_transfer_market_scope (tenant_no, schedule_id, del_flag)'
ELSE
'ALTER TABLE biz_protocol_payment_transfer DROP INDEX idx_biz_protocol_payment_transfer_market_scope, ADD INDEX idx_biz_protocol_payment_transfer_market_scope (tenant_no, schedule_id, del_flag)'
END;
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
@@ -1,175 +0,0 @@
-- 市场移动端样本统一列表、进度与轻量办理索引(MySQL 5.7)。
-- 同名索引的列顺序、唯一性、前缀或类型不一致时重建;重复执行保持幂等。
SET @schema_name = DATABASE();
SET @table_name = 'biz_protocol_sample';
SET @index_name = 'idx_bps_market_sample_code';
SELECT GROUP_CONCAT(column_name ORDER BY seq_in_index SEPARATOR ','), MIN(non_unique), MAX(non_unique),
SUM(CASE WHEN sub_part IS NOT NULL THEN 1 ELSE 0 END), COUNT(DISTINCT index_type), MIN(index_type)
INTO @index_columns, @index_non_unique_min, @index_non_unique_max,
@index_prefix_columns, @index_type_count, @index_type
FROM information_schema.statistics
WHERE table_schema = @schema_name AND table_name = @table_name AND index_name = @index_name;
SET @ddl = CASE
WHEN @index_columns = 'tenant_no,del_flag,sample_code' AND @index_non_unique_min = 1
AND @index_non_unique_max = 1 AND @index_prefix_columns = 0
AND @index_type_count = 1 AND @index_type = 'BTREE' THEN 'SELECT 1'
WHEN @index_columns IS NULL THEN
'ALTER TABLE biz_protocol_sample ADD INDEX idx_bps_market_sample_code (tenant_no, del_flag, sample_code)'
ELSE
'ALTER TABLE biz_protocol_sample DROP INDEX idx_bps_market_sample_code, ADD INDEX idx_bps_market_sample_code (tenant_no, del_flag, sample_code)'
END;
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @table_name = 'biz_protocol_sample';
SET @index_name = 'idx_bps_market_parent';
SELECT GROUP_CONCAT(column_name ORDER BY seq_in_index SEPARATOR ','), MIN(non_unique), MAX(non_unique),
SUM(CASE WHEN sub_part IS NOT NULL THEN 1 ELSE 0 END), COUNT(DISTINCT index_type), MIN(index_type)
INTO @index_columns, @index_non_unique_min, @index_non_unique_max,
@index_prefix_columns, @index_type_count, @index_type
FROM information_schema.statistics
WHERE table_schema = @schema_name AND table_name = @table_name AND index_name = @index_name;
SET @ddl = CASE
WHEN @index_columns = 'tenant_no,del_flag,collection_protocol_id' AND @index_non_unique_min = 1
AND @index_non_unique_max = 1 AND @index_prefix_columns = 0
AND @index_type_count = 1 AND @index_type = 'BTREE' THEN 'SELECT 1'
WHEN @index_columns IS NULL THEN
'ALTER TABLE biz_protocol_sample ADD INDEX idx_bps_market_parent (tenant_no, del_flag, collection_protocol_id)'
ELSE
'ALTER TABLE biz_protocol_sample DROP INDEX idx_bps_market_parent, ADD INDEX idx_bps_market_parent (tenant_no, del_flag, collection_protocol_id)'
END;
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @table_name = 'biz_research_donation_sample';
SET @index_name = 'idx_brds_market_sample_code';
SELECT GROUP_CONCAT(column_name ORDER BY seq_in_index SEPARATOR ','), MIN(non_unique), MAX(non_unique),
SUM(CASE WHEN sub_part IS NOT NULL THEN 1 ELSE 0 END), COUNT(DISTINCT index_type), MIN(index_type)
INTO @index_columns, @index_non_unique_min, @index_non_unique_max,
@index_prefix_columns, @index_type_count, @index_type
FROM information_schema.statistics
WHERE table_schema = @schema_name AND table_name = @table_name AND index_name = @index_name;
SET @ddl = CASE
WHEN @index_columns = 'tenant_no,del_flag,sample_code' AND @index_non_unique_min = 1
AND @index_non_unique_max = 1 AND @index_prefix_columns = 0
AND @index_type_count = 1 AND @index_type = 'BTREE' THEN 'SELECT 1'
WHEN @index_columns IS NULL THEN
'ALTER TABLE biz_research_donation_sample ADD INDEX idx_brds_market_sample_code (tenant_no, del_flag, sample_code)'
ELSE
'ALTER TABLE biz_research_donation_sample DROP INDEX idx_brds_market_sample_code, ADD INDEX idx_brds_market_sample_code (tenant_no, del_flag, sample_code)'
END;
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @table_name = 'biz_research_donation_sample';
SET @index_name = 'idx_brds_market_parent';
SELECT GROUP_CONCAT(column_name ORDER BY seq_in_index SEPARATOR ','), MIN(non_unique), MAX(non_unique),
SUM(CASE WHEN sub_part IS NOT NULL THEN 1 ELSE 0 END), COUNT(DISTINCT index_type), MIN(index_type)
INTO @index_columns, @index_non_unique_min, @index_non_unique_max,
@index_prefix_columns, @index_type_count, @index_type
FROM information_schema.statistics
WHERE table_schema = @schema_name AND table_name = @table_name AND index_name = @index_name;
SET @ddl = CASE
WHEN @index_columns = 'tenant_no,del_flag,research_donation_id' AND @index_non_unique_min = 1
AND @index_non_unique_max = 1 AND @index_prefix_columns = 0
AND @index_type_count = 1 AND @index_type = 'BTREE' THEN 'SELECT 1'
WHEN @index_columns IS NULL THEN
'ALTER TABLE biz_research_donation_sample ADD INDEX idx_brds_market_parent (tenant_no, del_flag, research_donation_id)'
ELSE
'ALTER TABLE biz_research_donation_sample DROP INDEX idx_brds_market_parent, ADD INDEX idx_brds_market_parent (tenant_no, del_flag, research_donation_id)'
END;
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @table_name = 'biz_sample_reception';
SET @index_name = 'idx_bsr_market_sample_progress';
SELECT GROUP_CONCAT(column_name ORDER BY seq_in_index SEPARATOR ','), MIN(non_unique), MAX(non_unique),
SUM(CASE WHEN sub_part IS NOT NULL THEN 1 ELSE 0 END), COUNT(DISTINCT index_type), MIN(index_type)
INTO @index_columns, @index_non_unique_min, @index_non_unique_max,
@index_prefix_columns, @index_type_count, @index_type
FROM information_schema.statistics
WHERE table_schema = @schema_name AND table_name = @table_name AND index_name = @index_name;
SET @ddl = CASE
WHEN @index_columns = 'tenant_no,reception_source,sample_code,del_flag,reception_time' AND @index_non_unique_min = 1
AND @index_non_unique_max = 1 AND @index_prefix_columns = 0
AND @index_type_count = 1 AND @index_type = 'BTREE' THEN 'SELECT 1'
WHEN @index_columns IS NULL THEN
'ALTER TABLE biz_sample_reception ADD INDEX idx_bsr_market_sample_progress (tenant_no, reception_source, sample_code, del_flag, reception_time)'
ELSE
'ALTER TABLE biz_sample_reception DROP INDEX idx_bsr_market_sample_progress, ADD INDEX idx_bsr_market_sample_progress (tenant_no, reception_source, sample_code, del_flag, reception_time)'
END;
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @table_name = 'biz_qc_work_order';
SET @index_name = 'idx_bqwo_market_sample_progress';
SELECT GROUP_CONCAT(column_name ORDER BY seq_in_index SEPARATOR ','), MIN(non_unique), MAX(non_unique),
SUM(CASE WHEN sub_part IS NOT NULL THEN 1 ELSE 0 END), COUNT(DISTINCT index_type), MIN(index_type)
INTO @index_columns, @index_non_unique_min, @index_non_unique_max,
@index_prefix_columns, @index_type_count, @index_type
FROM information_schema.statistics
WHERE table_schema = @schema_name AND table_name = @table_name AND index_name = @index_name;
SET @ddl = CASE
WHEN @index_columns = 'tenant_no,reception_source,trigger_source,sample_code,del_flag' AND @index_non_unique_min = 1
AND @index_non_unique_max = 1 AND @index_prefix_columns = 0
AND @index_type_count = 1 AND @index_type = 'BTREE' THEN 'SELECT 1'
WHEN @index_columns IS NULL THEN
'ALTER TABLE biz_qc_work_order ADD INDEX idx_bqwo_market_sample_progress (tenant_no, reception_source, trigger_source, sample_code, del_flag)'
ELSE
'ALTER TABLE biz_qc_work_order DROP INDEX idx_bqwo_market_sample_progress, ADD INDEX idx_bqwo_market_sample_progress (tenant_no, reception_source, trigger_source, sample_code, del_flag)'
END;
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @table_name = 'biz_storage_release_review';
SET @index_name = 'idx_bsrr_market_sample_progress';
SELECT GROUP_CONCAT(column_name ORDER BY seq_in_index SEPARATOR ','), MIN(non_unique), MAX(non_unique),
SUM(CASE WHEN sub_part IS NOT NULL THEN 1 ELSE 0 END), COUNT(DISTINCT index_type), MIN(index_type)
INTO @index_columns, @index_non_unique_min, @index_non_unique_max,
@index_prefix_columns, @index_type_count, @index_type
FROM information_schema.statistics
WHERE table_schema = @schema_name AND table_name = @table_name AND index_name = @index_name;
SET @ddl = CASE
WHEN @index_columns = 'tenant_no,reception_source,sample_code,del_flag,storage_time' AND @index_non_unique_min = 1
AND @index_non_unique_max = 1 AND @index_prefix_columns = 0
AND @index_type_count = 1 AND @index_type = 'BTREE' THEN 'SELECT 1'
WHEN @index_columns IS NULL THEN
'ALTER TABLE biz_storage_release_review ADD INDEX idx_bsrr_market_sample_progress (tenant_no, reception_source, sample_code, del_flag, storage_time)'
ELSE
'ALTER TABLE biz_storage_release_review DROP INDEX idx_bsrr_market_sample_progress, ADD INDEX idx_bsrr_market_sample_progress (tenant_no, reception_source, sample_code, del_flag, storage_time)'
END;
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @table_name = 'biz_protocol_storage_cert';
SET @index_name = 'idx_bpsc_market_parent';
SELECT GROUP_CONCAT(column_name ORDER BY seq_in_index SEPARATOR ','), MIN(non_unique), MAX(non_unique),
SUM(CASE WHEN sub_part IS NOT NULL THEN 1 ELSE 0 END), COUNT(DISTINCT index_type), MIN(index_type)
INTO @index_columns, @index_non_unique_min, @index_non_unique_max,
@index_prefix_columns, @index_type_count, @index_type
FROM information_schema.statistics
WHERE table_schema = @schema_name AND table_name = @table_name AND index_name = @index_name;
SET @ddl = CASE
WHEN @index_columns = 'tenant_no,collection_protocol_id,del_flag' AND @index_non_unique_min = 1
AND @index_non_unique_max = 1 AND @index_prefix_columns = 0
AND @index_type_count = 1 AND @index_type = 'BTREE' THEN 'SELECT 1'
WHEN @index_columns IS NULL THEN
'ALTER TABLE biz_protocol_storage_cert ADD INDEX idx_bpsc_market_parent (tenant_no, collection_protocol_id, del_flag)'
ELSE
'ALTER TABLE biz_protocol_storage_cert DROP INDEX idx_bpsc_market_parent, ADD INDEX idx_bpsc_market_parent (tenant_no, collection_protocol_id, del_flag)'
END;
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @table_name = 'biz_protocol_fullmoon_todo';
SET @index_name = 'idx_bpft_market_parent';
SELECT GROUP_CONCAT(column_name ORDER BY seq_in_index SEPARATOR ','), MIN(non_unique), MAX(non_unique),
SUM(CASE WHEN sub_part IS NOT NULL THEN 1 ELSE 0 END), COUNT(DISTINCT index_type), MIN(index_type)
INTO @index_columns, @index_non_unique_min, @index_non_unique_max,
@index_prefix_columns, @index_type_count, @index_type
FROM information_schema.statistics
WHERE table_schema = @schema_name AND table_name = @table_name AND index_name = @index_name;
SET @ddl = CASE
WHEN @index_columns = 'tenant_no,collection_protocol_id,del_flag' AND @index_non_unique_min = 1
AND @index_non_unique_max = 1 AND @index_prefix_columns = 0
AND @index_type_count = 1 AND @index_type = 'BTREE' THEN 'SELECT 1'
WHEN @index_columns IS NULL THEN
'ALTER TABLE biz_protocol_fullmoon_todo ADD INDEX idx_bpft_market_parent (tenant_no, collection_protocol_id, del_flag)'
ELSE
'ALTER TABLE biz_protocol_fullmoon_todo DROP INDEX idx_bpft_market_parent, ADD INDEX idx_bpft_market_parent (tenant_no, collection_protocol_id, del_flag)'
END;
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
@@ -1,235 +0,0 @@
-- =============================================================================
-- 移动端菜单重组:市场部 / OA工作流两大目录
--
-- 目标结构:
-- 移动端菜单
-- ├─ 市场部
-- │ ├─ 预签约订单(专员)…运费信息(专员)
-- │ └─ 预签约订单(经理)…运费信息(经理)
-- └─ OA工作流
-- └─ 发起审批…开启微信通知
--
-- 说明:
-- 1. 复用原「市场专员」目录 id,改名为「市场部」;原「地区经理」目录软删。
-- 2. 叶子 id / path / perms 不变,仅调整 parent_id、menu_name、order_num。
-- 3. 已授权 OA 叶子的角色自动补授「OA工作流」目录(幂等)。
-- 4. MySQL 5.7 可执行。
-- =============================================================================
-- 1) 「市场专员」→「市场部」
UPDATE sys_menu market_dir
INNER JOIN sys_menu root
ON root.id = market_dir.parent_id
AND root.parent_id = '0'
AND root.menu_name = '移动端菜单'
AND root.del_flag = 0
SET
market_dir.menu_name = '市场部',
market_dir.order_num = 10,
market_dir.remark = '小程序:市场部菜单目录',
market_dir.update_time = NOW()
WHERE market_dir.menu_type = 'M'
AND market_dir.path = ''
AND market_dir.del_flag = 0
AND market_dir.menu_name IN ('市场专员', '市场部');
-- 2) 新建「OA工作流」目录(每个租户移动端根下一条)
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
)
SELECT
MD5(CONCAT(
'mobile-oa-directory|', root.tenant_no, '|', root.id, '|M||OA工作流'
)),
'OA工作流',
root.id,
'M',
'',
NULL,
NULL,
1,
'1',
'1',
root.tenant_no,
20,
'小程序:OA工作流菜单目录',
NULL,
'',
NOW(),
NULL,
NOW(),
0
FROM sys_menu root
WHERE root.parent_id = '0'
AND root.menu_name = '移动端菜单'
AND root.del_flag = 0
AND NOT EXISTS (
SELECT 1
FROM sys_menu existing
WHERE existing.tenant_no = root.tenant_no
AND existing.parent_id = root.id
AND existing.menu_name = 'OA工作流'
AND existing.menu_type = 'M'
AND existing.path = ''
AND existing.del_flag = 0
)
ON DUPLICATE KEY UPDATE
tenant_no = VALUES(tenant_no),
parent_id = VALUES(parent_id),
menu_name = VALUES(menu_name),
menu_type = VALUES(menu_type),
path = VALUES(path),
order_num = VALUES(order_num),
remark = VALUES(remark),
update_time = VALUES(update_time),
del_flag = VALUES(del_flag);
-- 3) 市场叶子:挂到「市场部」,名称加(专员)/(经理),调整排序
UPDATE sys_menu leaf
INNER JOIN sys_menu root
ON root.parent_id = '0'
AND root.menu_name = '移动端菜单'
AND root.del_flag = 0
AND root.tenant_no = leaf.tenant_no
INNER JOIN sys_menu market_dir
ON market_dir.tenant_no = root.tenant_no
AND market_dir.parent_id = root.id
AND market_dir.menu_name = '市场部'
AND market_dir.menu_type = 'M'
AND market_dir.path = ''
AND market_dir.del_flag = 0
INNER JOIN (
SELECT 'mini:market:specialist:presign' AS perms, '预签约订单(专员)' AS menu_name, 10 AS order_num
UNION ALL SELECT 'mini:market:specialist:protocol', '协议单(专员)', 20
UNION ALL SELECT 'mini:market:specialist:payment-chase', '催缴信息(专员)', 30
UNION ALL SELECT 'mini:market:specialist:payment', '缴费信息(专员)', 40
UNION ALL SELECT 'mini:market:specialist:freight', '运费信息(专员)', 50
UNION ALL SELECT 'mini:market:manager:presign', '预签约订单(经理)', 60
UNION ALL SELECT 'mini:market:manager:protocol', '协议单(经理)', 70
UNION ALL SELECT 'mini:market:manager:payment-chase', '催缴信息(经理)', 80
UNION ALL SELECT 'mini:market:manager:payment', '缴费信息(经理)', 90
UNION ALL SELECT 'mini:market:manager:freight', '运费信息(经理)', 100
) map ON map.perms = leaf.perms
SET
leaf.parent_id = market_dir.id,
leaf.menu_name = map.menu_name,
leaf.order_num = map.order_num,
leaf.remark = CONCAT('小程序市场现场:', map.menu_name),
leaf.update_time = NOW()
WHERE leaf.menu_type = 'C'
AND leaf.del_flag = 0
AND leaf.perms LIKE 'mini:market:%';
-- 4) OA 叶子:挂到「OA工作流」
UPDATE sys_menu leaf
INNER JOIN sys_menu root
ON root.parent_id = '0'
AND root.menu_name = '移动端菜单'
AND root.del_flag = 0
AND root.tenant_no = leaf.tenant_no
INNER JOIN sys_menu oa_dir
ON oa_dir.tenant_no = root.tenant_no
AND oa_dir.parent_id = root.id
AND oa_dir.menu_name = 'OA工作流'
AND oa_dir.menu_type = 'M'
AND oa_dir.path = ''
AND oa_dir.del_flag = 0
SET
leaf.parent_id = oa_dir.id,
leaf.update_time = NOW()
WHERE leaf.del_flag = 0
AND leaf.menu_type = 'C'
AND (
leaf.perms IN (
'mini:oa:start',
'mini:oa:drafts',
'mini:oa:pending',
'mini:oa:done',
'mini:oa:mine',
'mini:oa:cc',
'oa:mention',
'mini:oa:wechat-notify'
)
OR leaf.path IN (
'/pages/oa/start/index',
'/pages/oa/drafts/index',
'/pages/oa/pending/index',
'/pages/oa/done/index',
'/pages/oa/mine/index',
'/pages/oa/cc/index',
'/pages/oa/mentions/index',
'/pages/oa/wechat-notify/index'
)
);
-- 5) 软删「地区经理」目录(步骤 3 已把子节点迁到市场部;避免同表子查询触发 1093)
UPDATE sys_menu manager_dir
INNER JOIN sys_menu root
ON root.id = manager_dir.parent_id
AND root.parent_id = '0'
AND root.menu_name = '移动端菜单'
AND root.del_flag = 0
SET
manager_dir.del_flag = 1,
manager_dir.update_time = NOW(),
manager_dir.remark = '已重组至市场部,软删'
WHERE manager_dir.menu_type = 'M'
AND manager_dir.path = ''
AND manager_dir.menu_name = '地区经理'
AND manager_dir.del_flag = 0;
-- 6) 已有 OA 叶子授权的角色补授「OA工作流」目录
INSERT INTO sys_role_menu (
id, role_id, menu_id, tenant_no, order_num, remark,
create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT
MD5(CONCAT(r.id, '|', oa_dir.id)),
r.id,
oa_dir.id,
IFNULL(NULLIF(r.tenant_no, ''), oa_dir.tenant_no),
0,
'grant OA工作流 directory to roles with OA leaf menus',
NULL,
'',
NOW(),
NULL,
NOW(),
0
FROM sys_role r
INNER JOIN sys_role_menu rm
ON rm.role_id = r.id
AND rm.del_flag = 0
INNER JOIN sys_menu leaf
ON leaf.id = rm.menu_id
AND leaf.del_flag = 0
AND leaf.menu_type = 'C'
AND (
leaf.perms IN (
'mini:oa:start',
'mini:oa:drafts',
'mini:oa:pending',
'mini:oa:done',
'mini:oa:mine',
'mini:oa:cc',
'oa:mention',
'mini:oa:wechat-notify'
)
OR leaf.path LIKE '/pages/oa/%'
)
INNER JOIN sys_menu oa_dir
ON oa_dir.id = leaf.parent_id
AND oa_dir.menu_name = 'OA工作流'
AND oa_dir.menu_type = 'M'
AND oa_dir.del_flag = 0
WHERE r.del_flag = 0
AND NOT EXISTS (
SELECT 1
FROM sys_role_menu x
WHERE x.del_flag = 0
AND x.role_id = r.id
AND x.menu_id = oa_dir.id
)
GROUP BY r.id, oa_dir.id, r.tenant_no, oa_dir.tenant_no;
@@ -1,15 +0,0 @@
-- =============================================================================
-- 菜单改名:缴费财务确认及开票 → 缴费财务确认
-- id: 50000000000000000000000000000004
-- perms: biz:cellStoragePaymentConfirm:list
-- =============================================================================
UPDATE sys_menu
SET menu_name = '缴费财务确认',
update_time = NOW()
WHERE del_flag = 0
AND (
id = '50000000000000000000000000000004'
OR perms = 'biz:cellStoragePaymentConfirm:list'
)
AND menu_name <> '缴费财务确认';
@@ -1,219 +0,0 @@
-- 样本客户服务扩展表 + 租户字典(2026-10-13)
-- 对齐 docs/superpowers/specs/2026-10-13-customer-service-management-design.md
CREATE TABLE IF NOT EXISTS biz_sample_customer_service (
id VARCHAR(64) NOT NULL COMMENT '主键ID',
sample_code VARCHAR(32) NOT NULL COMMENT '样本编码',
collection_protocol_id VARCHAR(64) DEFAULT NULL COMMENT '采集协议单ID冗余',
sms_send_date DATE DEFAULT NULL COMMENT '短信发送日期',
sms_send_status VARCHAR(64) DEFAULT NULL COMMENT '短信发送情况',
microbe_followup_date DATE DEFAULT NULL COMMENT '微生物回访日期',
microbe_followup_status VARCHAR(64) DEFAULT NULL COMMENT '微生物回访情况',
fullmoon_followup_date DATE DEFAULT NULL COMMENT '满月回访时间',
fullmoon_followup_status VARCHAR(64) DEFAULT NULL COMMENT '满月回访情况',
household_registered VARCHAR(64) DEFAULT NULL COMMENT '是否上户口',
einvoice_send_time DATETIME DEFAULT NULL COMMENT '电子发票发送时间',
effective_address VARCHAR(512) DEFAULT NULL COMMENT '有效地址',
tenant_no VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号',
order_num INT 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 COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT NULL COMMENT '更新人id',
update_time DATETIME NOT NULL COMMENT '更新时间',
del_flag SMALLINT NOT NULL DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (id),
UNIQUE KEY uk_bscs_tenant_sample (tenant_no, sample_code),
KEY idx_bscs_protocol (tenant_no, collection_protocol_id, del_flag)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='样本客户服务扩展';
-- 注:uk_bscs_tenant_sample 不含 del_flag;del_flag=0 行唯一性由应用层保证(与 biz_sample_storage_cert_qc 一致)。
CREATE TABLE IF NOT EXISTS biz_customer_service_dict (
id VARCHAR(64) NOT NULL COMMENT '主键ID',
dict_type VARCHAR(64) NOT NULL COMMENT '字典类型',
dict_value VARCHAR(128) NOT NULL COMMENT '选项值',
is_system CHAR(1) NOT NULL DEFAULT '0' COMMENT '1预置 0手输',
tenant_no VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号',
order_num INT 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 COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT NULL COMMENT '更新人id',
update_time DATETIME NOT NULL COMMENT '更新时间',
del_flag SMALLINT NOT NULL DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (id),
UNIQUE KEY uk_bcsd_tenant_type_value (tenant_no, dict_type, dict_value)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='客户服务字典';
-- 注:uk_bcsd_tenant_type_value 不含 del_flag;del_flag=0 行唯一性由应用层保证(与 biz_sample_storage_cert_qc 一致)。
-- 预置字典种子(tenant_no=zkxw,is_system=1;幂等 NOT EXISTS)
INSERT INTO biz_customer_service_dict (
id, dict_type, dict_value, is_system, tenant_no, order_num, remark,
create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT REPLACE(UUID(), '-', ''), 'sms_send_status', '已发', '1', 'zkxw', 1, NULL,
NULL, '', NOW(), NULL, NOW(), 0
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM biz_customer_service_dict
WHERE tenant_no = 'zkxw' AND dict_type = 'sms_send_status' AND dict_value = '已发' AND del_flag = 0
);
INSERT INTO biz_customer_service_dict (
id, dict_type, dict_value, is_system, tenant_no, order_num, remark,
create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT REPLACE(UUID(), '-', ''), 'sms_send_status', '合并发', '1', 'zkxw', 2, NULL,
NULL, '', NOW(), NULL, NOW(), 0
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM biz_customer_service_dict
WHERE tenant_no = 'zkxw' AND dict_type = 'sms_send_status' AND dict_value = '合并发' AND del_flag = 0
);
INSERT INTO biz_customer_service_dict (
id, dict_type, dict_value, is_system, tenant_no, order_num, remark,
create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT REPLACE(UUID(), '-', ''), 'sms_send_status', '/', '1', 'zkxw', 3, NULL,
NULL, '', NOW(), NULL, NOW(), 0
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM biz_customer_service_dict
WHERE tenant_no = 'zkxw' AND dict_type = 'sms_send_status' AND dict_value = '/' AND del_flag = 0
);
INSERT INTO biz_customer_service_dict (
id, dict_type, dict_value, is_system, tenant_no, order_num, remark,
create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT REPLACE(UUID(), '-', ''), 'microbe_followup_status', '回访成功', '1', 'zkxw', 1, NULL,
NULL, '', NOW(), NULL, NOW(), 0
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM biz_customer_service_dict
WHERE tenant_no = 'zkxw' AND dict_type = 'microbe_followup_status' AND dict_value = '回访成功' AND del_flag = 0
);
INSERT INTO biz_customer_service_dict (
id, dict_type, dict_value, is_system, tenant_no, order_num, remark,
create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT REPLACE(UUID(), '-', ''), 'microbe_followup_status', '无人接听', '1', 'zkxw', 2, NULL,
NULL, '', NOW(), NULL, NOW(), 0
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM biz_customer_service_dict
WHERE tenant_no = 'zkxw' AND dict_type = 'microbe_followup_status' AND dict_value = '无人接听' AND del_flag = 0
);
INSERT INTO biz_customer_service_dict (
id, dict_type, dict_value, is_system, tenant_no, order_num, remark,
create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT REPLACE(UUID(), '-', ''), 'microbe_followup_status', '挂机', '1', 'zkxw', 3, NULL,
NULL, '', NOW(), NULL, NOW(), 0
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM biz_customer_service_dict
WHERE tenant_no = 'zkxw' AND dict_type = 'microbe_followup_status' AND dict_value = '挂机' AND del_flag = 0
);
INSERT INTO biz_customer_service_dict (
id, dict_type, dict_value, is_system, tenant_no, order_num, remark,
create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT REPLACE(UUID(), '-', ''), 'microbe_followup_status', '微信通知', '1', 'zkxw', 4, NULL,
NULL, '', NOW(), NULL, NOW(), 0
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM biz_customer_service_dict
WHERE tenant_no = 'zkxw' AND dict_type = 'microbe_followup_status' AND dict_value = '微信通知' AND del_flag = 0
);
INSERT INTO biz_customer_service_dict (
id, dict_type, dict_value, is_system, tenant_no, order_num, remark,
create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT REPLACE(UUID(), '-', ''), 'fullmoon_followup_status', '回访成功', '1', 'zkxw', 1, NULL,
NULL, '', NOW(), NULL, NOW(), 0
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM biz_customer_service_dict
WHERE tenant_no = 'zkxw' AND dict_type = 'fullmoon_followup_status' AND dict_value = '回访成功' AND del_flag = 0
);
INSERT INTO biz_customer_service_dict (
id, dict_type, dict_value, is_system, tenant_no, order_num, remark,
create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT REPLACE(UUID(), '-', ''), 'fullmoon_followup_status', '无人接听', '1', 'zkxw', 2, NULL,
NULL, '', NOW(), NULL, NOW(), 0
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM biz_customer_service_dict
WHERE tenant_no = 'zkxw' AND dict_type = 'fullmoon_followup_status' AND dict_value = '无人接听' AND del_flag = 0
);
INSERT INTO biz_customer_service_dict (
id, dict_type, dict_value, is_system, tenant_no, order_num, remark,
create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT REPLACE(UUID(), '-', ''), 'fullmoon_followup_status', '挂机', '1', 'zkxw', 3, NULL,
NULL, '', NOW(), NULL, NOW(), 0
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM biz_customer_service_dict
WHERE tenant_no = 'zkxw' AND dict_type = 'fullmoon_followup_status' AND dict_value = '挂机' AND del_flag = 0
);
INSERT INTO biz_customer_service_dict (
id, dict_type, dict_value, is_system, tenant_no, order_num, remark,
create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT REPLACE(UUID(), '-', ''), 'fullmoon_followup_status', '微信通知', '1', 'zkxw', 4, NULL,
NULL, '', NOW(), NULL, NOW(), 0
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM biz_customer_service_dict
WHERE tenant_no = 'zkxw' AND dict_type = 'fullmoon_followup_status' AND dict_value = '微信通知' AND del_flag = 0
);
INSERT INTO biz_customer_service_dict (
id, dict_type, dict_value, is_system, tenant_no, order_num, remark,
create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT REPLACE(UUID(), '-', ''), 'household_registered', '是', '1', 'zkxw', 1, NULL,
NULL, '', NOW(), NULL, NOW(), 0
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM biz_customer_service_dict
WHERE tenant_no = 'zkxw' AND dict_type = 'household_registered' AND dict_value = '是' AND del_flag = 0
);
INSERT INTO biz_customer_service_dict (
id, dict_type, dict_value, is_system, tenant_no, order_num, remark,
create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT REPLACE(UUID(), '-', ''), 'household_registered', '否', '1', 'zkxw', 2, NULL,
NULL, '', NOW(), NULL, NOW(), 0
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM biz_customer_service_dict
WHERE tenant_no = 'zkxw' AND dict_type = 'household_registered' AND dict_value = '否' AND del_flag = 0
);
INSERT INTO biz_customer_service_dict (
id, dict_type, dict_value, is_system, tenant_no, order_num, remark,
create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT REPLACE(UUID(), '-', ''), 'household_registered', '未回复', '1', 'zkxw', 3, NULL,
NULL, '', NOW(), NULL, NOW(), 0
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM biz_customer_service_dict
WHERE tenant_no = 'zkxw' AND dict_type = 'household_registered' AND dict_value = '未回复' AND del_flag = 0
);
@@ -1,42 +0,0 @@
-- =============================================================================
-- 下线小程序「市场专员 / 地区经理 → 样本信息」菜单(2026-10-13)
-- =============================================================================
--
-- 样本信息(客服管理)不面向市场专员、地区经理;移动端入口与 OA H5 路由已移除。
-- 本脚本将已入库的叶子菜单设为不可见,并软删除已有角色菜单关联,
-- 避免历史授权仍可绕过入口直接调用样本 API。
--
-- perms:
-- mini:market:specialist:sample
-- mini:market:manager:sample
-- visible: '0'=隐藏
-- =============================================================================
UPDATE sys_menu
SET visible = '0',
update_time = NOW()
WHERE del_flag = 0
AND (
perms IN (
'mini:market:specialist:sample',
'mini:market:manager:sample'
)
OR path IN (
'/pages/market/webview/index?entry=specialist-sample',
'/pages/market/webview/index?entry=manager-sample'
)
);
UPDATE sys_role_menu
SET del_flag = 1,
update_time = NOW()
WHERE sys_role_menu.del_flag = 0
AND menu_id IN (
SELECT id
FROM sys_menu
WHERE del_flag = 0
AND perms IN (
'mini:market:specialist:sample',
'mini:market:manager:sample'
)
);
@@ -1,21 +0,0 @@
-- =============================================================================
-- 菜单改名:客户管理 → 客户基础信息
-- id: 121
-- perms: biz:customer:list
-- 父级:市场基础信息
--
-- 【前端固定菜单】同步修改:
-- - web-admin/src/router/modules/sqxp.ts
-- - backend/app/api/menu.py
-- =============================================================================
UPDATE sys_menu
SET menu_name = '客户基础信息',
update_time = NOW()
WHERE del_flag = 0
AND (
id = '121'
OR perms = 'biz:customer:list'
)
AND menu_type = 'C'
AND menu_name <> '客户基础信息';
@@ -1,21 +0,0 @@
-- =============================================================================
-- 菜单改名:样本信息管理 → 客户服务管理
-- id: 50000000000000000000000000000013
-- perms: cell-storage:sample-info:list
-- 父级:存储服务
--
-- 【前端固定菜单】后续任务同步修改:
-- - web-admin/src/router/modules/sqxp.ts
-- - backend/app/api/menu.py
-- =============================================================================
UPDATE sys_menu
SET menu_name = '客户服务管理',
update_time = NOW()
WHERE del_flag = 0
AND (
id = '50000000000000000000000000000013'
OR perms = 'cell-storage:sample-info:list'
)
AND menu_type = 'C'
AND menu_name <> '客户服务管理';
@@ -1,11 +0,0 @@
-- 科研捐赠:当月核量号(创建时按 create_time 年月生成 YYYYMM###;历史可空)
-- Spec: docs/superpowers/specs/2026-10-13-research-donation-list-excel-parity-design.md
SET NAMES utf8mb4;
ALTER TABLE `biz_research_donation`
ADD COLUMN `monthly_verify_no` varchar(16) DEFAULT NULL COMMENT '当月核量号 YYYYMM+3位序号' AFTER `donation_no`;
-- 租户内号唯一;多行 NULL 在 MySQL 下不冲突
ALTER TABLE `biz_research_donation`
ADD UNIQUE KEY `uk_rd_tenant_monthly_verify_no` (`tenant_no`, `monthly_verify_no`);
@@ -1,158 +0,0 @@
-- 样本级储存证 / 保险 + 协议级数据迁移(2026-10-13)
-- 对齐 docs/superpowers/specs/2026-10-13-customer-service-management-design.md
CREATE TABLE IF NOT EXISTS biz_sample_storage_cert (
id VARCHAR(64) NOT NULL COMMENT '主键ID',
sample_code VARCHAR(32) NOT NULL COMMENT '样本编码',
collection_protocol_id VARCHAR(64) DEFAULT NULL COMMENT '采集协议单ID冗余',
make_status CHAR(1) DEFAULT '0' COMMENT '制作状态 0-未制作 1-已制作',
cert_image_urls VARCHAR(4000) DEFAULT NULL COMMENT '证书图片URLs',
make_date CHAR(8) DEFAULT NULL COMMENT '制作日期YYYYMMDD',
handle_time DATETIME DEFAULT NULL COMMENT '办理时间',
handler_user_id VARCHAR(64) DEFAULT NULL COMMENT '办理人用户ID',
mail_cert_time DATETIME DEFAULT NULL COMMENT '邮寄储存证时间',
tenant_no VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号',
order_num INT 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 COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT NULL COMMENT '更新人id',
update_time DATETIME NOT NULL COMMENT '更新时间',
del_flag SMALLINT NOT NULL DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (id),
UNIQUE KEY uk_bssc_tenant_sample (tenant_no, sample_code),
KEY idx_bssc_protocol (tenant_no, collection_protocol_id, del_flag)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='样本储存证办理';
-- 注:uk_bssc_tenant_sample 不含 del_flag;del_flag=0 行唯一性由应用层保证(与 biz_sample_storage_cert_qc 一致)。
CREATE TABLE IF NOT EXISTS biz_sample_insurance (
id VARCHAR(64) NOT NULL COMMENT '主键ID',
sample_code VARCHAR(32) NOT NULL COMMENT '样本编码',
collection_protocol_id VARCHAR(64) DEFAULT NULL COMMENT '采集协议单ID冗余',
todo_status CHAR(1) NOT NULL DEFAULT '0' COMMENT '1已办保险 0未办保险',
handle_date CHAR(8) DEFAULT NULL COMMENT '办理日期',
handle_time DATETIME DEFAULT NULL COMMENT '办理时间',
handler_user_id VARCHAR(64) DEFAULT NULL COMMENT '办理人用户ID',
handle_remark VARCHAR(512) DEFAULT NULL COMMENT '办理备注',
insurance_attachments VARCHAR(4000) DEFAULT NULL COMMENT '保险附件逗号分隔',
insurance_company_id VARCHAR(64) DEFAULT NULL COMMENT '保险公司ID',
insurance_company_name VARCHAR(200) DEFAULT NULL COMMENT '保险公司名称(冗余快照)',
insurance_type VARCHAR(200) DEFAULT NULL COMMENT '保险险种',
insurance_policy_no VARCHAR(100) DEFAULT NULL COMMENT '保险单号',
insurance_effective_date CHAR(8) DEFAULT NULL COMMENT '保险单生效时间YYYYMMDD',
insurance_expiry_date CHAR(8) DEFAULT NULL COMMENT '保险单失效时间YYYYMMDD',
insurance_subscribed CHAR(1) DEFAULT '0' COMMENT '是否已订购保险',
insurance_fee DECIMAL(18, 2) DEFAULT NULL COMMENT '保单费用(元)',
policy_send_time DATETIME DEFAULT NULL COMMENT '保单发送时间',
tenant_no VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号',
order_num INT 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 COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT NULL COMMENT '更新人id',
update_time DATETIME NOT NULL COMMENT '更新时间',
del_flag SMALLINT NOT NULL DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (id),
UNIQUE KEY uk_bsi_tenant_sample (tenant_no, sample_code),
KEY idx_bsi_protocol (tenant_no, collection_protocol_id, del_flag)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='样本保险办理';
-- 注:uk_bsi_tenant_sample 不含 del_flag;del_flag=0 行唯一性由应用层保证(与 biz_sample_storage_cert_qc 一致)。
-- 迁移:协议级储存证 → 协议下每个未删样本一行(幂等 NOT EXISTS)
INSERT INTO biz_sample_storage_cert (
id, sample_code, collection_protocol_id, make_status, cert_image_urls,
make_date, handle_time, handler_user_id, mail_cert_time,
tenant_no, order_num, remark, create_by, create_dept_id, create_time,
update_by, update_time, del_flag
)
SELECT
-- MySQL 5.7 同语句批量 UUID() 可能撞主键,用业务键派生稳定唯一 id
MD5(CONCAT('bssc#', c.tenant_no, '#', ps.sample_code, '#', c.id, '#', ps.id)),
ps.sample_code,
c.collection_protocol_id,
c.make_status,
c.cert_image_urls,
c.make_date,
c.handle_time,
c.handler_user_id,
NULL,
c.tenant_no,
c.order_num,
c.remark,
c.create_by,
c.create_dept_id,
NOW(),
c.update_by,
NOW(),
0
FROM biz_protocol_storage_cert c
INNER JOIN biz_protocol_sample ps
ON (ps.collection_protocol_id COLLATE utf8mb4_unicode_ci) = (c.collection_protocol_id COLLATE utf8mb4_unicode_ci)
AND (ps.tenant_no COLLATE utf8mb4_unicode_ci) = (c.tenant_no COLLATE utf8mb4_unicode_ci)
AND ps.del_flag = 0
WHERE c.del_flag = 0
AND ps.sample_code IS NOT NULL
AND ps.sample_code <> ''
AND NOT EXISTS (
SELECT 1 FROM biz_sample_storage_cert t
WHERE (t.tenant_no COLLATE utf8mb4_unicode_ci) = (c.tenant_no COLLATE utf8mb4_unicode_ci)
AND (t.sample_code COLLATE utf8mb4_unicode_ci) = (ps.sample_code COLLATE utf8mb4_unicode_ci)
AND t.del_flag = 0
);
-- 迁移:协议级满月保险 → 协议下每个未删样本一行(幂等 NOT EXISTS)
INSERT INTO biz_sample_insurance (
id, sample_code, collection_protocol_id, todo_status, handle_date, handle_time,
handler_user_id, handle_remark, insurance_attachments,
insurance_company_id, insurance_company_name, insurance_type, insurance_policy_no,
insurance_effective_date, insurance_expiry_date, insurance_subscribed, insurance_fee,
policy_send_time,
tenant_no, order_num, remark, create_by, create_dept_id, create_time,
update_by, update_time, del_flag
)
SELECT
MD5(CONCAT('bsi#', t.tenant_no, '#', ps.sample_code, '#', t.id, '#', ps.id)),
ps.sample_code,
t.collection_protocol_id,
t.todo_status,
t.handle_date,
t.handle_time,
t.handler_user_id,
t.handle_remark,
t.insurance_attachments,
t.insurance_company_id,
t.insurance_company_name,
t.insurance_type,
t.insurance_policy_no,
t.insurance_effective_date,
t.insurance_expiry_date,
t.insurance_subscribed,
t.insurance_fee,
NULL,
t.tenant_no,
t.order_num,
t.remark,
t.create_by,
t.create_dept_id,
NOW(),
t.update_by,
NOW(),
0
FROM biz_protocol_fullmoon_todo t
INNER JOIN biz_protocol_sample ps
ON (ps.collection_protocol_id COLLATE utf8mb4_unicode_ci) = (t.collection_protocol_id COLLATE utf8mb4_unicode_ci)
AND (ps.tenant_no COLLATE utf8mb4_unicode_ci) = (t.tenant_no COLLATE utf8mb4_unicode_ci)
AND ps.del_flag = 0
WHERE t.del_flag = 0
AND ps.sample_code IS NOT NULL
AND ps.sample_code <> ''
AND NOT EXISTS (
SELECT 1 FROM biz_sample_insurance s
WHERE (s.tenant_no COLLATE utf8mb4_unicode_ci) = (t.tenant_no COLLATE utf8mb4_unicode_ci)
AND (s.sample_code COLLATE utf8mb4_unicode_ci) = (ps.sample_code COLLATE utf8mb4_unicode_ci)
AND s.del_flag = 0
);
@@ -1,12 +0,0 @@
-- 样本运费表校对规则对齐存量库 utf8mb4_unicode_ci
-- 背景:2026-10-07 建表仅写 DEFAULT CHARSET=utf8mb4,MySQL 5.7 默认落成
-- utf8mb4_general_ci;与协议/样本等 unicode_ci 表 JOIN 会报 1267
-- Illegal mix of collations,移动端 /mini/market/*/freights 返回 503。
SET NAMES utf8mb4;
ALTER TABLE `biz_sample_freight`
CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE `biz_sample_freight_item`
CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
@@ -1,119 +0,0 @@
-- 采集协议单缴费 v1→v2 迁移 · 清单与校验(只读,不改数据)
-- 实际迁移请用:backend/scripts/migrate_protocol_payment_v1_to_v2.py
-- MySQL 5.7 / utf8mb4
SET NAMES utf8mb4;
-- ---------------------------------------------------------------------------
-- 1) 仍为 v1 的协议概览
-- ---------------------------------------------------------------------------
SELECT
p.id AS protocol_id,
p.protocol_no,
p.tenant_no,
p.payment_model_version,
(
SELECT COUNT(*)
FROM biz_protocol_sample s
WHERE s.collection_protocol_id = p.id
AND s.del_flag = 0
AND IFNULL(s.sample_status, '') <> 'yzf'
) AS active_sample_count,
(
SELECT COUNT(*)
FROM biz_protocol_payment pay
WHERE pay.collection_protocol_id = p.id AND pay.del_flag = 0
) AS v1_payment_count,
(
SELECT COUNT(*)
FROM biz_protocol_sample_payment sp
WHERE sp.collection_protocol_id = p.id AND sp.del_flag = 0
) AS v2_sample_payment_count
FROM biz_collection_protocol p
WHERE p.del_flag = 0
AND p.payment_model_version = 1
ORDER BY p.create_time;
-- ---------------------------------------------------------------------------
-- 2) 人工清单候选:多样本
-- ---------------------------------------------------------------------------
SELECT
p.id AS protocol_id,
p.protocol_no,
COUNT(s.id) AS active_sample_count,
'multi_sample' AS skip_reason
FROM biz_collection_protocol p
INNER JOIN biz_protocol_sample s
ON s.collection_protocol_id = p.id
AND s.del_flag = 0
AND IFNULL(s.sample_status, '') <> 'yzf'
WHERE p.del_flag = 0
AND p.payment_model_version = 1
GROUP BY p.id, p.protocol_no
HAVING COUNT(s.id) > 1;
-- ---------------------------------------------------------------------------
-- 3) 人工清单候选:有退费痕迹的明细
-- ---------------------------------------------------------------------------
SELECT DISTINCT
p.id AS protocol_id,
p.protocol_no,
d.id AS detail_id,
d.pay_status,
d.refund_amount,
'has_refund' AS skip_reason
FROM biz_collection_protocol p
INNER JOIN biz_protocol_payment pay
ON pay.collection_protocol_id = p.id AND pay.del_flag = 0
INNER JOIN biz_protocol_payment_detail d
ON d.payment_id = pay.id AND d.del_flag = 0
WHERE p.del_flag = 0
AND p.payment_model_version = 1
AND (
d.pay_status IN ('tfqrz', 'ytf')
OR (d.refund_amount IS NOT NULL AND d.refund_amount > 0)
OR IFNULL(d.refund_attachments, '') <> ''
OR IFNULL(d.refund_remark, '') <> ''
OR IFNULL(d.refund_finance_confirm_attachments, '') <> ''
OR IFNULL(d.refund_finance_remark, '') <> ''
);
-- ---------------------------------------------------------------------------
-- 4) 可自动迁(单样本、有 v1 缴费、尚无 v2 方案)粗筛
-- 注意:退费还需用第 3 节排除,本查询仅看样本数
-- ---------------------------------------------------------------------------
SELECT
p.id AS protocol_id,
p.protocol_no,
s.sample_code,
pay.contract_amount,
pay.pay_mode
FROM biz_collection_protocol p
INNER JOIN biz_protocol_payment pay
ON pay.collection_protocol_id = p.id AND pay.del_flag = 0
INNER JOIN biz_protocol_sample s
ON s.collection_protocol_id = p.id
AND s.del_flag = 0
AND IFNULL(s.sample_status, '') <> 'yzf'
WHERE p.del_flag = 0
AND p.payment_model_version = 1
AND NOT EXISTS (
SELECT 1 FROM biz_protocol_sample_payment sp
WHERE sp.collection_protocol_id = p.id AND sp.del_flag = 0
)
GROUP BY p.id, p.protocol_no, s.sample_code, pay.contract_amount, pay.pay_mode
HAVING COUNT(DISTINCT s.id) = 1;
-- ---------------------------------------------------------------------------
-- 5) 迁移后抽样校验:某协议 v1 detail 金额合计 vs v2 transfer 金额合计
-- 将 @pid 换成协议 ID
-- ---------------------------------------------------------------------------
-- SET @pid := 'YOUR_PROTOCOL_ID';
-- SELECT 'v1_detail' AS src, COUNT(*) AS cnt, IFNULL(SUM(transfer_amount), 0) AS amt
-- FROM biz_protocol_payment_detail d
-- INNER JOIN biz_protocol_payment pay ON pay.id = d.payment_id AND pay.del_flag = 0
-- WHERE pay.collection_protocol_id = @pid AND d.del_flag = 0
-- UNION ALL
-- SELECT 'v2_transfer', COUNT(*), IFNULL(SUM(transfer_amount), 0)
-- FROM biz_protocol_payment_transfer t
-- WHERE t.collection_protocol_id = @pid AND t.del_flag = 0;
@@ -1,161 +0,0 @@
-- =============================================================================
-- 打开全部 OA 审批应用的「审批人去重」,并强制重新发布(2026-10-16)
-- MySQL 5.7
--
-- 行为:
-- 1. 每个已有「最新已发布」版本的应用:无论 approverDedup 当前是否已为 true,
-- 一律复制为新的 published 行(表单、流程不变,开关强制 true),
-- 旧的 is_latest_published 改为 0。
-- 已在途实例仍指向旧 version id;新发起走新版本。
-- 2. 全部未删除草稿:就地把 approverDedup 设为 true(不把草稿未完成改动一并发布)。
-- 3. 从未发布过的应用:把版本号最大的那条草稿标为已发布,并打开开关。
--
-- 注意:本脚本会为每个已发布应用再升一版;短时间内重复执行会连续升版。
-- =============================================================================
DROP PROCEDURE IF EXISTS sp_oa_enable_approver_dedup;
DELIMITER $$
CREATE PROCEDURE sp_oa_enable_approver_dedup()
BEGIN
DECLARE v_done INT DEFAULT 0;
DECLARE v_src_id VARCHAR(64);
DECLARE v_app_id VARCHAR(64);
DECLARE v_tenant VARCHAR(64);
DECLARE v_next_no INT;
DECLARE v_new_id VARCHAR(64);
DECLARE v_advanced TEXT;
-- 强制:所有当前最新已发布版本都重新发布一版
DECLARE cur CURSOR FOR
SELECT id, app_id, tenant_no
FROM oa_app_version
WHERE del_flag = 0
AND is_latest_published = '1';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_done = 1;
OPEN cur;
read_loop: LOOP
FETCH cur INTO v_src_id, v_app_id, v_tenant;
IF v_done = 1 THEN
LEAVE read_loop;
END IF;
SELECT IFNULL(MAX(version_no), 0) + 1
INTO v_next_no
FROM oa_app_version
WHERE app_id = v_app_id
AND tenant_no = v_tenant;
SET v_new_id = UPPER(REPLACE(UUID(), '-', ''));
SELECT CAST(
JSON_SET(
CASE
WHEN advanced_settings_json IS NULL
OR advanced_settings_json = ''
OR JSON_VALID(advanced_settings_json) = 0
THEN CAST('{}' AS JSON)
ELSE CAST(advanced_settings_json AS JSON)
END,
'$.approverDedup',
CAST('true' AS JSON)
) AS CHAR
)
INTO v_advanced
FROM oa_app_version
WHERE id = v_src_id;
INSERT INTO oa_app_version (
id, app_id, version_no, status, is_latest_published,
form_schema_json, flow_tree_json, advanced_settings_json,
tenant_no, order_num, remark,
create_by, create_dept_id, create_time,
update_by, update_time, del_flag
)
SELECT
v_new_id, app_id, v_next_no, 'published', '1',
form_schema_json, flow_tree_json, v_advanced,
tenant_no, order_num, remark,
create_by, create_dept_id, NOW(),
'sql-approver-dedup', NOW(), 0
FROM oa_app_version
WHERE id = v_src_id;
UPDATE oa_app_version
SET is_latest_published = '0',
update_by = 'sql-approver-dedup',
update_time = NOW()
WHERE id = v_src_id;
END LOOP;
CLOSE cur;
-- 草稿只改开关,避免把未完成的表单/流程改动一起发布
UPDATE oa_app_version
SET advanced_settings_json = CAST(
JSON_SET(
CASE
WHEN advanced_settings_json IS NULL
OR advanced_settings_json = ''
OR JSON_VALID(advanced_settings_json) = 0
THEN CAST('{}' AS JSON)
ELSE CAST(advanced_settings_json AS JSON)
END,
'$.approverDedup',
CAST('true' AS JSON)
) AS CHAR
),
update_by = 'sql-approver-dedup',
update_time = NOW()
WHERE del_flag = 0
AND status = 'draft';
-- 从未发布:提升当前最大版本号草稿
UPDATE oa_app_version d
INNER JOIN (
SELECT app_id, tenant_no, MAX(version_no) AS version_no
FROM oa_app_version
WHERE del_flag = 0
AND status = 'draft'
GROUP BY app_id, tenant_no
) latest_draft
ON latest_draft.app_id = d.app_id
AND latest_draft.tenant_no = d.tenant_no
AND latest_draft.version_no = d.version_no
LEFT JOIN (
SELECT app_id, tenant_no
FROM oa_app_version
WHERE del_flag = 0
AND is_latest_published = '1'
GROUP BY app_id, tenant_no
) published
ON published.app_id = d.app_id
AND published.tenant_no = d.tenant_no
SET d.status = 'published',
d.is_latest_published = '1',
d.update_by = 'sql-approver-dedup',
d.update_time = NOW()
WHERE d.del_flag = 0
AND d.status = 'draft'
AND published.app_id IS NULL;
END$$
DELIMITER ;
CALL sp_oa_enable_approver_dedup();
DROP PROCEDURE IF EXISTS sp_oa_enable_approver_dedup;
-- 核对:最新已发布应由本脚本生成,且开关为 true
SELECT app_id,
tenant_no,
version_no,
status,
is_latest_published,
update_by,
JSON_UNQUOTE(JSON_EXTRACT(advanced_settings_json, '$.approverDedup')) AS approver_dedup
FROM oa_app_version
WHERE del_flag = 0
AND is_latest_published = '1'
ORDER BY tenant_no, app_id;
@@ -1,43 +0,0 @@
-- OA 已办中心 · 上传银行回执(按钮 F)
-- perms: oa:done:bank-receipt
-- 执行前确认父菜单:perms='oa:done' 或菜单名「已办中心」
-- 执行后请在「角色管理」勾选该按钮权限,并重新登录/刷新菜单权限码
-- 查找父菜单(手工确认):
-- SELECT id, parent_id, menu_name, perms, menu_type
-- FROM sys_menu
-- WHERE del_flag = 0 AND (perms = 'oa:done' OR menu_name LIKE '%已办中心%');
INSERT INTO sys_menu (
id, parent_id, menu_name, 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
)
SELECT
REPLACE(UUID(), '-', ''),
m.id,
'上传银行回执',
'F',
'',
'',
'oa:done:bank-receipt',
1,
'0',
'1',
'zkxw',
90,
'已办中心操作列:上传/管理银行回执单',
'system',
'',
NOW(),
'system',
NOW(),
0
FROM sys_menu m
WHERE m.del_flag = 0
AND m.perms = 'oa:done'
AND NOT EXISTS (
SELECT 1 FROM sys_menu x
WHERE x.del_flag = 0 AND x.perms = 'oa:done:bank-receipt'
)
LIMIT 1;
@@ -1,322 +0,0 @@
-- 采集协议单 / 科研捐赠:状态变更历史表(追加写,同类型不覆盖)
-- 对齐 docs/superpowers/plans/2026-10-17-protocol-status-change-history.md
CREATE TABLE IF NOT EXISTS biz_protocol_status_change (
id VARCHAR(64) NOT NULL COMMENT '主键ID',
collection_protocol_id VARCHAR(64) NOT NULL COMMENT '采集协议单ID',
change_type VARCHAR(32) NOT NULL COMMENT '变更类型 activate/expire/void/to_research',
from_status VARCHAR(16) DEFAULT NULL COMMENT '变更前状态码',
to_status VARCHAR(16) NOT NULL COMMENT '变更后状态码',
change_time DATETIME NOT NULL COMMENT '业务变更时间',
change_desc VARCHAR(512) DEFAULT NULL COMMENT '变更描述',
void_type VARCHAR(32) DEFAULT NULL COMMENT '作废类型(字典 xyzflx)',
operator_user_id VARCHAR(64) DEFAULT NULL COMMENT '操作人用户ID',
tenant_no VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号',
order_num INT 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 COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT NULL COMMENT '更新人id',
update_time DATETIME NOT NULL COMMENT '更新时间',
del_flag SMALLINT NOT NULL DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (id),
KEY idx_bpsc_protocol_time (tenant_no, collection_protocol_id, del_flag, change_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='采集协议单状态变更记录';
CREATE TABLE IF NOT EXISTS biz_donation_status_change (
id VARCHAR(64) NOT NULL COMMENT '主键ID',
research_donation_id VARCHAR(64) NOT NULL COMMENT '科研捐赠单ID',
change_type VARCHAR(32) NOT NULL COMMENT '变更类型 activate/expire/void/to_storage',
from_status VARCHAR(16) DEFAULT NULL COMMENT '变更前状态码',
to_status VARCHAR(16) NOT NULL COMMENT '变更后状态码',
change_time DATETIME NOT NULL COMMENT '业务变更时间',
change_desc VARCHAR(512) DEFAULT NULL COMMENT '变更描述',
void_type VARCHAR(32) DEFAULT NULL COMMENT '作废类型(字典 xyzflx)',
operator_user_id VARCHAR(64) DEFAULT NULL COMMENT '操作人用户ID',
tenant_no VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号',
order_num INT 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 COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT NULL COMMENT '更新人id',
update_time DATETIME NOT NULL COMMENT '更新时间',
del_flag SMALLINT NOT NULL DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (id),
KEY idx_bdsc_donation_time (tenant_no, research_donation_id, del_flag, change_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='科研捐赠状态变更记录';
-- 回填:主表已有快照各插 1 条(幂等:同协议同 change_type 尚无历史时)
INSERT INTO biz_protocol_status_change (
id, collection_protocol_id, change_type, from_status, to_status,
change_time, change_desc, void_type, operator_user_id,
tenant_no, order_num, remark, create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT
UPPER(MD5(CONCAT(p.id, '|activate'))),
p.id,
'activate',
NULL,
'sxz',
p.effective_time,
p.effective_desc,
NULL,
p.update_by,
p.tenant_no,
0,
NULL,
p.update_by,
'',
IFNULL(p.effective_time, NOW()),
p.update_by,
IFNULL(p.effective_time, NOW()),
0
FROM biz_collection_protocol p
WHERE p.del_flag = 0
AND p.effective_time IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM biz_protocol_status_change c
WHERE c.collection_protocol_id = p.id
AND c.change_type = 'activate'
AND c.del_flag = 0
);
INSERT INTO biz_protocol_status_change (
id, collection_protocol_id, change_type, from_status, to_status,
change_time, change_desc, void_type, operator_user_id,
tenant_no, order_num, remark, create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT
UPPER(MD5(CONCAT(p.id, '|expire'))),
p.id,
'expire',
NULL,
'ygq',
p.expire_time,
p.expire_desc,
NULL,
p.update_by,
p.tenant_no,
0,
NULL,
p.update_by,
'',
IFNULL(p.expire_time, NOW()),
p.update_by,
IFNULL(p.expire_time, NOW()),
0
FROM biz_collection_protocol p
WHERE p.del_flag = 0
AND p.expire_time IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM biz_protocol_status_change c
WHERE c.collection_protocol_id = p.id
AND c.change_type = 'expire'
AND c.del_flag = 0
);
INSERT INTO biz_protocol_status_change (
id, collection_protocol_id, change_type, from_status, to_status,
change_time, change_desc, void_type, operator_user_id,
tenant_no, order_num, remark, create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT
UPPER(MD5(CONCAT(p.id, '|void'))),
p.id,
'void',
NULL,
'yzf',
p.void_time,
p.void_desc,
p.void_type,
p.update_by,
p.tenant_no,
0,
NULL,
p.update_by,
'',
IFNULL(p.void_time, NOW()),
p.update_by,
IFNULL(p.void_time, NOW()),
0
FROM biz_collection_protocol p
WHERE p.del_flag = 0
AND p.void_time IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM biz_protocol_status_change c
WHERE c.collection_protocol_id = p.id
AND c.change_type = 'void'
AND c.del_flag = 0
);
INSERT INTO biz_protocol_status_change (
id, collection_protocol_id, change_type, from_status, to_status,
change_time, change_desc, void_type, operator_user_id,
tenant_no, order_num, remark, create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT
UPPER(MD5(CONCAT(p.id, '|to_research'))),
p.id,
'to_research',
NULL,
'zky',
p.to_research_time,
p.to_research_desc,
NULL,
p.update_by,
p.tenant_no,
0,
NULL,
p.update_by,
'',
IFNULL(p.to_research_time, NOW()),
p.update_by,
IFNULL(p.to_research_time, NOW()),
0
FROM biz_collection_protocol p
WHERE p.del_flag = 0
AND p.to_research_time IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM biz_protocol_status_change c
WHERE c.collection_protocol_id = p.id
AND c.change_type = 'to_research'
AND c.del_flag = 0
);
INSERT INTO biz_donation_status_change (
id, research_donation_id, change_type, from_status, to_status,
change_time, change_desc, void_type, operator_user_id,
tenant_no, order_num, remark, create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT
UPPER(MD5(CONCAT(d.id, '|activate'))),
d.id,
'activate',
NULL,
'sxz',
d.effective_time,
d.effective_desc,
NULL,
d.update_by,
d.tenant_no,
0,
NULL,
d.update_by,
'',
IFNULL(d.effective_time, NOW()),
d.update_by,
IFNULL(d.effective_time, NOW()),
0
FROM biz_research_donation d
WHERE d.del_flag = 0
AND d.effective_time IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM biz_donation_status_change c
WHERE c.research_donation_id = d.id
AND c.change_type = 'activate'
AND c.del_flag = 0
);
INSERT INTO biz_donation_status_change (
id, research_donation_id, change_type, from_status, to_status,
change_time, change_desc, void_type, operator_user_id,
tenant_no, order_num, remark, create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT
UPPER(MD5(CONCAT(d.id, '|expire'))),
d.id,
'expire',
NULL,
'ygq',
d.expire_time,
d.expire_desc,
NULL,
d.update_by,
d.tenant_no,
0,
NULL,
d.update_by,
'',
IFNULL(d.expire_time, NOW()),
d.update_by,
IFNULL(d.expire_time, NOW()),
0
FROM biz_research_donation d
WHERE d.del_flag = 0
AND d.expire_time IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM biz_donation_status_change c
WHERE c.research_donation_id = d.id
AND c.change_type = 'expire'
AND c.del_flag = 0
);
INSERT INTO biz_donation_status_change (
id, research_donation_id, change_type, from_status, to_status,
change_time, change_desc, void_type, operator_user_id,
tenant_no, order_num, remark, create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT
UPPER(MD5(CONCAT(d.id, '|void'))),
d.id,
'void',
NULL,
'yzf',
d.void_time,
d.void_desc,
d.void_type,
d.update_by,
d.tenant_no,
0,
NULL,
d.update_by,
'',
IFNULL(d.void_time, NOW()),
d.update_by,
IFNULL(d.void_time, NOW()),
0
FROM biz_research_donation d
WHERE d.del_flag = 0
AND d.void_time IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM biz_donation_status_change c
WHERE c.research_donation_id = d.id
AND c.change_type = 'void'
AND c.del_flag = 0
);
INSERT INTO biz_donation_status_change (
id, research_donation_id, change_type, from_status, to_status,
change_time, change_desc, void_type, operator_user_id,
tenant_no, order_num, remark, create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT
UPPER(MD5(CONCAT(d.id, '|to_storage'))),
d.id,
'to_storage',
NULL,
'zckc',
d.to_storage_time,
d.to_storage_desc,
NULL,
d.update_by,
d.tenant_no,
0,
NULL,
d.update_by,
'',
IFNULL(d.to_storage_time, NOW()),
d.update_by,
IFNULL(d.to_storage_time, NOW()),
0
FROM biz_research_donation d
WHERE d.del_flag = 0
AND d.to_storage_time IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM biz_donation_status_change c
WHERE c.research_donation_id = d.id
AND c.change_type = 'to_storage'
AND c.del_flag = 0
);
@@ -1,96 +0,0 @@
-- 顺丰月结:运费可空,增加单号与结算字段;历史顺丰补未结算,不改原金额。
ALTER TABLE biz_sample_freight
MODIFY COLUMN freight_amount DECIMAL(18,2) NULL DEFAULT NULL COMMENT '运费(元),顺丰月结未填时为空',
ADD COLUMN waybill_no VARCHAR(64) NULL DEFAULT NULL COMMENT '顺丰单号' AFTER freight_amount,
ADD COLUMN settle_status VARCHAR(8) NULL DEFAULT NULL COMMENT '结算状态:wjs 未结算,yjs 已结算' AFTER waybill_no,
ADD COLUMN settle_time DATETIME NULL DEFAULT NULL COMMENT '结算时间' AFTER settle_status;
UPDATE biz_sample_freight
SET settle_status = 'wjs'
WHERE del_flag = 0
AND transport_mode = 'sfkd'
AND (settle_status IS NULL OR settle_status = '');
-- 菜单插在缴费财务确认之前。id 尚未占用。
UPDATE sys_menu
SET order_num = IFNULL(order_num, 0) + 1,
update_time = NOW()
WHERE id = '50000000000000000000000000000004'
AND del_flag = 0
AND NOT EXISTS (
SELECT 1 FROM (
SELECT id FROM sys_menu
WHERE id = '50000000000000000000000000000023' AND del_flag = 0
) existing_menu
);
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
)
SELECT
'50000000000000000000000000000023',
'顺丰月结',
'CEL00000000000000000000000000001',
'C',
'sf-express-settlement',
'/cell-storage/sf-express-settlement/index',
'cell-storage:sf-express-settlement:list',
1,
'1',
'1',
'zkxw',
(
SELECT order_num - 1 FROM (
SELECT order_num FROM sys_menu
WHERE id = '50000000000000000000000000000004' AND del_flag = 0
LIMIT 1
) pay_menu
),
'细胞存储:顺丰月结',
NULL,
'',
NOW(),
NULL,
NOW(),
0
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM sys_menu m
WHERE m.del_flag = 0
AND (
m.id = '50000000000000000000000000000023'
OR m.perms = 'cell-storage:sf-express-settlement:list'
)
);
INSERT INTO sys_role_menu (
id, role_id, menu_id, tenant_no, order_num, remark,
create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT
MD5(CONCAT(r.id, '|50000000000000000000000000000023')),
r.id,
'50000000000000000000000000000023',
IFNULL(NULLIF(r.tenant_no, ''), 'zkxw'),
0,
'grant sf-express settlement to payment-confirm roles',
NULL,
'',
NOW(),
NULL,
NOW(),
0
FROM sys_role r
INNER JOIN sys_role_menu rm
ON rm.role_id = r.id
AND rm.menu_id = '50000000000000000000000000000004'
AND rm.del_flag = 0
WHERE r.del_flag = 0
AND NOT EXISTS (
SELECT 1 FROM sys_role_menu x
WHERE x.role_id = r.id
AND x.menu_id = '50000000000000000000000000000023'
AND x.del_flag = 0
);
@@ -1,31 +0,0 @@
-- 微信服务号通知发送日志(幂等)
-- 对齐 docs/superpowers/specs/2026-10-22-basic-experiment-delivery-remind-design.md
CREATE TABLE IF NOT EXISTS biz_wx_notify_log (
id VARCHAR(64) NOT NULL COMMENT '主键ID',
biz_type VARCHAR(64) NOT NULL COMMENT '业务类型,如 basic_experiment_delivery_remind',
biz_id VARCHAR(64) NOT NULL COMMENT '业务主键(如交付计划ID)',
notify_date VARCHAR(10) NOT NULL COMMENT '提醒日 YYYY-MM-DD',
user_id VARCHAR(64) DEFAULT NULL COMMENT '接收用户ID',
status VARCHAR(16) NOT NULL DEFAULT 'success' COMMENT 'success/fail',
errcode VARCHAR(64) DEFAULT NULL COMMENT '微信/内部错误码',
errmsg VARCHAR(512) DEFAULT NULL COMMENT '错误信息',
tenant_no VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号',
order_num INT 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 COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT NULL COMMENT '更新人id',
update_time DATETIME NOT NULL COMMENT '更新时间',
del_flag SMALLINT NOT NULL DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (id),
UNIQUE KEY uk_wx_notify_tenant_biz (
tenant_no,
biz_type,
biz_id,
notify_date
),
KEY idx_wx_notify_tenant_time (tenant_no, create_time),
KEY idx_wx_notify_lookup (tenant_no, biz_type, biz_id, notify_date, del_flag)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='微信服务号通知发送日志';
@@ -1,93 +0,0 @@
-- 细胞产品包装主数据
-- 执行前请确认目标库;可重复执行:表 IF NOT EXISTS,种子数据按编码去重
CREATE TABLE IF NOT EXISTS `biz_cell_product_package` (
`id` VARCHAR(64) NOT NULL COMMENT '主键ID',
`package_code` VARCHAR(64) NOT NULL COMMENT '包装编码',
`package_name` VARCHAR(200) NOT NULL COMMENT '包装名称',
`tenant_no` VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号',
`order_num` INT NULL COMMENT '排序号',
`remark` VARCHAR(512) NULL COMMENT '备注',
`create_by` VARCHAR(64) NULL COMMENT '创建人id',
`create_dept_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人部门ID',
`create_time` DATETIME NOT NULL COMMENT '创建时间',
`update_by` VARCHAR(64) NULL COMMENT '更新人id',
`update_time` DATETIME NOT NULL COMMENT '更新时间',
`del_flag` SMALLINT NOT NULL DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (`id`),
KEY `idx_cell_product_package_tenant` (`tenant_no`, `del_flag`),
KEY `idx_cell_product_package_code` (`tenant_no`, `package_code`, `del_flag`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='细胞产品包装';
-- 初始包装类型(租户 zkxw;编码已存在则跳过)
INSERT INTO `biz_cell_product_package` (
`id`, `package_code`, `package_name`, `tenant_no`, `order_num`, `remark`,
`create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`
)
SELECT * FROM (
SELECT
'PKG000000000000000000000000000001' AS `id`,
'BZ01' AS `package_code`,
'冻存管' AS `package_name`,
'zkxw' AS `tenant_no`,
1 AS `order_num`,
'' AS `remark`,
NULL AS `create_by`,
'' AS `create_dept_id`,
NOW() AS `create_time`,
NULL AS `update_by`,
NOW() AS `update_time`,
0 AS `del_flag`
) AS t
WHERE NOT EXISTS (
SELECT 1 FROM `biz_cell_product_package` p
WHERE p.`tenant_no` = 'zkxw' AND p.`package_code` = 'BZ01' AND p.`del_flag` = 0
);
INSERT INTO `biz_cell_product_package` (
`id`, `package_code`, `package_name`, `tenant_no`, `order_num`, `remark`,
`create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`
)
SELECT * FROM (
SELECT
'PKG000000000000000000000000000002' AS `id`,
'BZ02' AS `package_code`,
'离心管' AS `package_name`,
'zkxw' AS `tenant_no`,
2 AS `order_num`,
'' AS `remark`,
NULL AS `create_by`,
'' AS `create_dept_id`,
NOW() AS `create_time`,
NULL AS `update_by`,
NOW() AS `update_time`,
0 AS `del_flag`
) AS t
WHERE NOT EXISTS (
SELECT 1 FROM `biz_cell_product_package` p
WHERE p.`tenant_no` = 'zkxw' AND p.`package_code` = 'BZ02' AND p.`del_flag` = 0
);
INSERT INTO `biz_cell_product_package` (
`id`, `package_code`, `package_name`, `tenant_no`, `order_num`, `remark`,
`create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`
)
SELECT * FROM (
SELECT
'PKG000000000000000000000000000003' AS `id`,
'BZ03' AS `package_code`,
'一次性塑料血袋' AS `package_name`,
'zkxw' AS `tenant_no`,
3 AS `order_num`,
'' AS `remark`,
NULL AS `create_by`,
'' AS `create_dept_id`,
NOW() AS `create_time`,
NULL AS `update_by`,
NOW() AS `update_time`,
0 AS `del_flag`
) AS t
WHERE NOT EXISTS (
SELECT 1 FROM `biz_cell_product_package` p
WHERE p.`tenant_no` = 'zkxw' AND p.`package_code` = 'BZ03' AND p.`del_flag` = 0
);
@@ -1,234 +0,0 @@
-- 修复内置工艺路线缺步骤(2026-10-22)
-- 对齐 docs/superpowers/specs/2026-10-22-cc-builtin-route-steps-fix-design.md
--
-- 现象:CC样本处理-BUILTIN 等内置路线仅有路线头、无 biz_process_route_step,
-- 导致 B* 入库编码自动派发后任务显示「样本处理 0/0」。
-- 策略:对「内置 + 无有效步骤 + 源路线有步骤」的 *-BUILTIN 路线,
-- 从源路线(route_code 去掉 -BUILTIN)深拷贝步骤及产出/数据采集等子表。
-- 幂等:仅当目标内置路线当前 0 条有效步骤时执行;已有步骤则跳过。
-- MySQL 5.7
-- 1) 需要修复的内置路线 ↔ 源路线
DROP TEMPORARY TABLE IF EXISTS tmp_builtin_route_fix;
CREATE TEMPORARY TABLE tmp_builtin_route_fix (
dst_route_id VARCHAR(64) NOT NULL,
src_route_id VARCHAR(64) NOT NULL,
tenant_no VARCHAR(64) NOT NULL,
PRIMARY KEY (dst_route_id)
) ENGINE=MEMORY;
INSERT INTO tmp_builtin_route_fix (dst_route_id, src_route_id, tenant_no)
SELECT
b.id,
(
SELECT s.id
FROM biz_process_route s
WHERE s.tenant_no = b.tenant_no
AND s.route_code = REPLACE(b.route_code, '-BUILTIN', '')
AND s.del_flag = 0
AND IFNULL(s.is_builtin, '0') = '0'
ORDER BY
CASE WHEN IFNULL(s.is_current, '0') = '1' THEN 0 ELSE 1 END,
s.update_time DESC,
s.create_time DESC
LIMIT 1
) AS src_route_id,
b.tenant_no
FROM biz_process_route b
WHERE IFNULL(b.is_builtin, '0') = '1'
AND b.del_flag = 0
AND b.route_code LIKE '%-BUILTIN'
AND NOT EXISTS (
SELECT 1
FROM biz_process_route_step st
WHERE st.route_id = b.id
AND st.del_flag = 0
);
-- 去掉找不到源、或源也无步骤的行
DELETE FROM tmp_builtin_route_fix
WHERE src_route_id IS NULL
OR NOT EXISTS (
SELECT 1
FROM biz_process_route_step st
WHERE st.route_id = tmp_builtin_route_fix.src_route_id
AND st.del_flag = 0
);
-- 2) 源步骤 → 新步骤 ID 映射
DROP TEMPORARY TABLE IF EXISTS tmp_step_id_map;
CREATE TEMPORARY TABLE tmp_step_id_map (
src_step_id VARCHAR(64) NOT NULL,
dst_step_id VARCHAR(64) NOT NULL,
dst_route_id VARCHAR(64) NOT NULL,
tenant_no VARCHAR(64) NOT NULL,
PRIMARY KEY (src_step_id)
) ENGINE=MEMORY;
INSERT INTO tmp_step_id_map (src_step_id, dst_step_id, dst_route_id, tenant_no)
SELECT
ss.id,
-- MD5+源步骤保证同语句多行不撞主键;前缀避免与历史 UUID 冲突
CONCAT('BF', UPPER(MD5(CONCAT('fix-builtin-step:', f.dst_route_id, ':', ss.id)))),
f.dst_route_id,
f.tenant_no
FROM tmp_builtin_route_fix f
INNER JOIN biz_process_route_step ss
ON ss.route_id = f.src_route_id
AND ss.del_flag = 0;
-- 3) 拷贝步骤
INSERT INTO biz_process_route_step (
id, route_id, process_id, step_order, process_code, category_id,
process_name, description, equipment_ids, equipment_name, estimated_duration,
sop_ids, sop_content, tenant_no, order_num, remark,
create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT
m.dst_step_id,
m.dst_route_id,
ss.process_id,
ss.step_order,
ss.process_code,
ss.category_id,
ss.process_name,
ss.description,
ss.equipment_ids,
ss.equipment_name,
ss.estimated_duration,
ss.sop_ids,
ss.sop_content,
ss.tenant_no,
ss.order_num,
ss.remark,
ss.create_by,
ss.create_dept_id,
NOW(),
ss.update_by,
NOW(),
0
FROM tmp_step_id_map m
INNER JOIN biz_process_route_step ss ON ss.id = m.src_step_id;
-- 4) 试剂
INSERT INTO biz_process_route_step_reagent (
id, step_id, reagent_code, reagent_name, spec, standard_usage, unit,
is_key_material, tenant_no, order_num, remark,
create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT
CONCAT('BF', UPPER(MD5(CONCAT('fix-builtin-reag:', m.dst_step_id, ':', r.id)))),
m.dst_step_id,
r.reagent_code,
r.reagent_name,
r.spec,
r.standard_usage,
r.unit,
r.is_key_material,
r.tenant_no,
r.order_num,
r.remark,
r.create_by,
r.create_dept_id,
NOW(),
r.update_by,
NOW(),
0
FROM tmp_step_id_map m
INNER JOIN biz_process_route_step_reagent r
ON r.step_id = m.src_step_id
AND r.del_flag = 0;
-- 5) 耗材
INSERT INTO biz_process_route_step_consumable (
id, step_id, consumable_code, consumable_name, spec_model, standard_usage,
unit, is_required, remark, tenant_no, order_num,
create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT
CONCAT('BF', UPPER(MD5(CONCAT('fix-builtin-cons:', m.dst_step_id, ':', c.id)))),
m.dst_step_id,
c.consumable_code,
c.consumable_name,
c.spec_model,
c.standard_usage,
c.unit,
c.is_required,
c.remark,
c.tenant_no,
c.order_num,
c.create_by,
c.create_dept_id,
NOW(),
c.update_by,
NOW(),
0
FROM tmp_step_id_map m
INNER JOIN biz_process_route_step_consumable c
ON c.step_id = m.src_step_id
AND c.del_flag = 0;
-- 6) 产出
INSERT INTO biz_process_route_step_output (
id, step_id, output_type_id, output_name, output_barcode, tenant_no,
order_num, remark, create_by, create_dept_id, create_time, update_by,
update_time, del_flag
)
SELECT
CONCAT('BF', UPPER(MD5(CONCAT('fix-builtin-out:', m.dst_step_id, ':', o.id)))),
m.dst_step_id,
o.output_type_id,
o.output_name,
o.output_barcode,
o.tenant_no,
o.order_num,
o.remark,
o.create_by,
o.create_dept_id,
NOW(),
o.update_by,
NOW(),
0
FROM tmp_step_id_map m
INNER JOIN biz_process_route_step_output o
ON o.step_id = m.src_step_id
AND o.del_flag = 0;
-- 7) 数据采集
INSERT INTO biz_process_route_step_data_collection (
id, step_id, collection_name, unit, tenant_no, order_num, remark,
create_by, create_dept_id, create_time, update_by, update_time, del_flag
)
SELECT
CONCAT('BF', UPPER(MD5(CONCAT('fix-builtin-dc:', m.dst_step_id, ':', d.id)))),
m.dst_step_id,
d.collection_name,
d.unit,
d.tenant_no,
d.order_num,
d.remark,
d.create_by,
d.create_dept_id,
NOW(),
d.update_by,
NOW(),
0
FROM tmp_step_id_map m
INNER JOIN biz_process_route_step_data_collection d
ON d.step_id = m.src_step_id
AND d.del_flag = 0;
-- 8) 校验(人工看结果)
SELECT
b.route_code,
b.route_name,
(SELECT COUNT(*) FROM biz_process_route_step st
WHERE st.route_id = b.id AND st.del_flag = 0) AS step_count
FROM biz_process_route b
WHERE IFNULL(b.is_builtin, '0') = '1'
AND b.del_flag = 0
AND b.route_code LIKE '%-BUILTIN'
ORDER BY b.route_code;
DROP TEMPORARY TABLE IF EXISTS tmp_step_id_map;
DROP TEMPORARY TABLE IF EXISTS tmp_builtin_route_fix;
@@ -1,44 +0,0 @@
-- 交付/输注计划及产品明细增加产品包装字段(透传至生产/质检出库工单展示)
-- 可重复执行:已存在列则跳过
SET @db := DATABASE();
-- biz_pilot_infusion_schedule.package_id
SET @exists := (
SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'biz_pilot_infusion_schedule' AND COLUMN_NAME = 'package_id'
);
SET @sql := IF(@exists = 0,
'ALTER TABLE `biz_pilot_infusion_schedule` ADD COLUMN `package_id` VARCHAR(64) NULL COMMENT ''产品包装ID'' AFTER `spec_text`',
'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- biz_pilot_infusion_schedule.package_text
SET @exists := (
SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'biz_pilot_infusion_schedule' AND COLUMN_NAME = 'package_text'
);
SET @sql := IF(@exists = 0,
'ALTER TABLE `biz_pilot_infusion_schedule` ADD COLUMN `package_text` VARCHAR(200) NULL COMMENT ''产品包装名称(冗余展示)'' AFTER `package_id`',
'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- biz_pilot_infusion_schedule_product.package_id
SET @exists := (
SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'biz_pilot_infusion_schedule_product' AND COLUMN_NAME = 'package_id'
);
SET @sql := IF(@exists = 0,
'ALTER TABLE `biz_pilot_infusion_schedule_product` ADD COLUMN `package_id` VARCHAR(64) NULL COMMENT ''产品包装ID'' AFTER `spec_text`',
'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- biz_pilot_infusion_schedule_product.package_text
SET @exists := (
SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'biz_pilot_infusion_schedule_product' AND COLUMN_NAME = 'package_text'
);
SET @sql := IF(@exists = 0,
'ALTER TABLE `biz_pilot_infusion_schedule_product` ADD COLUMN `package_text` VARCHAR(200) NULL COMMENT ''产品包装名称(冗余展示)'' AFTER `package_id`',
'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
@@ -1,24 +0,0 @@
-- 样本编码全链路纠错审计日志(2026-10-22)
-- 对齐 docs/superpowers/specs/2026-10-22-sample-code-correction-design.md
CREATE TABLE IF NOT EXISTS biz_sample_code_correction_log (
id VARCHAR(64) NOT NULL COMMENT '主键ID',
reception_source VARCHAR(32) NOT NULL COMMENT '来源 cell_storage/research_donation',
old_sample_code VARCHAR(32) NOT NULL COMMENT '旧样本编码',
new_sample_code VARCHAR(32) NOT NULL COMMENT '新样本编码',
impact_json TEXT COMMENT '各表影响行数 JSON',
reason VARCHAR(512) DEFAULT NULL COMMENT '更正原因',
tenant_no VARCHAR(64) NOT NULL DEFAULT 'zkxw' COMMENT '租户编号',
order_num INT 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 COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT NULL COMMENT '更新人id',
update_time DATETIME NOT NULL COMMENT '更新时间',
del_flag SMALLINT NOT NULL DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (id),
KEY idx_scc_tenant_time (tenant_no, create_time),
KEY idx_scc_tenant_old (tenant_no, old_sample_code, del_flag),
KEY idx_scc_tenant_new (tenant_no, new_sample_code, del_flag)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='样本编码纠错审计日志';
@@ -1,7 +0,0 @@
-- OA「提到我的」未读:oa_instance_view_grant.is_read
-- 存量行置已读,避免上线后角标暴涨
ALTER TABLE `oa_instance_view_grant`
ADD COLUMN `is_read` SMALLINT NOT NULL DEFAULT 0 COMMENT '是否已读 0未读 1已读' AFTER `granted_by`;
UPDATE `oa_instance_view_grant` SET `is_read` = 1 WHERE `del_flag` = 0;
@@ -1,41 +0,0 @@
-- 样本采集通知人员角色(幂等)
-- 仅插入角色,不挂菜单、不自动绑用户;由运营在「角色管理」中挂人。
-- 对齐 docs/superpowers/specs/2026-10-23-sample-collect-wx-notify-design.md
INSERT INTO sys_role (
id,
role_name,
data_scope,
status,
tenant_no,
order_num,
remark,
create_by,
create_dept_id,
create_time,
update_by,
update_time,
del_flag
)
SELECT
REPLACE(UUID(), '-', ''),
'样本采集通知人员',
'1',
'1',
'zkxw',
0,
'协议新增样本时接收服务号模板消息',
'system',
'',
NOW(),
'system',
NOW(),
0
FROM DUAL
WHERE NOT EXISTS (
SELECT 1
FROM sys_role r
WHERE r.tenant_no = 'zkxw'
AND r.role_name = '样本采集通知人员'
AND r.del_flag = 0
);
@@ -0,0 +1,20 @@
-- =============================================================================
-- 样本接收检查项改名:针头无暴露 → 针路密闭
-- 配置表与结果快照一并更新,接收表单和已保存的接收详情显示同一名称。
-- 可重复执行:名称已改过后 WHERE 不再命中。
-- =============================================================================
UPDATE biz_sample_reception_check_item
SET item_code = '针路密闭',
item_name = '针路密闭',
update_time = NOW()
WHERE del_flag = 0
AND item_code = '针头无暴露'
AND item_name = '针头无暴露';
UPDATE biz_sample_reception_check_result
SET item_code = '针路密闭',
item_name = '针路密闭',
update_time = NOW()
WHERE item_code = '针头无暴露'
OR item_name = '针头无暴露';
@@ -0,0 +1,184 @@
-- 【生产环境脚本】
-- 依据生产备份 zkxw_2026-10-28_094047.sql.gz(2026-10-28 09:40)核对后编写。
-- 不要在测试库执行。测试库已用同目录下文件名带【测试数据库脚本】的文件处理过。
-- 生产库请执行本文件。2026-10-27 18:04 那份脚本的待修复集合与本备份一致,但请以本文件为准。
--
-- 背景:保存步骤产出物、协议样本、捐赠样本时曾经整表软删再插入,
-- 下游仍指向 del_flag=1 的旧行。代码尚未发到生产,本备份仍是修复前的数据。
-- 策略:只在同业务键下恰好有一条有效行时改指。找不到或有多条时不动。
-- 幂等:已指向有效行的记录不会进入映射;重复执行影响行数为 0。
-- MySQL 5.7。跨表字符串比较统一 utf8mb4_general_ci,避免 1267。
-- 本脚本未在任何库执行。
--
-- 相对该备份的预期影响:
-- 质检工单 production_step_output_id 11
-- 生产工单 protocol_sample_id -> 协议样本 16
-- 生产工单 protocol_sample_id -> 捐赠样本 70
-- 质检短信 protocol_sample_id 15
-- 按样本缴费方案 0
-- 缴费方案有 3 条仍指向已软删协议样本,但同租户同编码下没有有效样本,本脚本不改:
-- ZKZX34000856、ZKZX34000175、ZKZX34000199
-- 样本接收、质检工单上的样本 id 在该备份中已指向有效行,本脚本不包含这两处。
--
-- 与 2026-10-27 18:04 备份相比,上述 11 / 16 / 70 / 15 条映射没有变化。
-- 过夜新增、本脚本不会改到的行:
-- 废液 NK-UBMC202603005:原有效产出物 F2755086E1AE49C1B10FA6E4624D5AE7 已被软删,
-- 当前有效行是 3513B95B3FDC400C9744277B8919DAB4。没有质检工单指向被删的那一行。
-- 新捐赠样本 KYJZ2026100055、KYJZ2026100056、KYJZ2026100057。
-- KYJZ2026100055 另有一条已软删样本,没有生产工单指向它。
-- ---------------------------------------------------------------------------
-- 1. 质检工单 -> 步骤产出物
-- 同一步骤、同租户,名称、条码、产出类型一致,且只有一条有效产出物
-- ---------------------------------------------------------------------------
DROP TEMPORARY TABLE IF EXISTS tmp_qc_output_relink;
CREATE TEMPORARY TABLE tmp_qc_output_relink (
qc_id VARCHAR(64) NOT NULL COLLATE utf8mb4_general_ci,
new_output_id VARCHAR(64) NOT NULL COLLATE utf8mb4_general_ci,
PRIMARY KEY (qc_id)
) ENGINE=MEMORY;
INSERT INTO tmp_qc_output_relink (qc_id, new_output_id)
SELECT q.id, MIN(live.id)
FROM biz_qc_work_order q
JOIN biz_production_task_step_output old
ON old.id = q.production_step_output_id
AND old.del_flag = 1
JOIN biz_production_task_step_output live
ON live.step_id = old.step_id
AND live.tenant_no = old.tenant_no
AND live.del_flag = 0
AND live.id <> old.id
AND IFNULL(live.output_name, '') = IFNULL(old.output_name, '')
AND IFNULL(live.output_barcode, '') = IFNULL(old.output_barcode, '')
AND IFNULL(live.output_type_id, '') = IFNULL(old.output_type_id, '')
WHERE q.del_flag = 0
AND IFNULL(q.production_step_output_id, '') <> ''
GROUP BY q.id
HAVING COUNT(*) = 1;
UPDATE biz_qc_work_order q
JOIN tmp_qc_output_relink m ON m.qc_id = q.id
SET q.production_step_output_id = m.new_output_id,
q.update_time = NOW()
WHERE q.del_flag = 0
AND q.production_step_output_id <> m.new_output_id;
DROP TEMPORARY TABLE IF EXISTS tmp_qc_output_relink;
-- ---------------------------------------------------------------------------
-- 2. 生产工单 / 缴费方案 / 质检短信 -> 协议样本或捐赠样本
-- 同租户、同编码恰好一条有效样本
-- ---------------------------------------------------------------------------
DROP TEMPORARY TABLE IF EXISTS tmp_sample_ref_relink;
CREATE TEMPORARY TABLE tmp_sample_ref_relink (
row_id VARCHAR(64) NOT NULL COLLATE utf8mb4_general_ci,
new_sample_id VARCHAR(64) NOT NULL COLLATE utf8mb4_general_ci,
PRIMARY KEY (row_id)
) ENGINE=MEMORY;
-- 生产工单 -> 采集协议样本
INSERT INTO tmp_sample_ref_relink (row_id, new_sample_id)
SELECT w.id, MIN(live.id)
FROM biz_production_work_order w
JOIN biz_protocol_sample old
ON old.id COLLATE utf8mb4_general_ci = w.protocol_sample_id COLLATE utf8mb4_general_ci
AND old.del_flag = 1
JOIN biz_protocol_sample live
ON live.del_flag = 0
AND live.tenant_no COLLATE utf8mb4_general_ci = old.tenant_no COLLATE utf8mb4_general_ci
AND IFNULL(live.sample_code, '') COLLATE utf8mb4_general_ci = IFNULL(old.sample_code, '') COLLATE utf8mb4_general_ci
AND IFNULL(live.sample_code, '') <> ''
AND live.id COLLATE utf8mb4_general_ci <> old.id COLLATE utf8mb4_general_ci
WHERE w.del_flag = 0
GROUP BY w.id
HAVING COUNT(*) = 1;
UPDATE biz_production_work_order w
JOIN tmp_sample_ref_relink m
ON m.row_id COLLATE utf8mb4_general_ci = w.id COLLATE utf8mb4_general_ci
SET w.protocol_sample_id = m.new_sample_id,
w.update_time = NOW()
WHERE w.del_flag = 0;
DELETE FROM tmp_sample_ref_relink;
-- 生产工单 -> 科研捐赠样本(工单的 protocol_sample_id 存捐赠样本 ID)
INSERT INTO tmp_sample_ref_relink (row_id, new_sample_id)
SELECT w.id, MIN(live.id)
FROM biz_production_work_order w
JOIN biz_research_donation_sample old
ON old.id COLLATE utf8mb4_general_ci = w.protocol_sample_id COLLATE utf8mb4_general_ci
AND old.del_flag = 1
JOIN biz_research_donation_sample live
ON live.del_flag = 0
AND live.tenant_no COLLATE utf8mb4_general_ci = old.tenant_no COLLATE utf8mb4_general_ci
AND IFNULL(live.sample_code, '') COLLATE utf8mb4_general_ci = IFNULL(old.sample_code, '') COLLATE utf8mb4_general_ci
AND IFNULL(live.sample_code, '') <> ''
AND live.id COLLATE utf8mb4_general_ci <> old.id COLLATE utf8mb4_general_ci
WHERE w.del_flag = 0
GROUP BY w.id
HAVING COUNT(*) = 1;
UPDATE biz_production_work_order w
JOIN tmp_sample_ref_relink m
ON m.row_id COLLATE utf8mb4_general_ci = w.id COLLATE utf8mb4_general_ci
SET w.protocol_sample_id = m.new_sample_id,
w.update_time = NOW()
WHERE w.del_flag = 0;
DELETE FROM tmp_sample_ref_relink;
-- 按样本缴费方案。该备份中可唯一匹配的行数为 0,下面语句不会改数据。
INSERT INTO tmp_sample_ref_relink (row_id, new_sample_id)
SELECT p.id, MIN(live.id)
FROM biz_protocol_sample_payment p
JOIN biz_protocol_sample old
ON old.id COLLATE utf8mb4_general_ci = p.protocol_sample_id COLLATE utf8mb4_general_ci
AND old.del_flag = 1
JOIN biz_protocol_sample live
ON live.del_flag = 0
AND live.tenant_no COLLATE utf8mb4_general_ci = old.tenant_no COLLATE utf8mb4_general_ci
AND IFNULL(live.sample_code, '') COLLATE utf8mb4_general_ci = IFNULL(old.sample_code, '') COLLATE utf8mb4_general_ci
AND IFNULL(live.sample_code, '') <> ''
AND live.id COLLATE utf8mb4_general_ci <> old.id COLLATE utf8mb4_general_ci
WHERE p.del_flag = 0
GROUP BY p.id
HAVING COUNT(*) = 1;
UPDATE biz_protocol_sample_payment p
JOIN tmp_sample_ref_relink m
ON m.row_id COLLATE utf8mb4_general_ci = p.id COLLATE utf8mb4_general_ci
SET p.protocol_sample_id = m.new_sample_id,
p.update_time = NOW()
WHERE p.del_flag = 0;
DELETE FROM tmp_sample_ref_relink;
-- 质检短信。del_flag 是字符 '0'
INSERT INTO tmp_sample_ref_relink (row_id, new_sample_id)
SELECT s.id, MIN(live.id)
FROM biz_qc_sms_notification s
JOIN biz_protocol_sample old
ON old.id COLLATE utf8mb4_general_ci = s.protocol_sample_id COLLATE utf8mb4_general_ci
AND old.del_flag = 1
JOIN biz_protocol_sample live
ON live.del_flag = 0
AND live.tenant_no COLLATE utf8mb4_general_ci = old.tenant_no COLLATE utf8mb4_general_ci
AND IFNULL(live.sample_code, '') COLLATE utf8mb4_general_ci = IFNULL(old.sample_code, '') COLLATE utf8mb4_general_ci
AND IFNULL(live.sample_code, '') <> ''
AND live.id COLLATE utf8mb4_general_ci <> old.id COLLATE utf8mb4_general_ci
WHERE s.del_flag = '0'
GROUP BY s.id
HAVING COUNT(*) = 1;
UPDATE biz_qc_sms_notification s
JOIN tmp_sample_ref_relink m
ON m.row_id COLLATE utf8mb4_general_ci = s.id COLLATE utf8mb4_general_ci
SET s.protocol_sample_id = m.new_sample_id,
s.update_time = NOW()
WHERE s.del_flag = '0';
DROP TEMPORARY TABLE IF EXISTS tmp_sample_ref_relink;