-- ============================================================================
-- S5-IQC-B1 · 原材料检规 ↔ 物料 派生映射桥表
--
-- 目标：为 S5 IQC 建立 qms_jygf → ItemMaster 的稳定映射基础设施。
--       本批 **不改变任何现有业务写路径行为**：不动 qms_qcp_inspbill、
--       不动 GenerateInspBill、不动 qms_jygf / qms_jygfzb、不动报检数据。
--
-- ⚠️ 数据回填范围：**仅 UAT-A 租户（SysOrg.Code = 'UATTEST_CHL'）**
-- ⚠️ 明确不回填 797403760988229 的 4704 条历史检规，不治理其他租户。
-- ⚠️ 表结构本身是多租户通用的；只有下方 ③ 的 seed 是 tenant scoped。
--
-- 写入范围：CREATE TABLE 1 · INSERT(仅 UAT-A) · UPDATE 0 · DELETE 0 · 其他 DDL 0
--
-- ---------------------------------------------------------------------------
-- 【分词等价性保证 —— 本脚本最重要的约束】
--
-- 运行期分词口径唯一定义在 C# S5IqcMaterialTokenizer：
--     分隔符 = ; ； , ， 、 : ： / 空格 Tab CR LF        （**不含 . - _**，它们出现在合法物料码内部）
--     标准化 = TRIM + UPPER
--     分级   = EXACT(命中 ItemMaster) / NOT_FOUND / GLUE_UNRESOLVED
--
-- SQL 无法可靠复刻上述正则。为杜绝「迁移一套规则、运行期另一套规则」，本脚本
-- **只为不含任何分隔符的单 token 检规生成映射**（见 ③ 的 NOT LIKE 守卫）——
-- 此时分词退化为恒等变换 TRIM(UPPER(wlbm))，SQL 与 C# 结果**可证明相同**。
-- 含分隔符的检规一律不由本脚本落地，必须调用 POST /api/S5IqcSpecMap/rebuild 生成。
--
-- 实测（2026-09-02）UAT-A 8 条检规的 wlbm 全部无分隔符 → 本脚本覆盖 8/8，无遗漏。
-- 该守卫同时保证：即使将来 UAT-A 新增了多物料检规，脚本也只会「少做」不会「做错」。
-- ---------------------------------------------------------------------------
--
-- 幂等：CREATE TABLE IF NOT EXISTS + INSERT ... WHERE NOT EXISTS(唯一业务键)。
--       重复执行结果恒等，不产生重复行。
-- ============================================================================

