Commit c60b7231 by jianan

转介人门户:新增「我的客户」,后端运行时升级 JRE 11

- 新增 GET /c/customer/list,以新单跟进记录为粒度输出客户进展阶段(8 态语义码)
- 仪表盘新增 caseCount(跟进记录数)与 followingCount(跟进中数,不含暂缓)
- 后端镜像基础镜像 temurin 8 改为 11-jre-jammy,修复 JDK 8 下 OIDC 校验失败导致的登录刷新循环
parent 0ac1d533
# JRE 运行时(原 openjdk:8 官方镜像已停更无补丁);tag 固定到具体小版本保证构建可重现 # JRE 11 运行时:JDK 8 下 OIDC JWT 校验链路失败导致前端登录刷新循环,运行时对齐本地已验证的 JDK 11。
# 升级时替换为服务器可拉取的最新 8uXXX-bXX-jre-jammy # 用 11-jre-jammy 锁定大版本/JRE/jammy 基底(与现有 8-jre-jammy 同为 Ubuntu 22.04,无新内核兼容风险);
FROM eclipse-temurin:8u422-b05-jre-jammy # 如需构建可重现,可在服务器拉取清单后钉死为 11.0.XX_Y-jre-jammy。
FROM eclipse-temurin:11-jre-jammy
LABEL maintainer="zxm<2060197959@qq.com>" LABEL maintainer="zxm<2060197959@qq.com>"
......
package com.yd.csf.api.controller;
import com.yd.common.result.Result;
import com.yd.csf.api.security.ReferrerIdentityContext;
import com.yd.csf.service.service.IReferrerPortalService;
import com.yd.csf.service.vo.ReferrerCustomerVO;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
/**
* 转介人门户 - 我的客户。
*
* <p>容器内路径 {@code /c/customer/**},对外经 context-path 暴露为
* {@code /csf/api/c/customer/**},走转介人门户独立安全链。
* 以新单跟进记录为粒度,一行一条,按跟进记录 id 倒序,纯展示。</p>
*/
@RestController
@RequestMapping("/c/customer")
public class ReferrerCustomerController {
@Resource
private IReferrerPortalService referrerPortalService;
/**
* 我的客户列表:姓名 + 进展阶段 + 产品名 + 联系电话。
*/
@GetMapping("/list")
public Result<List<ReferrerCustomerVO>> list() {
String brokerBizId = ReferrerIdentityContext.requireBrokerBizId();
return Result.success(referrerPortalService.listCustomers(brokerBizId));
}
}
package com.yd.csf.api.enums;
import com.yd.csf.service.enums.PolicyFollowStatusEnum;
import com.yd.csf.service.enums.ReferrerCustomerStage;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* 转介人「我的客户」进展阶段归并单元测试。
*/
class ReferrerCustomerStageTest {
@Test
void fiveProgressStatuses_mergeToInProgress() {
assertEquals(ReferrerCustomerStage.IN_PROGRESS,
ReferrerCustomerStage.fromFollowStatus(PolicyFollowStatusEnum.PENDING_SIGN.getItemValue()));
assertEquals(ReferrerCustomerStage.IN_PROGRESS,
ReferrerCustomerStage.fromFollowStatus(PolicyFollowStatusEnum.SIGNED.getItemValue()));
assertEquals(ReferrerCustomerStage.IN_PROGRESS,
ReferrerCustomerStage.fromFollowStatus(PolicyFollowStatusEnum.WAITING_PAYMENT.getItemValue()));
assertEquals(ReferrerCustomerStage.IN_PROGRESS,
ReferrerCustomerStage.fromFollowStatus(PolicyFollowStatusEnum.PREMIUM_RECONCILING.getItemValue()));
assertEquals(ReferrerCustomerStage.IN_PROGRESS,
ReferrerCustomerStage.fromFollowStatus(
PolicyFollowStatusEnum.PREMIUM_RECONCILED_UNDERWRITING.getItemValue()));
}
@Test
void delayedUnderwriting_isStandaloneDelayed() {
assertEquals(ReferrerCustomerStage.DELAYED,
ReferrerCustomerStage.fromFollowStatus(
PolicyFollowStatusEnum.DELAYED_UNDERWRITING.getItemValue()));
}
@Test
void terminalStatuses_passThrough() {
assertEquals(ReferrerCustomerStage.EFFECTIVE,
ReferrerCustomerStage.fromFollowStatus(PolicyFollowStatusEnum.EFFECTIVE.getItemValue()));
assertEquals(ReferrerCustomerStage.CANCELLED,
ReferrerCustomerStage.fromFollowStatus(PolicyFollowStatusEnum.CANCELLED.getItemValue()));
assertEquals(ReferrerCustomerStage.DECLINED,
ReferrerCustomerStage.fromFollowStatus(PolicyFollowStatusEnum.DECLINED.getItemValue()));
assertEquals(ReferrerCustomerStage.LAPSED,
ReferrerCustomerStage.fromFollowStatus(PolicyFollowStatusEnum.LAPSED.getItemValue()));
assertEquals(ReferrerCustomerStage.CLOSED,
ReferrerCustomerStage.fromFollowStatus(PolicyFollowStatusEnum.CLOSED.getItemValue()));
assertEquals(ReferrerCustomerStage.APPOINTMENT_CANCELLED,
ReferrerCustomerStage.fromFollowStatus(PolicyFollowStatusEnum.QX_YY.getItemValue()));
}
@Test
void blankOrUnknown_fallbackToInProgress() {
assertEquals(ReferrerCustomerStage.IN_PROGRESS,
ReferrerCustomerStage.fromFollowStatus(null));
assertEquals(ReferrerCustomerStage.IN_PROGRESS,
ReferrerCustomerStage.fromFollowStatus(""));
assertEquals(ReferrerCustomerStage.IN_PROGRESS,
ReferrerCustomerStage.fromFollowStatus("NOT_A_REAL_STATUS"));
}
}
package com.yd.csf.service.enums;
/**
* 转介人「我的客户」进展阶段(面向转介人的语义码,前端按码做文案/配色)。
*
* <p>由新单跟进内部状态 {@link PolicyFollowStatusEnum} 归并而来:
* 5 个推进态合并为 {@link #IN_PROGRESS},延缓承保单列 {@link #DELAYED},
* 各终态分别透传。未识别状态保守兜底为 {@link #IN_PROGRESS},避免把在途误判为终止。</p>
*/
public enum ReferrerCustomerStage {
/** 跟进中:待签单/已签单/等待汇款/首期对账中/核保中 */
IN_PROGRESS,
/** 暂缓:延缓承保 */
DELAYED,
/** 已生效 */
EFFECTIVE,
/** 已取消(取消投保) */
CANCELLED,
/** 已拒保 */
DECLINED,
/** 已失效 */
LAPSED,
/** 已关闭 */
CLOSED,
/** 预约已取消 */
APPOINTMENT_CANCELLED;
public static ReferrerCustomerStage fromFollowStatus(String status) {
PolicyFollowStatusEnum s = PolicyFollowStatusEnum.getEnumByValue(status);
if (s == null) {
return IN_PROGRESS;
}
switch (s) {
case DELAYED_UNDERWRITING:
return DELAYED;
case EFFECTIVE:
return EFFECTIVE;
case CANCELLED:
return CANCELLED;
case DECLINED:
return DECLINED;
case LAPSED:
return LAPSED;
case CLOSED:
return CLOSED;
case QX_YY:
return APPOINTMENT_CANCELLED;
default:
// PENDING_SIGN / SIGNED / WAITING_PAYMENT /
// PREMIUM_RECONCILING / PREMIUM_RECONCILED_UNDERWRITING
return IN_PROGRESS;
}
}
}
package com.yd.csf.service.service; package com.yd.csf.service.service;
import com.yd.csf.service.vo.ReferrerCustomerVO;
import com.yd.csf.service.vo.ReferrerDashboardVO; import com.yd.csf.service.vo.ReferrerDashboardVO;
import com.yd.csf.service.vo.ReferrerPipelineVO; import com.yd.csf.service.vo.ReferrerPipelineVO;
import com.yd.csf.service.vo.ReferrerPolicyCommissionVO; import com.yd.csf.service.vo.ReferrerPolicyCommissionVO;
...@@ -46,4 +47,9 @@ public interface IReferrerPortalService { ...@@ -46,4 +47,9 @@ public interface IReferrerPortalService {
* 在途佣金列表。 * 在途佣金列表。
*/ */
List<ReferrerPipelineVO> listPipeline(String brokerBizId); List<ReferrerPipelineVO> listPipeline(String brokerBizId);
/**
* 我的客户进展列表(以新单跟进记录为粒度,按 id 倒序)。
*/
List<ReferrerCustomerVO> listCustomers(String brokerBizId);
} }
\ No newline at end of file
...@@ -6,13 +6,17 @@ import com.yd.csf.feign.enums.SalaryStatusEnum; ...@@ -6,13 +6,17 @@ import com.yd.csf.feign.enums.SalaryStatusEnum;
import com.yd.csf.service.enums.FortuneAccountStatusEnum; import com.yd.csf.service.enums.FortuneAccountStatusEnum;
import com.yd.csf.service.enums.PolicyStatusEnum; import com.yd.csf.service.enums.PolicyStatusEnum;
import com.yd.csf.service.enums.ReferrerCommissionStage; import com.yd.csf.service.enums.ReferrerCommissionStage;
import com.yd.csf.service.enums.ReferrerCustomerStage;
import com.yd.csf.service.model.Customer;
import com.yd.csf.service.model.ExpectedFortune; import com.yd.csf.service.model.ExpectedFortune;
import com.yd.csf.service.model.Fortune; import com.yd.csf.service.model.Fortune;
import com.yd.csf.service.model.FortuneAccount; import com.yd.csf.service.model.FortuneAccount;
import com.yd.csf.service.model.Policy; import com.yd.csf.service.model.Policy;
import com.yd.csf.service.model.PolicyBroker; import com.yd.csf.service.model.PolicyBroker;
import com.yd.csf.service.model.PolicyFollow;
import com.yd.csf.service.model.Salary; import com.yd.csf.service.model.Salary;
import com.yd.csf.service.model.SalaryRemittance; import com.yd.csf.service.model.SalaryRemittance;
import com.yd.csf.service.service.CustomerService;
import com.yd.csf.service.service.FortuneAccountService; import com.yd.csf.service.service.FortuneAccountService;
import com.yd.csf.service.service.FortuneService; import com.yd.csf.service.service.FortuneService;
import com.yd.csf.service.service.IExpectedFortuneService; import com.yd.csf.service.service.IExpectedFortuneService;
...@@ -20,7 +24,9 @@ import com.yd.csf.service.service.IReferrerPortalService; ...@@ -20,7 +24,9 @@ import com.yd.csf.service.service.IReferrerPortalService;
import com.yd.csf.service.service.ISalaryRemittanceService; import com.yd.csf.service.service.ISalaryRemittanceService;
import com.yd.csf.service.service.ISalaryService; import com.yd.csf.service.service.ISalaryService;
import com.yd.csf.service.service.PolicyBrokerService; import com.yd.csf.service.service.PolicyBrokerService;
import com.yd.csf.service.service.PolicyFollowService;
import com.yd.csf.service.service.PolicyService; import com.yd.csf.service.service.PolicyService;
import com.yd.csf.service.vo.ReferrerCustomerVO;
import com.yd.csf.service.vo.ReferrerDashboardVO; import com.yd.csf.service.vo.ReferrerDashboardVO;
import com.yd.csf.service.vo.ReferrerPipelineVO; import com.yd.csf.service.vo.ReferrerPipelineVO;
import com.yd.csf.service.vo.ReferrerPolicyCommissionItemVO; import com.yd.csf.service.vo.ReferrerPolicyCommissionItemVO;
...@@ -83,6 +89,10 @@ public class ReferrerPortalServiceImpl implements IReferrerPortalService { ...@@ -83,6 +89,10 @@ public class ReferrerPortalServiceImpl implements IReferrerPortalService {
private PolicyService policyService; private PolicyService policyService;
@Resource @Resource
private PolicyBrokerService policyBrokerService; private PolicyBrokerService policyBrokerService;
@Resource
private PolicyFollowService policyFollowService;
@Resource
private CustomerService customerService;
@Override @Override
public ReferrerDashboardVO getDashboard(String brokerBizId) { public ReferrerDashboardVO getDashboard(String brokerBizId) {
...@@ -92,6 +102,8 @@ public class ReferrerPortalServiceImpl implements IReferrerPortalService { ...@@ -92,6 +102,8 @@ public class ReferrerPortalServiceImpl implements IReferrerPortalService {
vo.setPipelineCommission(BigDecimal.ZERO); vo.setPipelineCommission(BigDecimal.ZERO);
vo.setEffectivePolicyCount(0L); vo.setEffectivePolicyCount(0L);
vo.setFirstYearFyp(BigDecimal.ZERO); vo.setFirstYearFyp(BigDecimal.ZERO);
vo.setCaseCount(0L);
vo.setFollowingCount(0L);
YearMonth now = YearMonth.now(); YearMonth now = YearMonth.now();
YearMonth taxStart = now.getMonthValue() >= 4 YearMonth taxStart = now.getMonthValue() >= 4
...@@ -136,6 +148,15 @@ public class ReferrerPortalServiceImpl implements IReferrerPortalService { ...@@ -136,6 +148,15 @@ public class ReferrerPortalServiceImpl implements IReferrerPortalService {
firstYearFyp = firstYearFyp.add(personalFirstYearPremiumHkd(policy, policyBroker)); firstYearFyp = firstYearFyp.add(personalFirstYearPremiumHkd(policy, policyBroker));
} }
vo.setFirstYearFyp(n2(firstYearFyp)); vo.setFirstYearFyp(n2(firstYearFyp));
// 我的客户:以新单跟进记录为粒度,不去重;副标仅统计「跟进中」(不含暂缓)
List<PolicyFollow> myFollows = listMyFollows(brokerBizId);
vo.setCaseCount((long) myFollows.size());
long following = myFollows.stream()
.filter(f -> ReferrerCustomerStage.fromFollowStatus(f.getStatus())
== ReferrerCustomerStage.IN_PROGRESS)
.count();
vo.setFollowingCount(following);
return vo; return vo;
} }
...@@ -300,8 +321,64 @@ public class ReferrerPortalServiceImpl implements IReferrerPortalService { ...@@ -300,8 +321,64 @@ public class ReferrerPortalServiceImpl implements IReferrerPortalService {
}).collect(Collectors.toList()); }).collect(Collectors.toList());
} }
@Override
public List<ReferrerCustomerVO> listCustomers(String brokerBizId) {
List<PolicyFollow> follows = listMyFollows(brokerBizId);
if (follows.isEmpty()) {
return new ArrayList<>();
}
Set<String> customerBizIds = follows.stream()
.map(PolicyFollow::getCustomerBizId)
.filter(StringUtils::isNotBlank)
.collect(Collectors.toSet());
Map<String, Customer> customerMap = customerBizIds.isEmpty()
? Collections.emptyMap()
: customerService.lambdaQuery()
.in(Customer::getCustomerBizId, customerBizIds)
.eq(Customer::getIsDeleted, 0)
.list()
.stream()
.collect(Collectors.toMap(Customer::getCustomerBizId, c -> c, (a, b) -> a));
List<ReferrerCustomerVO> list = new ArrayList<>();
for (PolicyFollow follow : follows) {
ReferrerCustomerVO vo = new ReferrerCustomerVO();
Customer customer = customerMap.get(follow.getCustomerBizId());
String name = customer == null ? null : customer.getNameCn();
vo.setName(StringUtils.isNotBlank(name) ? name : follow.getCustomerName());
vo.setStage(ReferrerCustomerStage.fromFollowStatus(follow.getStatus()).name());
vo.setProductName(follow.getProductName());
vo.setMobile(buildMobile(customer));
list.add(vo);
}
return list;
}
// ==================== 私有组装方法 ==================== // ==================== 私有组装方法 ====================
/**
* 名下新单跟进记录(强制首转介人归属,未删除,按 id 倒序)。
*/
private List<PolicyFollow> listMyFollows(String brokerBizId) {
return policyFollowService.lambdaQuery()
.eq(PolicyFollow::getFirstBrokerBizId, brokerBizId)
.eq(PolicyFollow::getIsDeleted, 0)
.orderByDesc(PolicyFollow::getId)
.list();
}
/**
* 联系电话:区号 + 号码;档案缺失或无号码时返回 null。
*/
private String buildMobile(Customer customer) {
if (customer == null || StringUtils.isBlank(customer.getMobile())) {
return null;
}
String code = customer.getMobileCode();
return StringUtils.isBlank(code) ? customer.getMobile() : code + customer.getMobile();
}
private List<Salary> listPushedSalaries(String brokerBizId, YearMonth start, YearMonth end) { private List<Salary> listPushedSalaries(String brokerBizId, YearMonth start, YearMonth end) {
return salaryService.lambdaQuery() return salaryService.lambdaQuery()
.eq(Salary::getBrokerBizId, brokerBizId) .eq(Salary::getBrokerBizId, brokerBizId)
......
package com.yd.csf.service.vo;
import lombok.Data;
import java.io.Serializable;
/**
* 转介人「我的客户」列表项(以新单跟进记录为粒度,第一版纯展示)。
*/
@Data
public class ReferrerCustomerVO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 客户姓名(档案缺失时回退为跟进记录客户名称)
*/
private String name;
/**
* 当前进展阶段语义码(ReferrerCustomerStage)
*/
private String stage;
/**
* 产品名称(该跟进记录冗余字段)
*/
private String productName;
/**
* 联系电话(含区号;无档案或无号码时为空)
*/
private String mobile;
}
...@@ -37,4 +37,14 @@ public class ReferrerDashboardVO implements Serializable { ...@@ -37,4 +37,14 @@ public class ReferrerDashboardVO implements Serializable {
* 首年业绩 FYP(HKD,按本人占比折算) * 首年业绩 FYP(HKD,按本人占比折算)
*/ */
private BigDecimal firstYearFyp; private BigDecimal firstYearFyp;
/**
* 我的客户:名下新单跟进记录数(不去重,含全部状态)
*/
private Long caseCount;
/**
* 我的客户:仍在「跟进中」阶段的数量(不含暂缓)
*/
private Long followingCount;
} }
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment