✨ 完善客户业务员关联和同步
This commit is contained in:
@@ -1,10 +1,4 @@
|
||||
-- 客户归属销售员由"工号"改存本系统用户ID(数据权限按 user_id 控制,工号后续修改不影响归属)
|
||||
-- 同步链路已在 CustomerSyncService.resolveSalesUserId 做同样转换,本脚本只处理存量数据。
|
||||
-- 仅转换"含非数字字符"的工号值(工号形如 WS0052/WSC0184/wsadmin);
|
||||
-- 已是数字(user_id)的行不触碰,脚本可重复执行。
|
||||
UPDATE ctr_customer c
|
||||
JOIN sys_user u ON u.del_flag = '0' AND u.user_name = c.sales_user_id
|
||||
SET c.sales_user_id = CONVERT(u.user_id, CHAR)
|
||||
WHERE c.del_flag = '0'
|
||||
AND c.sales_user_id IS NOT NULL
|
||||
AND c.sales_user_id REGEXP '[^0-9]';
|
||||
-- DEPRECATED: 不要执行本文件。
|
||||
-- 旧版脚本仅按非数字判断工号并直接 UPDATE,无法安全处理重复工号和数字工号/user_id 冲突。
|
||||
-- 请先执行 customer+supplier/20261028_customer_sales_user_classification.sql 查看清单,
|
||||
-- 再按上线顺序执行 customer+supplier/20261028_customer_sales_user_migration.sql。
|
||||
|
||||
@@ -356,7 +356,8 @@ CREATE TABLE `ctr_customer` (
|
||||
`credit_code` varchar(18) DEFAULT '' COMMENT '统一社会信用代码',
|
||||
`customer_type` char(1) DEFAULT '0' COMMENT '业务类型(0直客 1同行 2集运)',
|
||||
`is_enable` char(1) DEFAULT '0' COMMENT '启用状态(0正常 1停用)',
|
||||
`sales_user_id` varchar(64) DEFAULT '' COMMENT '业务员用户ID',
|
||||
`sales_user_id` varchar(64) DEFAULT NULL COMMENT '业务员系统用户ID(sys_user.user_id)',
|
||||
`source_sales_code` varchar(255) DEFAULT NULL COMMENT '客户同步源业务员工号(内部字段)',
|
||||
`sales_user` varchar(50) DEFAULT '' COMMENT '业务员姓名',
|
||||
`ed` decimal(15,2) DEFAULT '0.00' COMMENT '资金额度(元)',
|
||||
`zq` int(11) DEFAULT '0' COMMENT '回款账期(天)',
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
-- MySQL 5.7
|
||||
-- 客户业务员存量迁移前分类清单(只读,不修改数据)。
|
||||
-- 请先执行本文件,检查 ID/工号冲突,再执行 20261028_customer_sales_user_migration.sql。
|
||||
-- 分类依据:精确匹配 sys_user.user_id;或按区分大小写的 sys_user.user_name 唯一匹配。
|
||||
|
||||
-- 分类汇总:VALID_USER_ID 保留;UNIQUE_USERNAME 可安全转换;其余分类写入冲突/未匹配清单。
|
||||
SELECT classified.classification, COUNT(*) AS customer_count
|
||||
FROM (
|
||||
SELECT c.customer_id,
|
||||
CASE
|
||||
WHEN c.sales_user_id IS NULL OR TRIM(c.sales_user_id) = '' THEN 'EMPTY'
|
||||
WHEN id_match.match_count > 0 AND name_match.match_count > 0 THEN 'ID_USERNAME_COLLISION'
|
||||
WHEN id_match.match_count > 0 THEN 'VALID_USER_ID'
|
||||
WHEN name_match.match_count = 1 THEN 'UNIQUE_USERNAME'
|
||||
WHEN name_match.match_count > 1 THEN 'AMBIGUOUS_USERNAME'
|
||||
ELSE 'NO_MATCH'
|
||||
END AS classification
|
||||
FROM ctr_customer c
|
||||
LEFT JOIN (
|
||||
SELECT CAST(user_id AS CHAR) AS lookup_value, COUNT(*) AS match_count
|
||||
FROM sys_user
|
||||
WHERE del_flag = '0'
|
||||
GROUP BY CAST(user_id AS CHAR)
|
||||
) id_match ON BINARY id_match.lookup_value = BINARY TRIM(c.sales_user_id)
|
||||
LEFT JOIN (
|
||||
SELECT BINARY TRIM(user_name) AS lookup_value, COUNT(*) AS match_count
|
||||
FROM sys_user
|
||||
WHERE del_flag = '0'
|
||||
AND user_name IS NOT NULL
|
||||
AND user_name != ''
|
||||
GROUP BY BINARY TRIM(user_name)
|
||||
) name_match ON name_match.lookup_value = BINARY TRIM(c.sales_user_id)
|
||||
WHERE c.del_flag = '0'
|
||||
) classified
|
||||
GROUP BY classified.classification
|
||||
ORDER BY classified.classification;
|
||||
|
||||
-- 逐条分类。冲突项会列出原值、user_id 候选和 user_name 候选,供管理员人工核实。
|
||||
SELECT c.customer_id,
|
||||
c.customer_code,
|
||||
c.customer_name,
|
||||
c.sales_user_id AS original_sales_user_id,
|
||||
CASE
|
||||
WHEN c.sales_user_id IS NULL OR TRIM(c.sales_user_id) = '' THEN 'EMPTY'
|
||||
WHEN id_match.match_count > 0 AND name_match.match_count > 0 THEN 'ID_USERNAME_COLLISION'
|
||||
WHEN id_match.match_count > 0 THEN 'VALID_USER_ID'
|
||||
WHEN name_match.match_count = 1 THEN 'UNIQUE_USERNAME'
|
||||
WHEN name_match.match_count > 1 THEN 'AMBIGUOUS_USERNAME'
|
||||
ELSE 'NO_MATCH'
|
||||
END AS classification,
|
||||
COALESCE(id_match.match_count, 0) AS user_id_match_count,
|
||||
COALESCE(name_match.match_count, 0) AS user_name_match_count,
|
||||
id_match.candidate_user_ids AS user_id_candidates,
|
||||
name_match.candidate_user_ids AS user_name_candidates
|
||||
FROM ctr_customer c
|
||||
LEFT JOIN (
|
||||
SELECT CAST(user_id AS CHAR) AS lookup_value,
|
||||
COUNT(*) AS match_count,
|
||||
GROUP_CONCAT(CAST(user_id AS CHAR) ORDER BY user_id SEPARATOR ',') AS candidate_user_ids
|
||||
FROM sys_user
|
||||
WHERE del_flag = '0'
|
||||
GROUP BY CAST(user_id AS CHAR)
|
||||
) id_match ON BINARY id_match.lookup_value = BINARY TRIM(c.sales_user_id)
|
||||
LEFT JOIN (
|
||||
SELECT BINARY TRIM(user_name) AS lookup_value,
|
||||
COUNT(*) AS match_count,
|
||||
GROUP_CONCAT(CAST(user_id AS CHAR) ORDER BY user_id SEPARATOR ',') AS candidate_user_ids
|
||||
FROM sys_user
|
||||
WHERE del_flag = '0'
|
||||
AND user_name IS NOT NULL
|
||||
AND user_name != ''
|
||||
GROUP BY BINARY TRIM(user_name)
|
||||
) name_match ON name_match.lookup_value = BINARY TRIM(c.sales_user_id)
|
||||
WHERE c.del_flag = '0'
|
||||
ORDER BY classification, c.customer_id;
|
||||
@@ -0,0 +1,142 @@
|
||||
-- MySQL 5.7
|
||||
-- 客户业务员工号 -> 系统用户ID 存量迁移。
|
||||
-- 上线前先执行 20261028_customer_sales_user_classification.sql 并核对 ID_USERNAME_COLLISION。
|
||||
-- 自动转换仅处理唯一 user_name 匹配;未匹配、重复匹配和 ID/工号冲突会备份原值、清空 sales_user_id,留给管理员在客户编辑页处理。
|
||||
-- 本脚本不连接数据库执行;可重复运行。完成标记写入后,重复运行不会再次处理存量或改写备份。
|
||||
|
||||
-- 新增内部源工号列(兼容 MySQL 5.7,不依赖 ADD COLUMN IF NOT EXISTS)。
|
||||
SET @add_source_sales_code_sql = (
|
||||
SELECT IF(COUNT(*) = 0,
|
||||
'ALTER TABLE ctr_customer ADD COLUMN source_sales_code varchar(255) DEFAULT NULL COMMENT ''客户同步源业务员工号(内部字段)'' AFTER sales_user_id',
|
||||
'SELECT 1')
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'ctr_customer'
|
||||
AND column_name = 'source_sales_code'
|
||||
);
|
||||
PREPARE add_source_sales_code_stmt FROM @add_source_sales_code_sql;
|
||||
EXECUTE add_source_sales_code_stmt;
|
||||
DEALLOCATE PREPARE add_source_sales_code_stmt;
|
||||
|
||||
-- 未匹配时使用 NULL,避免空字符串伪装成用户ID。
|
||||
ALTER TABLE ctr_customer
|
||||
MODIFY COLUMN sales_user_id varchar(64) DEFAULT NULL COMMENT '业务员系统用户ID(sys_user.user_id)';
|
||||
|
||||
-- 备份表同时保留分类结果和候选ID;PRIMARY KEY + INSERT IGNORE 保证原始值不会被后续重跑覆盖。
|
||||
CREATE TABLE IF NOT EXISTS ctr_customer_sales_user_migration_backup (
|
||||
customer_id bigint(20) NOT NULL COMMENT '客户ID',
|
||||
customer_code varchar(50) NOT NULL COMMENT '客户编码',
|
||||
original_sales_user_id varchar(64) DEFAULT NULL COMMENT '迁移前 sales_user_id 原值',
|
||||
classification varchar(40) NOT NULL COMMENT '迁移前分类',
|
||||
user_id_match_count int NOT NULL DEFAULT 0 COMMENT '精确 user_id 匹配数',
|
||||
user_name_match_count int NOT NULL DEFAULT 0 COMMENT '精确 user_name 匹配数',
|
||||
user_id_candidates text COMMENT 'user_id 匹配候选',
|
||||
user_name_candidates text COMMENT 'user_name 匹配候选',
|
||||
backup_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '备份时间',
|
||||
PRIMARY KEY (customer_id),
|
||||
KEY idx_customer_sales_user_backup_classification (classification)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户业务员ID迁移原值及分类备份';
|
||||
|
||||
-- 防止成功执行后重跑时把后来新建/人工维护的客户重新当作存量数据处理。
|
||||
CREATE TABLE IF NOT EXISTS ctr_customer_sales_user_migration_control (
|
||||
migration_key varchar(64) NOT NULL,
|
||||
status varchar(16) NOT NULL DEFAULT 'PENDING',
|
||||
completed_at datetime DEFAULT NULL,
|
||||
PRIMARY KEY (migration_key)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户业务员ID迁移执行状态';
|
||||
|
||||
INSERT IGNORE INTO ctr_customer_sales_user_migration_control (migration_key, status)
|
||||
VALUES ('20261028_customer_sales_user', 'PENDING');
|
||||
|
||||
START TRANSACTION;
|
||||
|
||||
-- 首次运行时先保留旧值和候选;后续事务失败可安全重跑,成功后由 control 表阻止再次处理。
|
||||
INSERT IGNORE INTO ctr_customer_sales_user_migration_backup (
|
||||
customer_id, customer_code, original_sales_user_id, classification,
|
||||
user_id_match_count, user_name_match_count, user_id_candidates, user_name_candidates
|
||||
)
|
||||
SELECT c.customer_id,
|
||||
c.customer_code,
|
||||
c.sales_user_id,
|
||||
CASE
|
||||
WHEN c.sales_user_id IS NULL OR TRIM(c.sales_user_id) = '' THEN 'EMPTY'
|
||||
WHEN id_match.match_count > 0 AND name_match.match_count > 0 THEN 'ID_USERNAME_COLLISION'
|
||||
WHEN id_match.match_count > 0 THEN 'VALID_USER_ID'
|
||||
WHEN name_match.match_count = 1 THEN 'UNIQUE_USERNAME'
|
||||
WHEN name_match.match_count > 1 THEN 'AMBIGUOUS_USERNAME'
|
||||
ELSE 'NO_MATCH'
|
||||
END,
|
||||
COALESCE(id_match.match_count, 0),
|
||||
COALESCE(name_match.match_count, 0),
|
||||
id_match.candidate_user_ids,
|
||||
name_match.candidate_user_ids
|
||||
FROM ctr_customer c
|
||||
JOIN ctr_customer_sales_user_migration_control migration
|
||||
ON migration.migration_key = '20261028_customer_sales_user'
|
||||
AND migration.status = 'PENDING'
|
||||
LEFT JOIN (
|
||||
SELECT CAST(user_id AS CHAR) AS lookup_value,
|
||||
COUNT(*) AS match_count,
|
||||
GROUP_CONCAT(CAST(user_id AS CHAR) ORDER BY user_id SEPARATOR ',') AS candidate_user_ids
|
||||
FROM sys_user
|
||||
WHERE del_flag = '0'
|
||||
GROUP BY CAST(user_id AS CHAR)
|
||||
) id_match ON BINARY id_match.lookup_value = BINARY TRIM(c.sales_user_id)
|
||||
LEFT JOIN (
|
||||
SELECT BINARY TRIM(user_name) AS lookup_value,
|
||||
COUNT(*) AS match_count,
|
||||
GROUP_CONCAT(CAST(user_id AS CHAR) ORDER BY user_id SEPARATOR ',') AS candidate_user_ids
|
||||
FROM sys_user
|
||||
WHERE del_flag = '0'
|
||||
AND user_name IS NOT NULL
|
||||
AND user_name != ''
|
||||
GROUP BY BINARY TRIM(user_name)
|
||||
) name_match ON name_match.lookup_value = BINARY TRIM(c.sales_user_id)
|
||||
WHERE c.del_flag = '0'
|
||||
AND c.source_sales_code IS NULL;
|
||||
|
||||
-- 唯一工号映射到系统用户ID;不唯一、不匹配或存在ID/工号冲突的项不猜测,清空关联并保留源值。
|
||||
UPDATE ctr_customer c
|
||||
JOIN ctr_customer_sales_user_migration_backup b
|
||||
ON b.customer_id = c.customer_id
|
||||
JOIN ctr_customer_sales_user_migration_control migration
|
||||
ON migration.migration_key = '20261028_customer_sales_user'
|
||||
AND migration.status = 'PENDING'
|
||||
SET c.source_sales_code = b.original_sales_user_id,
|
||||
c.sales_user_id = CASE
|
||||
WHEN b.classification = 'UNIQUE_USERNAME'
|
||||
THEN b.user_name_candidates
|
||||
ELSE NULL
|
||||
END
|
||||
WHERE c.del_flag = '0'
|
||||
AND c.source_sales_code IS NULL
|
||||
AND b.classification IN (
|
||||
'UNIQUE_USERNAME',
|
||||
'AMBIGUOUS_USERNAME',
|
||||
'NO_MATCH',
|
||||
'ID_USERNAME_COLLISION'
|
||||
);
|
||||
|
||||
-- 将旧空字符串归一为 NULL,已确认是系统用户ID的行保持不变。
|
||||
UPDATE ctr_customer c
|
||||
JOIN ctr_customer_sales_user_migration_control migration
|
||||
ON migration.migration_key = '20261028_customer_sales_user'
|
||||
AND migration.status = 'PENDING'
|
||||
SET c.sales_user_id = NULL
|
||||
WHERE c.del_flag = '0'
|
||||
AND c.sales_user_id IS NOT NULL
|
||||
AND TRIM(c.sales_user_id) = '';
|
||||
|
||||
UPDATE ctr_customer_sales_user_migration_control
|
||||
SET status = 'DONE', completed_at = NOW()
|
||||
WHERE migration_key = '20261028_customer_sales_user'
|
||||
AND status = 'PENDING';
|
||||
|
||||
COMMIT;
|
||||
|
||||
-- 执行后核对人工处理清单;原值和候选均保存在备份表中。
|
||||
SELECT customer_id, customer_code, original_sales_user_id, classification,
|
||||
user_id_candidates, user_name_candidates
|
||||
FROM ctr_customer_sales_user_migration_backup
|
||||
WHERE classification IN ('ID_USERNAME_COLLISION', 'AMBIGUOUS_USERNAME', 'NO_MATCH')
|
||||
ORDER BY classification, customer_id;
|
||||
Reference in New Issue
Block a user