From e4e4cc2347f2bec9904b42f4a3ff10031ba58bde Mon Sep 17 00:00:00 2001 From: luoweijian <1329394916@qq.com> Date: Tue, 25 Aug 2026 15:40:40 +0800 Subject: [PATCH] =?UTF-8?q?fix(opportunity):=20=E8=BD=AC=E5=95=86=E6=9C=BA?= =?UTF-8?q?=E5=A5=91=E7=BA=A6=E8=A1=A5=E7=9C=81=E5=B8=82=E4=B8=A4=E7=BA=A7?= =?UTF-8?q?=E5=9C=B0=E5=8C=BA=EF=BC=8C=E4=BF=AE=E4=B8=A2=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E8=84=8F=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因:转商机命令 CreateOpportunityCmd 只带单个 regionCode,而源头线索与 目的地商机主表都存 provinceCode+cityCode 两级(均 not null)。命令丢了一 级信息,商机侧被迫两列同填一个 code → 省份列存市/区 code,列表区划回显错。 这是命令契约丢信息,非缺拆分算法(两级值源头本就都有)。 修(补契约字段,非拆分): - CreateOpportunityCmd:regionCode → provinceCode + cityCode - ConvertOpportunityParam:同拆两字段(转商机弹窗地区选择器本就省市两级) - LeadServiceImpl:必填校验拆两字段 + 组装 cmd 透传 param 的省/市 - OpportunityCreationPortImpl:分别 set 两列,删同填占位与「省市拆分待专票」TODO - 单测:断言省/市两级分别落库(110000/110100);既有 LeadServiceImplTest 同步拆 mvn -pl crm-opportunity -am test 152 全绿 + crm-lead 116 全绿。 --- .../domain/param/ConvertOpportunityParam.java | 7 +++++-- .../lead/port/outbound/CreateOpportunityCmd.java | 6 ++++-- .../crm/lead/service/impl/LeadServiceImpl.java | 5 +++-- .../lead/service/impl/LeadServiceImplTest.java | 3 ++- .../inbound/impl/OpportunityCreationPortImpl.java | 15 ++++++--------- .../inbound/OpportunityCreationPortImplTest.java | 5 ++++- 6 files changed, 24 insertions(+), 17 deletions(-) diff --git a/crm-lead/src/main/java/com/crm/lead/domain/param/ConvertOpportunityParam.java b/crm-lead/src/main/java/com/crm/lead/domain/param/ConvertOpportunityParam.java index ee175d9..a118682 100644 --- a/crm-lead/src/main/java/com/crm/lead/domain/param/ConvertOpportunityParam.java +++ b/crm-lead/src/main/java/com/crm/lead/domain/param/ConvertOpportunityParam.java @@ -27,8 +27,11 @@ public class ConvertOpportunityParam { @Schema(description = "意向客户 id(票 11;下拉选客户得到,引用 A4 客户模块。A4 下拉未就绪前可空)") private Long customerId; - @Schema(description = "地区国标 code(必填)") - private String regionCode; + @Schema(description = "省份国标 code(必填)") + private String provinceCode; + + @Schema(description = "市/区国标 code(必填)") + private String cityCode; @Schema(description = "备注(自由文本,可空)") private String remark; diff --git a/crm-lead/src/main/java/com/crm/lead/port/outbound/CreateOpportunityCmd.java b/crm-lead/src/main/java/com/crm/lead/port/outbound/CreateOpportunityCmd.java index 6a75470..eac58bf 100644 --- a/crm-lead/src/main/java/com/crm/lead/port/outbound/CreateOpportunityCmd.java +++ b/crm-lead/src/main/java/com/crm/lead/port/outbound/CreateOpportunityCmd.java @@ -12,7 +12,8 @@ package com.crm.lead.port.outbound; * @param customerId 意向客户 id(票 11;下拉选客户得到,引用 A4 客户模块)。 * 暂可空:A4 客户下拉未就绪前前端传 null,商机侧跳过建关联子表; * A4 就绪后收紧为必填(票 11 终态契约)。 - * @param regionCode 地区国标 code(varchar 12,必填) + * @param provinceCode 省份国标 code(从线索带出,必填) + * @param cityCode 市/区国标 code(从线索带出,必填) * @param remark 备注(自由文本,可空) * @param sourceLeadName 线索名称快照 * @param sourcePhone 联系电话快照 @@ -28,7 +29,8 @@ public record CreateOpportunityCmd( String partyA, String intendedCustomer, Long customerId, - String regionCode, + String provinceCode, + String cityCode, String remark, String sourceLeadName, String sourcePhone, diff --git a/crm-lead/src/main/java/com/crm/lead/service/impl/LeadServiceImpl.java b/crm-lead/src/main/java/com/crm/lead/service/impl/LeadServiceImpl.java index 35044f9..c80a475 100644 --- a/crm-lead/src/main/java/com/crm/lead/service/impl/LeadServiceImpl.java +++ b/crm-lead/src/main/java/com/crm/lead/service/impl/LeadServiceImpl.java @@ -346,7 +346,8 @@ public class LeadServiceImpl extends BaseServiceImpl if (StrUtil.isBlank(param.getOpportunityName()) || StrUtil.isBlank(param.getIndustryCode()) || StrUtil.isBlank(param.getIntendedCustomer()) - || StrUtil.isBlank(param.getRegionCode())) { + || StrUtil.isBlank(param.getProvinceCode()) + || StrUtil.isBlank(param.getCityCode())) { throw new BusinessErrorException(LeadConstants.CODE_LEAD_INVALID, "商机名称、行业、意向客户、地区为必填"); } @@ -362,7 +363,7 @@ public class LeadServiceImpl extends BaseServiceImpl opportunityId = port.createOpportunity(new CreateOpportunityCmd( lead.getId(), param.getOpportunityName(), param.getIndustryCode(), param.getPartyA(), param.getIntendedCustomer(), param.getCustomerId(), - param.getRegionCode(), + param.getProvinceCode(), param.getCityCode(), param.getRemark(), lead.getLeadName(), lead.getPhone(), lead.getProductCode(), lead.getOwnerUserId(), lead.getOwnerDeptIdSnapshot(), lead.getPoolId())); diff --git a/crm-lead/src/test/java/com/crm/lead/service/impl/LeadServiceImplTest.java b/crm-lead/src/test/java/com/crm/lead/service/impl/LeadServiceImplTest.java index a353349..765fd4c 100644 --- a/crm-lead/src/test/java/com/crm/lead/service/impl/LeadServiceImplTest.java +++ b/crm-lead/src/test/java/com/crm/lead/service/impl/LeadServiceImplTest.java @@ -182,7 +182,8 @@ class LeadServiceImplTest { param.setOpportunityName("测试商机"); param.setIndustryCode("it"); param.setIntendedCustomer("测试客户"); - param.setRegionCode("440100"); + param.setProvinceCode("440000"); + param.setCityCode("440100"); return param; } diff --git a/crm-opportunity/src/main/java/com/crm/opportunity/port/inbound/impl/OpportunityCreationPortImpl.java b/crm-opportunity/src/main/java/com/crm/opportunity/port/inbound/impl/OpportunityCreationPortImpl.java index 1ebf393..778e9f4 100644 --- a/crm-opportunity/src/main/java/com/crm/opportunity/port/inbound/impl/OpportunityCreationPortImpl.java +++ b/crm-opportunity/src/main/java/com/crm/opportunity/port/inbound/impl/OpportunityCreationPortImpl.java @@ -25,13 +25,10 @@ import java.time.LocalDateTime; *

票 12 唯一性预检:新建前查 {@code source_lead_id} 是否已被占用,命中即抛业务错误 * (避免依赖 DB 唯一键冲突走错误路径);DB {@code uk(source_lead_id)} 仍作并发最终防线。

* - *

票 13 关联客户:{@code cmd.customerId()} 非空时建一条 {@code is_primary_intended=1} + *

沨 A4 走法甲:{@code cmd.customerId()} 非空时建一条 {@code is_primary_intended=1} * 主要意向客户子表 + 刷主表冗余 {@code primary_customer_id/name}(票 03 C3)。 - * customerId 走法甲(票 11):A4 客户下拉未就绪前 customerId 可空,此时跳过建子表 / - * 不刷冗余(不倒退现有自由文本转商机);A4 就绪后收紧为必填。

- * - *

省市 TODO:cmd 仅带单个 {@code regionCode},主表分 {@code provinceCode}/{@code cityCode} - * 两列且 not null;省市拆分逻辑无 spec、非本票职责,暂两列同填 regionCode 占位,待「省市拆分」专票。

+ * A4 客户下拉未就绪前 customerId 可空,此时跳过建子表 / 不刷冗余(不倒退现有自由文本转商机); + * A4 就绪后收紧为必填。

*/ @Service @RequiredArgsConstructor @@ -74,9 +71,9 @@ public class OpportunityCreationPortImpl implements OpportunityCreationPort { opp.setIndustryCode(cmd.industryCode()); opp.setPartyA(cmd.partyA()); opp.setRemark(cmd.remark()); - // 省市 TODO:单 regionCode → 两列 not null,暂同填占位,待省市拆分专票 - opp.setProvinceCode(cmd.regionCode()); - opp.setCityCode(cmd.regionCode()); + // 省/市国标 code(与线索同构两级,由转商机弹窗地区选择器带出) + opp.setProvinceCode(cmd.provinceCode()); + opp.setCityCode(cmd.cityCode()); // 归属 / 来源快照(从线索带出) opp.setOwnerUserId(cmd.ownerUserId()); opp.setOwnerDeptId(cmd.ownerDeptId()); diff --git a/crm-opportunity/src/test/java/com/crm/opportunity/port/inbound/OpportunityCreationPortImplTest.java b/crm-opportunity/src/test/java/com/crm/opportunity/port/inbound/OpportunityCreationPortImplTest.java index 932e279..887e596 100644 --- a/crm-opportunity/src/test/java/com/crm/opportunity/port/inbound/OpportunityCreationPortImplTest.java +++ b/crm-opportunity/src/test/java/com/crm/opportunity/port/inbound/OpportunityCreationPortImplTest.java @@ -48,7 +48,7 @@ class OpportunityCreationPortImplTest { private CreateOpportunityCmd cmd(Long customerId) { return new CreateOpportunityCmd( LEAD_ID, "商机A", "ind01", "甲方X", "意向客户名", customerId, - "110100", "备注", "线索A", "13800000000", "prod01", + "110000", "110100", "备注", "线索A", "13800000000", "prod01", 100L, 200L, 300L); } @@ -128,6 +128,9 @@ class OpportunityCreationPortImplTest { assertThat(saved.getSourceLeadId()).isEqualTo(LEAD_ID); assertThat(saved.getClaimTime()).isNotNull(); assertThat(saved.getPrimaryCustomerNameSnapshot()).isEqualTo("意向客户名"); + // 省/市两级分别落库(不再同填同一 code) + assertThat(saved.getProvinceCode()).isEqualTo("110000"); + assertThat(saved.getCityCode()).isEqualTo("110100"); } @Test