-- ===========================================================================
-- ① 桥表
--    唯一约束 uk_s5_iqc_map(tenant_id, spec_id, raw_token, is_manual)：
--      raw_token 决定 material_code，故该组合即「spec_id + material_code 唯一」的
--      等价防重复机制，且在 material_code 为 NULL(NOT_FOUND/GLUE) 时依然有效
--      （MySQL 唯一索引允许多个 NULL，若用 material_code 做键将无法防重）。
-- ===========================================================================
CREATE TABLE IF NOT EXISTS ado_s5_iqc_spec_material_map (
    id             BIGINT       NOT NULL COMMENT '主键(雪花)',
    tenant_id      BIGINT       NOT NULL COMMENT '租户ID',
    spec_id        BIGINT       NOT NULL COMMENT '原材料检规ID → qms_jygf.id',
    spec_no        VARCHAR(100) NULL     COMMENT '检规文件编号 wjbh(冗余，随主表同步刷新)',
    spec_version   VARCHAR(50)  NULL     COMMENT '检规版本 bb(冗余，随主表同步刷新)',
    seq            INT          NOT NULL DEFAULT 0 COMMENT 'token 在 wlbm 中的序号；人工绑定行为 0',
    raw_token      VARCHAR(200) NOT NULL COMMENT 'wlbm 切分出的原始片段(保留原貌供审计)',
    material_code  VARCHAR(100) NULL     COMMENT '标准化后的物料码；仅 EXACT/MANUAL 落值，其余 NULL(绝不猜物料)',
    match_status   VARCHAR(20)  NOT NULL COMMENT 'EXACT / MANUAL / NOT_FOUND / GLUE_UNRESOLVED',
    source_raw     TEXT         NULL     COMMENT '检规 wlbm 原始全串(审计用)',
    is_manual      TINYINT      NOT NULL DEFAULT 0 COMMENT '1=人工绑定，自动同步永不删除',
    note           VARCHAR(500) NULL     COMMENT '人工绑定备注',
    create_time    DATETIME     NULL,
    update_time    DATETIME     NULL,
    create_user_id BIGINT       NULL,
    PRIMARY KEY (id),
    UNIQUE KEY uk_s5_iqc_map (tenant_id, spec_id, raw_token, is_manual),
    KEY idx_s5_iqc_map_item (tenant_id, material_code),
    KEY idx_s5_iqc_map_spec (tenant_id, spec_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
  COMMENT='S5 IQC 原材料检规↔物料 派生映射(B-1)';

-- ===========================================================================
-- ② 目标租户解析（UAT-A）。解析不到则下方 INSERT 全部为空，脚本安全空转。
-- ===========================================================================
SET @uat_a := (SELECT t.Id FROM SysTenant t JOIN SysOrg o ON o.Id = t.OrgId
               WHERE o.Code = 'UATTEST_CHL' AND t.Status = 1 LIMIT 1);

-- ===========================================================================
-- ③ UAT-A 回填（仅无分隔符的单 token 检规；仅 EXACT/NOT_FOUND 两态）
--
--    分隔符守卫：wlbm 不得含 ; ； , ， 、 : ： / 空格 Tab CR LF
--    （'\\' 为 MySQL LIKE 默认转义符，此处无需转义任何目标字符）
--    GLUE_UNRESOLVED 在「单 token」前提下只可能因长度越界或含中文产生，
--    本脚本对这两种情况也如实落 GLUE_UNRESOLVED，与 C# Classify 完全一致。
-- ===========================================================================
INSERT INTO ado_s5_iqc_spec_material_map
    (id, tenant_id, spec_id, spec_no, spec_version, seq, raw_token, material_code,
     match_status, source_raw, is_manual, note, create_time, update_time, create_user_id)
SELECT
    -- 确定性 ID = 3000000000000030000 + 该 spec 在本租户 qms_jygf 中按 id 升序的稳定排名。
    -- 【为什么不用 @rn := @rn + 1】MySQL 8 明确规定 SELECT 中用户变量的求值顺序未定义，
    --   实测会产生重复值并触发 PRIMARY 冲突（1062）。
    -- 【为什么不用 ROW_NUMBER()】窗口序号是对**过滤后**结果集编号，重跑时若部分行已存在，
    --   剩余行会被重新编号 → 与首次写入的 id 冲突。
    -- 相关子查询排名只依赖 qms_jygf 自身内容，与本表已有多少行无关，故重跑恒等。
    3000000000000030000 + (
        SELECT COUNT(*) FROM qms_jygf r
        WHERE r.tenant_id = g.tenant_id AND r.id <= g.id
    ),
    g.tenant_id,
    g.id,
    g.wjbh,
    g.bb,
    1,
    TRIM(g.wlbm),
    CASE WHEN im.ItemNum IS NOT NULL THEN UPPER(TRIM(g.wlbm)) ELSE NULL END,
    CASE
        WHEN im.ItemNum IS NOT NULL THEN 'EXACT'
        WHEN CHAR_LENGTH(TRIM(g.wlbm)) < 4 OR CHAR_LENGTH(TRIM(g.wlbm)) > 16 THEN 'GLUE_UNRESOLVED'
        WHEN TRIM(g.wlbm) REGEXP '[\\x{4E00}-\\x{9FFF}]' THEN 'GLUE_UNRESOLVED'
        ELSE 'NOT_FOUND'
    END,
    g.wlbm,
    0,
    NULL,
    NOW(), NOW(), NULL
FROM qms_jygf g
LEFT JOIN ItemMaster im
       ON im.tenant_id = g.tenant_id
      AND im.ItemNum = UPPER(TRIM(g.wlbm))
WHERE @uat_a IS NOT NULL
  AND g.tenant_id = @uat_a
  AND g.wlbm IS NOT NULL
  AND TRIM(g.wlbm) <> ''
  -- 分词等价性守卫：只处理无分隔符的单 token
  AND TRIM(g.wlbm) NOT LIKE '%;%'
  AND TRIM(g.wlbm) NOT LIKE '%；%'
  AND TRIM(g.wlbm) NOT LIKE '%,%'
  AND TRIM(g.wlbm) NOT LIKE '%，%'
  AND TRIM(g.wlbm) NOT LIKE '%、%'
  AND TRIM(g.wlbm) NOT LIKE '%:%'
  AND TRIM(g.wlbm) NOT LIKE '%：%'
  AND TRIM(g.wlbm) NOT LIKE '%/%'
  AND TRIM(g.wlbm) NOT LIKE '% %'
  AND TRIM(g.wlbm) NOT LIKE CONCAT('%', CHAR(9), '%')
  AND TRIM(g.wlbm) NOT LIKE CONCAT('%', CHAR(13), '%')
  AND TRIM(g.wlbm) NOT LIKE CONCAT('%', CHAR(10), '%')
  -- 幂等：同 (tenant, spec, raw_token, is_manual) 已存在则跳过
  AND NOT EXISTS (
        SELECT 1 FROM ado_s5_iqc_spec_material_map x
        WHERE x.tenant_id = g.tenant_id
          AND x.spec_id   = g.id
          AND x.raw_token = TRIM(g.wlbm)
          AND x.is_manual = 0);
