-- Petstore Phase 0: create stable store-customer relationship master. -- Target: MySQL. Run after 20260801_split_service_identity.sql. -- One-time migration; back up the database before execution. CREATE TABLE t_store_customer ( id BIGINT NOT NULL AUTO_INCREMENT, store_id BIGINT NOT NULL COMMENT '门店 ID', customer_user_id BIGINT NULL COMMENT '全局 customer 账号,留资客户可空', phone VARCHAR(20) NULL COMMENT '本店最后确认的联系手机号', display_name VARCHAR(64) NULL COMMENT '门店视角客户称呼', source VARCHAR(32) NOT NULL COMMENT '首次来源', first_contact_at DATETIME NULL COMMENT '首次接触时间', last_contact_at DATETIME NULL COMMENT '最近接触时间', create_time DATETIME NOT NULL, update_time DATETIME NOT NULL, deleted TINYINT(1) NOT NULL DEFAULT 0, PRIMARY KEY (id), UNIQUE KEY uk_store_customer_user (store_id, customer_user_id), UNIQUE KEY uk_store_customer_phone (store_id, phone), KEY idx_store_customer_active_update (store_id, deleted, update_time), KEY idx_store_customer_last_contact (store_id, last_contact_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='门店客户关系主档'; -- 历史预约客户建档。旧数据无法精确确定首次是自助或代客,统一记为 appointment。 INSERT INTO t_store_customer ( store_id, customer_user_id, phone, display_name, source, first_contact_at, last_contact_at, create_time, update_time, deleted ) SELECT a.store_id, a.customer_user_id, MAX(u.phone), MAX(COALESCE(NULLIF(u.name, ''), NULLIF(u.username, ''), '客户')), 'appointment', MIN(COALESCE(a.create_time, a.appointment_time, NOW())), MAX(COALESCE(a.update_time, a.create_time, a.appointment_time, NOW())), NOW(), NOW(), 0 FROM t_appointment a JOIN t_user u ON u.id = a.customer_user_id AND u.deleted = 0 AND u.role = 'customer' WHERE a.deleted = 0 AND a.store_id IS NOT NULL AND a.customer_user_id IS NOT NULL GROUP BY a.store_id, a.customer_user_id; -- 报告留资建档。若同店同手机已由预约建档,只更新最近接触时间并保留首次来源。 INSERT INTO t_store_customer ( store_id, customer_user_id, phone, display_name, source, first_contact_at, last_contact_at, create_time, update_time, deleted ) SELECT l.store_id, MAX(CASE WHEN u.role = 'customer' AND u.deleted = 0 THEN u.id ELSE NULL END), l.phone, MAX(CASE WHEN u.role = 'customer' AND u.deleted = 0 THEN COALESCE(NULLIF(u.name, ''), NULLIF(u.username, ''), '留资客户') ELSE '留资客户' END), 'report_lead', MIN(COALESCE(l.create_time, NOW())), MAX(COALESCE(l.update_time, l.create_time, NOW())), NOW(), NOW(), 0 FROM t_report_lead l LEFT JOIN t_user u ON u.phone = l.phone WHERE l.store_id IS NOT NULL AND l.phone IS NOT NULL AND l.phone <> '' GROUP BY l.store_id, l.phone ON DUPLICATE KEY UPDATE customer_user_id = COALESCE(t_store_customer.customer_user_id, VALUES(customer_user_id)), phone = COALESCE(t_store_customer.phone, VALUES(phone)), display_name = CASE WHEN t_store_customer.display_name IS NULL OR t_store_customer.display_name = '' THEN VALUES(display_name) ELSE t_store_customer.display_name END, first_contact_at = LEAST( COALESCE(t_store_customer.first_contact_at, VALUES(first_contact_at)), VALUES(first_contact_at) ), last_contact_at = GREATEST( COALESCE(t_store_customer.last_contact_at, VALUES(last_contact_at)), VALUES(last_contact_at) ), update_time = NOW(), deleted = 0; -- Verification queries. All three counts should be 0 before application release. SELECT COUNT(*) AS appointments_without_store_customer FROM t_appointment a LEFT JOIN t_store_customer sc ON sc.store_id = a.store_id AND sc.customer_user_id = a.customer_user_id AND sc.deleted = 0 WHERE a.deleted = 0 AND a.customer_user_id IS NOT NULL AND sc.id IS NULL; SELECT COUNT(*) AS leads_without_store_customer FROM t_report_lead l LEFT JOIN t_store_customer sc ON sc.store_id = l.store_id AND sc.phone = l.phone AND sc.deleted = 0 WHERE l.store_id IS NOT NULL AND l.phone IS NOT NULL AND l.phone <> '' AND sc.id IS NULL; SELECT COUNT(*) AS store_customers_linked_to_non_customer FROM t_store_customer sc JOIN t_user u ON u.id = sc.customer_user_id WHERE sc.deleted = 0 AND (u.deleted <> 0 OR u.role <> 'customer');