Browse Source

fix(opportunity): 转商机契约补省市两级地区,修丢字段脏数据

根因:转商机命令 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 全绿。
master
luoweijian 2 weeks ago
parent
commit
e4e4cc2347
  1. 7
      crm-lead/src/main/java/com/crm/lead/domain/param/ConvertOpportunityParam.java
  2. 6
      crm-lead/src/main/java/com/crm/lead/port/outbound/CreateOpportunityCmd.java
  3. 5
      crm-lead/src/main/java/com/crm/lead/service/impl/LeadServiceImpl.java
  4. 3
      crm-lead/src/test/java/com/crm/lead/service/impl/LeadServiceImplTest.java
  5. 15
      crm-opportunity/src/main/java/com/crm/opportunity/port/inbound/impl/OpportunityCreationPortImpl.java
  6. 5
      crm-opportunity/src/test/java/com/crm/opportunity/port/inbound/OpportunityCreationPortImplTest.java

7
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 下拉未就绪前可空)") @Schema(description = "意向客户 id(票 11;下拉选客户得到,引用 A4 客户模块。A4 下拉未就绪前可空)")
private Long customerId; private Long customerId;
@Schema(description = "地区国标 code(必填)") @Schema(description = "省份国标 code(必填)")
private String regionCode; private String provinceCode;
@Schema(description = "市/区国标 code(必填)")
private String cityCode;
@Schema(description = "备注(自由文本,可空)") @Schema(description = "备注(自由文本,可空)")
private String remark; private String remark;

6
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 客户模块 * @param customerId 意向客户 id 11下拉选客户得到引用 A4 客户模块
* <b>暂可空</b>A4 客户下拉未就绪前前端传 null商机侧跳过建关联子表 * <b>暂可空</b>A4 客户下拉未就绪前前端传 null商机侧跳过建关联子表
* A4 就绪后收紧为必填 11 终态契约 * A4 就绪后收紧为必填 11 终态契约
* @param regionCode 地区国标 codevarchar 12必填 * @param provinceCode 省份国标 code从线索带出必填
* @param cityCode /区国标 code从线索带出必填
* @param remark 备注自由文本可空 * @param remark 备注自由文本可空
* @param sourceLeadName 线索名称快照 * @param sourceLeadName 线索名称快照
* @param sourcePhone 联系电话快照 * @param sourcePhone 联系电话快照
@ -28,7 +29,8 @@ public record CreateOpportunityCmd(
String partyA, String partyA,
String intendedCustomer, String intendedCustomer,
Long customerId, Long customerId,
String regionCode, String provinceCode,
String cityCode,
String remark, String remark,
String sourceLeadName, String sourceLeadName,
String sourcePhone, String sourcePhone,

5
crm-lead/src/main/java/com/crm/lead/service/impl/LeadServiceImpl.java

@ -346,7 +346,8 @@ public class LeadServiceImpl extends BaseServiceImpl<LeadMapper, Lead>
if (StrUtil.isBlank(param.getOpportunityName()) if (StrUtil.isBlank(param.getOpportunityName())
|| StrUtil.isBlank(param.getIndustryCode()) || StrUtil.isBlank(param.getIndustryCode())
|| StrUtil.isBlank(param.getIntendedCustomer()) || StrUtil.isBlank(param.getIntendedCustomer())
|| StrUtil.isBlank(param.getRegionCode())) { || StrUtil.isBlank(param.getProvinceCode())
|| StrUtil.isBlank(param.getCityCode())) {
throw new BusinessErrorException(LeadConstants.CODE_LEAD_INVALID, throw new BusinessErrorException(LeadConstants.CODE_LEAD_INVALID,
"商机名称、行业、意向客户、地区为必填"); "商机名称、行业、意向客户、地区为必填");
} }
@ -362,7 +363,7 @@ public class LeadServiceImpl extends BaseServiceImpl<LeadMapper, Lead>
opportunityId = port.createOpportunity(new CreateOpportunityCmd( opportunityId = port.createOpportunity(new CreateOpportunityCmd(
lead.getId(), param.getOpportunityName(), param.getIndustryCode(), lead.getId(), param.getOpportunityName(), param.getIndustryCode(),
param.getPartyA(), param.getIntendedCustomer(), param.getCustomerId(), param.getPartyA(), param.getIntendedCustomer(), param.getCustomerId(),
param.getRegionCode(), param.getProvinceCode(), param.getCityCode(),
param.getRemark(), lead.getLeadName(), lead.getPhone(), param.getRemark(), lead.getLeadName(), lead.getPhone(),
lead.getProductCode(), lead.getOwnerUserId(), lead.getProductCode(), lead.getOwnerUserId(),
lead.getOwnerDeptIdSnapshot(), lead.getPoolId())); lead.getOwnerDeptIdSnapshot(), lead.getPoolId()));

3
crm-lead/src/test/java/com/crm/lead/service/impl/LeadServiceImplTest.java

@ -182,7 +182,8 @@ class LeadServiceImplTest {
param.setOpportunityName("测试商机"); param.setOpportunityName("测试商机");
param.setIndustryCode("it"); param.setIndustryCode("it");
param.setIntendedCustomer("测试客户"); param.setIntendedCustomer("测试客户");
param.setRegionCode("440100"); param.setProvinceCode("440000");
param.setCityCode("440100");
return param; return param;
} }

