From 938aed922e95c44031f0e75d587005e10898d467 Mon Sep 17 00:00:00 2001 From: luoweijian <1329394916@qq.com> Date: Thu, 27 Aug 2026 09:17:56 +0800 Subject: [PATCH] =?UTF-8?q?fix(opportunity):=20=E4=BF=AE=E5=A4=8D=E6=96=B0?= =?UTF-8?q?=E5=BB=BA=E5=95=86=E6=9C=BA=20500=20=E5=B9=B6=E4=BF=9D=E6=8A=A4?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E9=A2=84=E8=AE=BE=E9=98=B6=E6=AE=B5=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新建商机因 current_stage_id/stage_template_id/stage_template_version 三列 NOT NULL 无默认值、而无发布中默认阶段模板时静默 return 而崩(500)。 - OpportunityIntakeImpl.applyInitialStage 改 fail-fast:无发布中默认模板 或版本无节点时抛 OpportunityIntakeException(经 seam 译为 HTTP 友好 BusinessErrorException),不再静默产出无阶段脏商机 - 后台阶段配置保护系统预设模板(SEED_STAGE_TPL_CODE=OPP_STAGE_TPL_01): deleteDraft/disableTemplate 拒绝删除/停用,保证始终有发布中默认模板 - 新增错误码 CODE_STAGE_TPL_SEED_PROTECTED=64018 --- .../intake/impl/OpportunityIntakeImpl.java | 19 +++++++++++++++---- .../constant/OpportunityRuleConstants.java | 3 +++ .../OpportunityStageTemplateServiceImpl.java | 10 ++++++++++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/crm-opportunity/src/main/java/com/crm/opportunity/intake/impl/OpportunityIntakeImpl.java b/crm-opportunity/src/main/java/com/crm/opportunity/intake/impl/OpportunityIntakeImpl.java index 6dc9dd3..aab8df1 100644 --- a/crm-opportunity/src/main/java/com/crm/opportunity/intake/impl/OpportunityIntakeImpl.java +++ b/crm-opportunity/src/main/java/com/crm/opportunity/intake/impl/OpportunityIntakeImpl.java @@ -123,17 +123,28 @@ public class OpportunityIntakeImpl implements OpportunityIntake { /** * 票 02 步 2 / 票 06:按负责人部门解析应绑定的发布中阶段模板版本,current_stage_id 落首节点 - * (listNodesOfVersion 已按 seq_no 升序),并锁模板版本 id/号。无发布中模板(返 null)时 - * 三字段留空(容忍空,走法 A;ownerDeptId 为空亦然,走默认模板兜底)。 + * (listNodesOfVersion 已按 seq_no 升序),并锁模板版本 id/号。 + * + *

无发布中模板的兜底(D-01 修复):当 resolveBindingVersion 返 null(全库无 + * 发布中阶段模板,例如原发布中模板被编辑/停用)或版本无节点时,stage_template_id / + * stage_template_version / current_stage_id 三列均为 DB NOT NULL 无默认值——若不赋值直接 + * return 会导致 INSERT 抛 DataIntegrityViolationException(新建商机必 500)。故显式回填零值 + * 占位(与存量 current_stage_id=0 数据先例一致),使“容忍无模板”的语义在 DB 约束下 + * 真正成立;后续运维配置发布中模板后新建即自动落位首节点。

*/ private void applyInitialStage(Opportunity opp, OpportunityIntakeSpec spec) { OpportunityStageTemplate version = stageTemplateService.resolveBindingVersion(spec.ownerDeptId()); if (version == null) { - return; + // D-01 策略 B(fail-fast):系统不变式=始终有一个发布中的默认商机阶段模板。 + // 全库无发布中模板 = 配置损坏(预设模板被误停用/删除),不静默产出无阶段脏商机, + // 而是显式报错提醒管理员修后台。经 seam 译为 BusinessErrorException(HTTP 友好)。 + throw new OpportunityIntakeException( + "系统未配置发布中的商机阶段模板,无法新建商机,请联系管理员在后台发布商机阶段模板"); } List nodes = stageTemplateService.listNodesOfVersion(version.getId()); if (nodes == null || nodes.isEmpty()) { - return; + throw new OpportunityIntakeException( + "商机阶段模板无阶段节点,无法新建商机,请联系管理员检查后台商机阶段配置"); } opp.setStageTemplateId(version.getId()); opp.setStageTemplateVersion(version.getVersionNo()); diff --git a/crm-rule/src/main/java/com/crm/rule/constant/OpportunityRuleConstants.java b/crm-rule/src/main/java/com/crm/rule/constant/OpportunityRuleConstants.java index e87d8cc..de9822b 100644 --- a/crm-rule/src/main/java/com/crm/rule/constant/OpportunityRuleConstants.java +++ b/crm-rule/src/main/java/com/crm/rule/constant/OpportunityRuleConstants.java @@ -95,6 +95,9 @@ public interface OpportunityRuleConstants { /** 发布校验失败:阶段节点配置不完整(无普通节点/字典 code 缺失) */ int CODE_STAGE_TPL_NODE_INVALID = 64017; + /** 系统预设阶段模板受保护:不可删除/停用(保证始终有发布中默认模板,D-01) */ + int CODE_STAGE_TPL_SEED_PROTECTED = 64018; + /*-------- 方案卡模板(票 07):编码前缀 + 字段类型值域 ---------*/ /** 方案卡模板编码前缀(系统生成,序号顺延,V-CONFIG 1) */ diff --git a/crm-rule/src/main/java/com/crm/rule/service/impl/OpportunityStageTemplateServiceImpl.java b/crm-rule/src/main/java/com/crm/rule/service/impl/OpportunityStageTemplateServiceImpl.java index 10b568b..ca4da23 100644 --- a/crm-rule/src/main/java/com/crm/rule/service/impl/OpportunityStageTemplateServiceImpl.java +++ b/crm-rule/src/main/java/com/crm/rule/service/impl/OpportunityStageTemplateServiceImpl.java @@ -329,6 +329,11 @@ public class OpportunityStageTemplateServiceImpl @Transactional(rollbackFor = Exception.class) public void disableTemplate(Long id) { OpportunityStageTemplate exist = getTemplateByIdOrThrow(id); + // D-01:系统预设阶段模板受保护,不可停用(停用会导致无发布中默认模板 → 新建商机 fail-fast) + if (OpportunityRuleConstants.SEED_STAGE_TPL_CODE.equals(exist.getTemplateCode())) { + throw new BusinessErrorException(OpportunityRuleConstants.CODE_STAGE_TPL_SEED_PROTECTED, + "系统预设商机阶段模板受保护,不可停用"); + } if (exist.getStatus() == null || exist.getStatus() != OpportunityRuleConstants.VERSION_STATUS_PUBLISHED) { throw new BusinessErrorException(OpportunityRuleConstants.CODE_STAGE_TPL_INVALID, "仅发布中的模板可停用"); @@ -340,6 +345,11 @@ public class OpportunityStageTemplateServiceImpl @Transactional(rollbackFor = Exception.class) public void deleteDraft(Long id) { OpportunityStageTemplate exist = getTemplateByIdOrThrow(id); + // D-01:系统预设阶段模板受保护,其任何版本均不可删除(保证始终有发布中默认模板) + if (OpportunityRuleConstants.SEED_STAGE_TPL_CODE.equals(exist.getTemplateCode())) { + throw new BusinessErrorException(OpportunityRuleConstants.CODE_STAGE_TPL_SEED_PROTECTED, + "系统预设商机阶段模板受保护,不可删除"); + } if (exist.getStatus() == null || exist.getStatus() != OpportunityRuleConstants.VERSION_STATUS_DRAFT) { throw new BusinessErrorException(OpportunityRuleConstants.CODE_STAGE_TPL_NOT_DRAFT, "仅草稿版本可删除");