From 9559b251d7d841bda5b2a6df001858ee507cee56 Mon Sep 17 00:00:00 2001 From: luoweijian <1329394916@qq.com> Date: Fri, 11 Sep 2026 01:02:35 +0800 Subject: [PATCH] =?UTF-8?q?ticket01=E4=BF=AE=E6=AD=A3:=20=E8=BF=81?= =?UTF-8?q?=E7=A7=BB=E5=99=A8=E6=94=BE=E5=AE=BD=E9=81=97=E7=95=99=E5=88=97?= =?UTF-8?q?=20opp=5Fid=20=E4=B8=BA=E5=8F=AF=E7=A9=BA=EF=BC=88=E5=AE=9E?= =?UTF-8?q?=E4=BD=93=E4=B8=8D=E5=86=8D=E6=98=A0=E5=B0=84=EF=BC=8CNOT=20NUL?= =?UTF-8?q?L=20=E4=BC=9A=E8=AE=A9=20A3=20=E6=96=B0=E5=BB=BA=E5=8D=A1=20INS?= =?UTF-8?q?ERT=20=E5=A4=B1=E8=B4=A5=EF=BC=8C=E7=9C=9F=E5=AE=9E=E7=8E=AF?= =?UTF-8?q?=E5=A2=83=E5=AE=9E=E6=B5=8B=EF=BC=89+=20ADR-0041=20=E4=BF=AE?= =?UTF-8?q?=E8=AE=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../opportunity/config/SchemeCardOwnerMigrationRunner.java | 5 +++++ docs/adr/0041-scheme-card-owner-decouple.md | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/crm-opportunity/src/main/java/com/crm/opportunity/config/SchemeCardOwnerMigrationRunner.java b/crm-opportunity/src/main/java/com/crm/opportunity/config/SchemeCardOwnerMigrationRunner.java index 4aebc58..5b8f70c 100644 --- a/crm-opportunity/src/main/java/com/crm/opportunity/config/SchemeCardOwnerMigrationRunner.java +++ b/crm-opportunity/src/main/java/com/crm/opportunity/config/SchemeCardOwnerMigrationRunner.java @@ -67,6 +67,11 @@ public class SchemeCardOwnerMigrationRunner implements CommandLineRunner { "UPDATE " + TABLE + " SET owner_type = 1, owner_id = opp_id" + " WHERE owner_id IS NULL OR owner_id = 0"); log.info("[scheme-card-owner-migration] 存量方案卡回填商机归属 {} 行", backfilled); + // 放宽旧列为可空:实体已不映射 opp_id,新插卡不带该列——NOT NULL 会让 A3 新建卡 + // 首次 INSERT 即失败(真实环境实测)。旧行值保留供审计,新行 NULL。 + jdbcTemplate.execute("ALTER TABLE " + TABLE + " MODIFY COLUMN opp_id bigint NULL" + + " COMMENT '(已废弃·owner 化解耦遗留)历史所属商机ID,新行恒 NULL'"); + log.info("[scheme-card-owner-migration] 旧列 opp_id 已放宽为可空"); } if (!indexExists(conn, TABLE, "uk_osc_owner_customer")) { diff --git a/docs/adr/0041-scheme-card-owner-decouple.md b/docs/adr/0041-scheme-card-owner-decouple.md index e02439f..214b904 100644 --- a/docs/adr/0041-scheme-card-owner-decouple.md +++ b/docs/adr/0041-scheme-card-owner-decouple.md @@ -25,7 +25,7 @@ A5 项目管理(crm-project,独立建表 `crm_project`,见 deliverable-spe ## Decision 1. **归属列**:`oppId`(not null) → `ownerType`(tinyint: 1=OPPORTUNITY/2=PROJECT, default 1) + `ownerId`(bigint not null)。唯一约束 `uk_osc_opp_customer(opp_id, customer_id, delete_key)` → `uk_osc_owner_customer(owner_type, owner_id, customer_id, delete_key)`;`opp_id` 命名陷阱检查通过(AGENTS.md,无需钉列名)。 -2. **存量迁移**:启动期 `SchemeCardOwnerMigrationRunner`(幂等,按列/索引存在性判断)——老库补列、存量行回填 `owner_type=1, owner_id=opp_id`、唯一约束换轨;旧列 `opp_id` 与旧索引 `idx_osc_opp` 保留为只读死列(共享库不做破坏性 DROP,应用代码零引用)。 +2. **存量迁移**:启动期 `SchemeCardOwnerMigrationRunner`(幂等,按列/索引存在性判断)——老库补列、存量行回填 `owner_type=1, owner_id=opp_id`、唯一约束换轨;旧列 `opp_id` 放宽为**可空死列**(实体已不映射,新插卡不带该列,NOT NULL 会让 A3 新建卡 INSERT 失败——真实环境实测修正;旧行值保留供审计)。 3. **商机侧行为不变**:`OpportunitySchemeCardService` 商机入口签名不动(`listCards(oppId)` 等内部过滤 `owner_type=OPPORTUNITY`);商机卡保存的客户校验(必属本商机关联客户,无孤立卡)原样保留;商机列表/详情的方案卡列 join 加 `ownerType=OPPORTUNITY` 过滤(`OpportunityViewQueryImpl.fillSchemeCardColumns`、`OpportunityDetailServiceImpl` 兜底查)。 4. **PROJECT 归属校验接口化**:新增出站端口 `SchemeCardProjectPort`(crm-opportunity 声明 + Noop 桩,真实现住 crm-project `@Primary` 覆盖,对称 `OpportunityOwnerSyncPortImpl`/Noop 范式)。桩 **fail-closed**:`requireLiveProject` 抛 66015 明确拒绝——「无孤立卡」铁律不允许校验缺位时静默放行。保存入口按 `dto.oppId`/`dto.projectId` 互斥分叉(双归属互斥,一次一张归属的卡)。 5. **提交路径差异**:项目卡无商机「暂缓」守卫、无商机主表快照回写(D-17 仅商机卡);提交必填校验等其余逻辑共用。