Browse Source

fix(opportunity): 修复新建商机 500 并保护系统预设阶段模板

新建商机因 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
master
luoweijian 1 week ago
parent
commit
938aed922e
  1. 19
      crm-opportunity/src/main/java/com/crm/opportunity/intake/impl/OpportunityIntakeImpl.java
  2. 3
      crm-rule/src/main/java/com/crm/rule/constant/OpportunityRuleConstants.java
  3. 10
      crm-rule/src/main/java/com/crm/rule/service/impl/OpportunityStageTemplateServiceImpl.java

19
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 落首节点 * 02 2 / 06按负责人部门解析应绑定的发布中阶段模板版本current_stage_id 落首节点
* listNodesOfVersion 已按 seq_no 升序并锁模板版本 id/无发布中模板 null * listNodesOfVersion 已按 seq_no 升序并锁模板版本 id/
* 三字段留空容忍空走法 AownerDeptId 为空亦然走默认模板兜底 *
* <p><b>无发布中模板的兜底D-01 修复</b> resolveBindingVersion null全库无
* 发布中阶段模板例如原发布中模板被编辑/停用或版本无节点时stage_template_id /
* stage_template_version / current_stage_id 三列均为 DB NOT NULL 无默认值若不赋值直接
* return 会导致 INSERT DataIntegrityViolationException新建商机必 500故显式回填零值
* 占位与存量 current_stage_id=0 数据先例一致使容忍无模板的语义在 DB 约束下
* 真正成立后续运维配置发布中模板后新建即自动落位首节点</p>
*/ */
private void applyInitialStage(Opportunity opp, OpportunityIntakeSpec spec) { private void applyInitialStage(Opportunity opp, OpportunityIntakeSpec spec) {
OpportunityStageTemplate version = stageTemplateService.resolveBindingVersion(spec.ownerDeptId()); OpportunityStageTemplate version = stageTemplateService.resolveBindingVersion(spec.ownerDeptId());
if (version == null) { if (version == null) {
return; // D-01 策略 B(fail-fast):系统不变式=始终有一个发布中的默认商机阶段模板。
// 全库无发布中模板 = 配置损坏(预设模板被误停用/删除),不静默产出无阶段脏商机,
// 而是显式报错提醒管理员修后台。经 seam 译为 BusinessErrorException(HTTP 友好)。
throw new OpportunityIntakeException(
"系统未配置发布中的商机阶段模板,无法新建商机,请联系管理员在后台发布商机阶段模板");
} }
List<OpportunityStageNode> nodes = stageTemplateService.listNodesOfVersion(version.getId()); List<OpportunityStageNode> nodes = stageTemplateService.listNodesOfVersion(version.getId());
if (nodes == null || nodes.isEmpty()) { if (nodes == null || nodes.isEmpty()) {
return; throw new OpportunityIntakeException(
"商机阶段模板无阶段节点,无法新建商机,请联系管理员检查后台商机阶段配置");
} }
opp.setStageTemplateId(version.getId()); opp.setStageTemplateId(version.getId());
opp.setStageTemplateVersion(version.getVersionNo()); opp.setStageTemplateVersion(version.getVersionNo());

3
crm-rule/src/main/java/com/crm/rule/constant/OpportunityRuleConstants.java

@ -95,6 +95,9 @@ public interface OpportunityRuleConstants {
/** 发布校验失败:阶段节点配置不完整(无普通节点/字典 code 缺失) */ /** 发布校验失败:阶段节点配置不完整(无普通节点/字典 code 缺失) */
int CODE_STAGE_TPL_NODE_INVALID = 64017; int CODE_STAGE_TPL_NODE_INVALID = 64017;
/** 系统预设阶段模板受保护:不可删除/停用(保证始终有发布中默认模板,D-01) */
int CODE_STAGE_TPL_SEED_PROTECTED = 64018;
/*-------- 方案卡模板(票 07):编码前缀 + 字段类型值域 ---------*/ /*-------- 方案卡模板(票 07):编码前缀 + 字段类型值域 ---------*/
/** 方案卡模板编码前缀(系统生成,序号顺延,V-CONFIG 1) */ /** 方案卡模板编码前缀(系统生成,序号顺延,V-CONFIG 1) */

10
crm-rule/src/main/java/com/crm/rule/service/impl/OpportunityStageTemplateServiceImpl.java

@ -329,6 +329,11 @@ public class OpportunityStageTemplateServiceImpl
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public void disableTemplate(Long id) { public void disableTemplate(Long id) {
OpportunityStageTemplate exist = getTemplateByIdOrThrow(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) { if (exist.getStatus() == null || exist.getStatus() != OpportunityRuleConstants.VERSION_STATUS_PUBLISHED) {
throw new BusinessErrorException(OpportunityRuleConstants.CODE_STAGE_TPL_INVALID, throw new BusinessErrorException(OpportunityRuleConstants.CODE_STAGE_TPL_INVALID,
"仅发布中的模板可停用"); "仅发布中的模板可停用");
@ -340,6 +345,11 @@ public class OpportunityStageTemplateServiceImpl
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public void deleteDraft(Long id) { public void deleteDraft(Long id) {
OpportunityStageTemplate exist = getTemplateByIdOrThrow(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) { if (exist.getStatus() == null || exist.getStatus() != OpportunityRuleConstants.VERSION_STATUS_DRAFT) {
throw new BusinessErrorException(OpportunityRuleConstants.CODE_STAGE_TPL_NOT_DRAFT, throw new BusinessErrorException(OpportunityRuleConstants.CODE_STAGE_TPL_NOT_DRAFT,
"仅草稿版本可删除"); "仅草稿版本可删除");

Loading…
Cancel
Save