15
crm-opportunity/src/main/java/com/crm/opportunity/port/inbound/impl/OpportunityCreationPortImpl.java

@ -25,13 +25,10 @@ import java.time.LocalDateTime;
* <p><b> 12 唯一性预检</b>新建前查 {@code source_lead_id} 是否已被占用命中即抛业务错误 * <p><b> 12 唯一性预检</b>新建前查 {@code source_lead_id} 是否已被占用命中即抛业务错误
* 避免依赖 DB 唯一键冲突走错误路径DB {@code uk(source_lead_id)} 仍作并发最终防线</p> * 避免依赖 DB 唯一键冲突走错误路径DB {@code uk(source_lead_id)} 仍作并发最终防线</p>
* *
* <p><b> 13 关联客户</b>{@code cmd.customerId()} 非空时建一条 {@code is_primary_intended=1} * <p><b> A4 走法甲</b>{@code cmd.customerId()} 非空时建一条 {@code is_primary_intended=1}
* 主要意向客户子表 + 刷主表冗余 {@code primary_customer_id/name} 03 C3 * 主要意向客户子表 + 刷主表冗余 {@code primary_customer_id/name} 03 C3
* <b>customerId 走法甲 11</b>A4 客户下拉未就绪前 customerId 可空此时跳过建子表 / * A4 客户下拉未就绪前 customerId 可空此时跳过建子表 / 不刷冗余不倒退现有自由文本转商机
* 不刷冗余不倒退现有自由文本转商机A4 就绪后收紧为必填</p> * A4 就绪后收紧为必填</p>
*
* <p><b>省市 TODO</b>cmd 仅带单个 {@code regionCode}主表分 {@code provinceCode}/{@code cityCode}
* 两列且 not null省市拆分逻辑无 spec非本票职责暂两列同填 regionCode 占位省市拆分专票</p>
*/ */
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@ -74,9 +71,9 @@ public class OpportunityCreationPortImpl implements OpportunityCreationPort {
opp.setIndustryCode(cmd.industryCode()); opp.setIndustryCode(cmd.industryCode());
opp.setPartyA(cmd.partyA()); opp.setPartyA(cmd.partyA());
opp.setRemark(cmd.remark()); opp.setRemark(cmd.remark());
// 省市 TODO:单 regionCode → 两列 not null,暂同填占位,待省市拆分专票 // 省/市国标 code(与线索同构两级,由转商机弹窗地区选择器带出)
opp.setProvinceCode(cmd.regionCode()); opp.setProvinceCode(cmd.provinceCode());
opp.setCityCode(cmd.regionCode()); opp.setCityCode(cmd.cityCode());
// 归属 / 来源快照(从线索带出) // 归属 / 来源快照(从线索带出)
opp.setOwnerUserId(cmd.ownerUserId()); opp.setOwnerUserId(cmd.ownerUserId());
opp.setOwnerDeptId(cmd.ownerDeptId()); opp.setOwnerDeptId(cmd.ownerDeptId());

5
crm-opportunity/src/test/java/com/crm/opportunity/port/inbound/OpportunityCreationPortImplTest.java

@ -48,7 +48,7 @@ class OpportunityCreationPortImplTest {
private CreateOpportunityCmd cmd(Long customerId) { private CreateOpportunityCmd cmd(Long customerId) {
return new CreateOpportunityCmd( return new CreateOpportunityCmd(
LEAD_ID, "商机A", "ind01", "甲方X", "意向客户名", customerId, LEAD_ID, "商机A", "ind01", "甲方X", "意向客户名", customerId,
"110100", "备注", "线索A", "13800000000", "prod01", "110000", "110100", "备注", "线索A", "13800000000", "prod01",
100L, 200L, 300L); 100L, 200L, 300L);
} }
@ -128,6 +128,9 @@ class OpportunityCreationPortImplTest {
assertThat(saved.getSourceLeadId()).isEqualTo(LEAD_ID); assertThat(saved.getSourceLeadId()).isEqualTo(LEAD_ID);
assertThat(saved.getClaimTime()).isNotNull(); assertThat(saved.getClaimTime()).isNotNull();
assertThat(saved.getPrimaryCustomerNameSnapshot()).isEqualTo("意向客户名"); assertThat(saved.getPrimaryCustomerNameSnapshot()).isEqualTo("意向客户名");
// 省/市两级分别落库(不再同填同一 code)
assertThat(saved.getProvinceCode()).isEqualTo("110000");
assertThat(saved.getCityCode()).isEqualTo("110100");
} }
@Test @Test

Loading…
Cancel
Save