feat(credit): 添加客户跟进七步骤功能

- 在CreditMpCustomer实体类中添加七个跟进步骤相关字段
- 实现跟进步骤审核功能,包括单个和批量审核接口
- 添加待审核步骤查询接口和客户跟进统计功能
- 实现流程结束功能和详细的步骤状态管理
- 创建相应的DTO和VO数据传输对象
- 在Mapper层添加待审核步骤查询SQL实现
This commit is contained in:
2026-03-22 22:32:02 +08:00
parent ddebb6f16c
commit 9c929301b9
14 changed files with 1266 additions and 0 deletions

View File

@@ -0,0 +1,458 @@
# 客户跟进7步骤后端实现指南
## 📋 概述
本指南详细说明如何实现客户跟进7个步骤功能的后端代码包括数据库设计、Java后端实现和API接口。
## 🗄️ 数据库设计
### 1. 修改 credit_mp_customer 表结构
```sql
-- 为第5-7步添加字段第1-4步字段已存在
-- 第5步合同签订
ALTER TABLE credit_mp_customer ADD COLUMN follow_step5_submitted TINYINT DEFAULT 0 COMMENT '是否已提交';
ALTER TABLE credit_mp_customer ADD COLUMN follow_step5_submitted_at VARCHAR(255) COMMENT '提交时间';
ALTER TABLE credit_mp_customer ADD COLUMN follow_step5_contracts TEXT COMMENT '合同信息JSON数组';
ALTER TABLE credit_mp_customer ADD COLUMN follow_step5_need_approval TINYINT DEFAULT 1 COMMENT '是否需要审核';
ALTER TABLE credit_mp_customer ADD COLUMN follow_step5_approved TINYINT DEFAULT 0 COMMENT '是否审核通过';
ALTER TABLE credit_mp_customer ADD COLUMN follow_step5_approved_at VARCHAR(255) COMMENT '审核时间';
ALTER TABLE credit_mp_customer ADD COLUMN follow_step5_approved_by BIGINT COMMENT '审核人ID';
-- 第6步订单回款
ALTER TABLE credit_mp_customer ADD COLUMN follow_step6_submitted TINYINT DEFAULT 0 COMMENT '是否已提交';
ALTER TABLE credit_mp_customer ADD COLUMN follow_step6_submitted_at VARCHAR(255) COMMENT '提交时间';
ALTER TABLE credit_mp_customer ADD COLUMN follow_step6_payment_records TEXT COMMENT '财务录入的回款记录JSON数组';
ALTER TABLE credit_mp_customer ADD COLUMN follow_step6_expected_payments TEXT COMMENT '预计回款JSON数组';
ALTER TABLE credit_mp_customer ADD COLUMN follow_step6_need_approval TINYINT DEFAULT 1 COMMENT '是否需要审核';
ALTER TABLE credit_mp_customer ADD COLUMN follow_step6_approved TINYINT DEFAULT 0 COMMENT '是否审核通过';
ALTER TABLE credit_mp_customer ADD COLUMN follow_step6_approved_at VARCHAR(255) COMMENT '审核时间';
ALTER TABLE credit_mp_customer ADD COLUMN follow_step6_approved_by BIGINT COMMENT '审核人ID';
-- 第7步电话回访
ALTER TABLE credit_mp_customer ADD COLUMN follow_step7_submitted TINYINT DEFAULT 0 COMMENT '是否已提交';
ALTER TABLE credit_mp_customer ADD COLUMN follow_step7_submitted_at VARCHAR(255) COMMENT '提交时间';
ALTER TABLE credit_mp_customer ADD COLUMN follow_step7_visit_records TEXT COMMENT '回访记录JSON数组';
ALTER TABLE credit_mp_customer ADD COLUMN follow_step7_need_approval TINYINT DEFAULT 1 COMMENT '是否需要审核';
ALTER TABLE credit_mp_customer ADD COLUMN follow_step7_approved TINYINT DEFAULT 0 COMMENT '是否审核通过';
ALTER TABLE credit_mp_customer ADD COLUMN follow_step7_approved_at VARCHAR(255) COMMENT '审核时间';
ALTER TABLE credit_mp_customer ADD COLUMN follow_step7_approved_by BIGINT COMMENT '审核人ID';
-- 添加流程结束相关字段
ALTER TABLE credit_mp_customer ADD COLUMN follow_process_ended TINYINT DEFAULT 0 COMMENT '流程是否已结束';
ALTER TABLE credit_mp_customer ADD COLUMN follow_process_end_time VARCHAR(255) COMMENT '流程结束时间';
ALTER TABLE credit_mp_customer ADD COLUMN follow_process_end_reason TEXT COMMENT '流程结束原因';
```
### 2. 创建审核记录表(可选)
```sql
CREATE TABLE credit_follow_approval (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
customer_id BIGINT NOT NULL COMMENT '客户ID',
step TINYINT NOT NULL COMMENT '步骤号',
approved TINYINT NOT NULL COMMENT '是否通过',
remark TEXT COMMENT '审核备注',
approved_by BIGINT COMMENT '审核人ID',
approved_at VARCHAR(255) COMMENT '审核时间',
created_at VARCHAR(255) COMMENT '创建时间',
INDEX idx_customer_step (customer_id, step),
INDEX idx_approved_by (approved_by)
) COMMENT='跟进步骤审核记录';
```
## ☕ Java后端实现
### 1. 实体类修改
```java
// CreditMpCustomer.java 添加字段
public class CreditMpCustomer {
// ... 现有字段
// 第5步字段
private Integer followStep5Submitted;
private String followStep5SubmittedAt;
private String followStep5Contracts;
private Integer followStep5NeedApproval;
private Integer followStep5Approved;
private String followStep5ApprovedAt;
private Long followStep5ApprovedBy;
// 第6步字段
private Integer followStep6Submitted;
private String followStep6SubmittedAt;
private String followStep6PaymentRecords;
private String followStep6ExpectedPayments;
private Integer followStep6NeedApproval;
private Integer followStep6Approved;
private String followStep6ApprovedAt;
private Long followStep6ApprovedBy;
// 第7步字段
private Integer followStep7Submitted;
private String followStep7SubmittedAt;
private String followStep7VisitRecords;
private Integer followStep7NeedApproval;
private Integer followStep7Approved;
private String followStep7ApprovedAt;
private Long followStep7ApprovedBy;
// 流程结束字段
private Integer followProcessEnded;
private String followProcessEndTime;
private String followProcessEndReason;
// getter/setter 方法...
}
```
### 2. DTO类创建
```java
// FollowStepApprovalDTO.java
@Data
public class FollowStepApprovalDTO {
private Long customerId;
private Integer step;
private Boolean approved;
private String remark;
}
// BatchFollowStepApprovalDTO.java
@Data
public class BatchFollowStepApprovalDTO {
private List<FollowStepApprovalDTO> approvals;
}
// FollowStatisticsDTO.java
@Data
public class FollowStatisticsDTO {
private Integer totalSteps;
private Integer completedSteps;
private Integer currentStep;
private Double progress;
private List<FollowStepDetailDTO> stepDetails;
}
// FollowStepDetailDTO.java
@Data
public class FollowStepDetailDTO {
private Integer step;
private String title;
private String status; // pending, submitted, approved, rejected
private String submittedAt;
private String approvedAt;
}
```
### 3. Service层实现
```java
// CreditMpCustomerServiceImpl.java 添加方法
@Service
public class CreditMpCustomerServiceImpl implements CreditMpCustomerService {
/**
* 审核跟进步骤
*/
@Override
@Transactional
public void approveFollowStep(FollowStepApprovalDTO dto) {
CreditMpCustomer customer = getById(dto.getCustomerId());
if (customer == null) {
throw new ServiceException("客户不存在");
}
// 验证步骤是否已提交
if (!isStepSubmitted(customer, dto.getStep())) {
throw new ServiceException("该步骤尚未提交,无法审核");
}
// 更新审核状态
updateStepApproval(customer, dto);
// 记录审核日志
saveApprovalLog(dto);
// 如果审核通过,更新客户步骤状态
if (dto.getApproved()) {
updateCustomerStep(customer, dto.getStep());
}
}
/**
* 批量审核跟进步骤
*/
@Override
@Transactional
public void batchApproveFollowSteps(BatchFollowStepApprovalDTO dto) {
for (FollowStepApprovalDTO approval : dto.getApprovals()) {
approveFollowStep(approval);
}
}
/**
* 获取待审核的跟进步骤列表
*/
@Override
public List<PendingApprovalStepVO> getPendingApprovalSteps(FollowStepQueryDTO query) {
return baseMapper.selectPendingApprovalSteps(query);
}
/**
* 获取客户跟进统计
*/
@Override
public FollowStatisticsDTO getFollowStatistics(Long customerId) {
CreditMpCustomer customer = getById(customerId);
if (customer == null) {
throw new ServiceException("客户不存在");
}
FollowStatisticsDTO statistics = new FollowStatisticsDTO();
statistics.setTotalSteps(7);
List<FollowStepDetailDTO> stepDetails = new ArrayList<>();
int completedSteps = 0;
int currentStep = 1;
for (int i = 1; i <= 7; i++) {
FollowStepDetailDTO detail = getStepDetail(customer, i);
stepDetails.add(detail);
if ("approved".equals(detail.getStatus())) {
completedSteps++;
currentStep = i + 1;
} else if ("submitted".equals(detail.getStatus()) && currentStep == 1) {
currentStep = i;
}
}
statistics.setCompletedSteps(completedSteps);
statistics.setCurrentStep(Math.min(currentStep, 7));
statistics.setProgress((double) completedSteps / 7 * 100);
statistics.setStepDetails(stepDetails);
return statistics;
}
/**
* 结束客户跟进流程
*/
@Override
@Transactional
public void endFollowProcess(Long customerId, String reason) {
CreditMpCustomer customer = getById(customerId);
if (customer == null) {
throw new ServiceException("客户不存在");
}
customer.setFollowProcessEnded(1);
customer.setFollowProcessEndTime(DateUtil.formatDateTime(new Date()));
customer.setFollowProcessEndReason(reason);
updateById(customer);
}
// 私有辅助方法...
private boolean isStepSubmitted(CreditMpCustomer customer, Integer step) {
switch (step) {
case 1: return customer.getFollowStep1Submitted() == 1;
case 2: return customer.getFollowStep2Submitted() == 1;
// ... 其他步骤
case 7: return customer.getFollowStep7Submitted() == 1;
default: return false;
}
}
private void updateStepApproval(CreditMpCustomer customer, FollowStepApprovalDTO dto) {
String currentTime = DateUtil.formatDateTime(new Date());
Long currentUserId = SecurityFrameworkUtils.getLoginUserId();
switch (dto.getStep()) {
case 5:
customer.setFollowStep5Approved(dto.getApproved() ? 1 : 0);
customer.setFollowStep5ApprovedAt(currentTime);
customer.setFollowStep5ApprovedBy(currentUserId);
break;
case 6:
customer.setFollowStep6Approved(dto.getApproved() ? 1 : 0);
customer.setFollowStep6ApprovedAt(currentTime);
customer.setFollowStep6ApprovedBy(currentUserId);
break;
case 7:
customer.setFollowStep7Approved(dto.getApproved() ? 1 : 0);
customer.setFollowStep7ApprovedAt(currentTime);
customer.setFollowStep7ApprovedBy(currentUserId);
break;
}
updateById(customer);
}
}
```
### 4. Controller层实现
```java
// CreditMpCustomerController.java 添加接口
@RestController
@RequestMapping("/credit/credit-mp-customer")
public class CreditMpCustomerController {
@PostMapping("/approve-follow-step")
@OperLog(title = "审核跟进步骤", businessType = BusinessType.UPDATE)
public R<Void> approveFollowStep(@RequestBody FollowStepApprovalDTO dto) {
creditMpCustomerService.approveFollowStep(dto);
return R.ok();
}
@PostMapping("/batch-approve-follow-steps")
@OperLog(title = "批量审核跟进步骤", businessType = BusinessType.UPDATE)
public R<Void> batchApproveFollowSteps(@RequestBody BatchFollowStepApprovalDTO dto) {
creditMpCustomerService.batchApproveFollowSteps(dto);
return R.ok();
}
@GetMapping("/pending-approval-steps")
@OperLog(title = "获取待审核跟进步骤", businessType = BusinessType.SELECT)
public R<List<PendingApprovalStepVO>> getPendingApprovalSteps(FollowStepQueryDTO query) {
List<PendingApprovalStepVO> list = creditMpCustomerService.getPendingApprovalSteps(query);
return R.ok(list);
}
@GetMapping("/follow-statistics/{customerId}")
@OperLog(title = "获取客户跟进统计", businessType = BusinessType.SELECT)
public R<FollowStatisticsDTO> getFollowStatistics(@PathVariable Long customerId) {
FollowStatisticsDTO statistics = creditMpCustomerService.getFollowStatistics(customerId);
return R.ok(statistics);
}
@PostMapping("/end-follow-process")
@OperLog(title = "结束客户跟进流程", businessType = BusinessType.UPDATE)
public R<Void> endFollowProcess(@RequestBody EndFollowProcessDTO dto) {
creditMpCustomerService.endFollowProcess(dto.getCustomerId(), dto.getReason());
return R.ok();
}
}
```
### 5. Mapper层SQL
```xml
<!-- CreditMpCustomerMapper.xml 添加查询方法 -->
<select id="selectPendingApprovalSteps" resultType="com.your.package.PendingApprovalStepVO">
SELECT
c.id as customerId,
c.to_user as customerName,
5 as step,
'合同签订' as stepTitle,
c.follow_step5_submitted_at as submittedAt,
u.real_name as submittedBy,
c.follow_step5_contracts as content
FROM credit_mp_customer c
LEFT JOIN sys_user u ON c.user_id = u.user_id
WHERE c.follow_step5_submitted = 1
AND c.follow_step5_approved = 0
AND c.deleted = 0
UNION ALL
SELECT
c.id as customerId,
c.to_user as customerName,
6 as step,
'订单回款' as stepTitle,
c.follow_step6_submitted_at as submittedAt,
u.real_name as submittedBy,
c.follow_step6_expected_payments as content
FROM credit_mp_customer c
LEFT JOIN sys_user u ON c.user_id = u.user_id
WHERE c.follow_step6_submitted = 1
AND c.follow_step6_approved = 0
AND c.deleted = 0
UNION ALL
SELECT
c.id as customerId,
c.to_user as customerName,
7 as step,
'电话回访' as stepTitle,
c.follow_step7_submitted_at as submittedAt,
u.real_name as submittedBy,
c.follow_step7_visit_records as content
FROM credit_mp_customer c
LEFT JOIN sys_user u ON c.user_id = u.user_id
WHERE c.follow_step7_submitted = 1
AND c.follow_step7_approved = 0
AND c.deleted = 0
<if test="step != null">
HAVING step = #{step}
</if>
<if test="customerId != null">
HAVING customerId = #{customerId}
</if>
ORDER BY submittedAt DESC
</select>
```
## 🔧 业务逻辑说明
### 1. 步骤解锁机制
- 第一步始终可用
- 后续步骤需要前一步审核通过才能进行
- 前端通过 `canEnterStep` 逻辑控制
### 2. 审核流程
- 步骤提交后设置 `needApproval = 1`
- 管理员在后台审核
- 审核通过后设置 `approved = 1` 并更新时间
### 3. 数据格式
- 所有复杂数据使用JSON格式存储
- 文件上传返回URL存储在JSON数组中
- 时间统一使用 `YYYY-MM-DD HH:mm:ss` 格式
### 4. 权限控制
- 销售只能提交和查看自己的客户
- 管理员可以审核所有步骤
- 财务人员可以录入第6步回款数据
## 📱 前端集成
前端代码已经完成,包括:
- 7个步骤的完整页面
- 步骤状态显示和跳转逻辑
- 数据提交和验证
- 客户详情页面的汇总显示
## 🚀 部署步骤
1. 执行数据库迁移脚本
2. 部署Java后端代码
3. 更新前端API调用
4. 测试完整流程
5. 配置权限和审核流程
## 📝 注意事项
1. **数据备份**:执行数据库变更前请备份
2. **权限配置**:确保各角色权限正确配置
3. **文件上传**:确认文件上传服务正常
4. **审核流程**:测试审核流程的完整性
5. **性能优化**:大量数据时考虑分页和索引优化
## 🔄 后续扩展
可以考虑的功能:
- 跟进模板和标准化流程
- 自动提醒和通知
- 数据统计和报表
- 跟进效率分析
- 客户满意度评估

View File

@@ -4,6 +4,12 @@ import com.gxwebsoft.common.core.web.BaseController;
import com.gxwebsoft.credit.service.CreditMpCustomerService; import com.gxwebsoft.credit.service.CreditMpCustomerService;
import com.gxwebsoft.credit.entity.CreditMpCustomer; import com.gxwebsoft.credit.entity.CreditMpCustomer;
import com.gxwebsoft.credit.param.CreditMpCustomerParam; import com.gxwebsoft.credit.param.CreditMpCustomerParam;
import com.gxwebsoft.credit.param.BatchFollowStepApprovalDTO;
import com.gxwebsoft.credit.param.EndFollowProcessDTO;
import com.gxwebsoft.credit.param.FollowStepApprovalDTO;
import com.gxwebsoft.credit.param.FollowStepQueryDTO;
import com.gxwebsoft.credit.vo.FollowStatisticsDTO;
import com.gxwebsoft.credit.vo.PendingApprovalStepVO;
import com.gxwebsoft.common.core.web.ApiResult; import com.gxwebsoft.common.core.web.ApiResult;
import com.gxwebsoft.common.core.web.PageResult; import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.common.core.web.PageParam; import com.gxwebsoft.common.core.web.PageParam;
@@ -126,4 +132,47 @@ public class CreditMpCustomerController extends BaseController {
return fail("删除失败"); return fail("删除失败");
} }
@PreAuthorize("hasAuthority('credit:creditMpCustomer:update')")
@OperationLog
@Operation(summary = "审核跟进步骤")
@PostMapping("/approve-follow-step")
public ApiResult<?> approveFollowStep(@RequestBody FollowStepApprovalDTO dto) {
creditMpCustomerService.approveFollowStep(dto);
return success("审核成功");
}
@PreAuthorize("hasAuthority('credit:creditMpCustomer:update')")
@OperationLog
@Operation(summary = "批量审核跟进步骤")
@PostMapping("/batch-approve-follow-steps")
public ApiResult<?> batchApproveFollowSteps(@RequestBody BatchFollowStepApprovalDTO dto) {
creditMpCustomerService.batchApproveFollowSteps(dto);
return success("批量审核成功");
}
@PreAuthorize("hasAuthority('credit:creditMpCustomer:list')")
@Operation(summary = "获取待审核的跟进步骤")
@GetMapping("/pending-approval-steps")
public ApiResult<List<PendingApprovalStepVO>> getPendingApprovalSteps(FollowStepQueryDTO query) {
List<PendingApprovalStepVO> list = creditMpCustomerService.getPendingApprovalSteps(query);
return success(list);
}
@PreAuthorize("hasAuthority('credit:creditMpCustomer:list')")
@Operation(summary = "获取客户跟进统计")
@GetMapping("/follow-statistics/{customerId}")
public ApiResult<FollowStatisticsDTO> getFollowStatistics(@PathVariable Long customerId) {
FollowStatisticsDTO statistics = creditMpCustomerService.getFollowStatistics(customerId);
return success(statistics);
}
@PreAuthorize("hasAuthority('credit:creditMpCustomer:update')")
@OperationLog
@Operation(summary = "结束客户跟进流程")
@PostMapping("/end-follow-process")
public ApiResult<?> endFollowProcess(@RequestBody EndFollowProcessDTO dto) {
creditMpCustomerService.endFollowProcess(dto);
return success("流程结束成功");
}
} }

View File

@@ -110,4 +110,159 @@ public class CreditMpCustomer implements Serializable {
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime; private LocalDateTime updateTime;
// 第1步案件受理字段
@Schema(description = "第1步是否已提交")
private Integer followStep1Submitted;
@Schema(description = "第1步提交时间")
private String followStep1SubmittedAt;
@Schema(description = "第1步是否需要审核")
private Integer followStep1NeedApproval;
@Schema(description = "第1步是否审核通过")
private Integer followStep1Approved;
@Schema(description = "第1步审核时间")
private String followStep1ApprovedAt;
@Schema(description = "第1步审核人ID")
private Long followStep1ApprovedBy;
// 第2步材料准备字段
@Schema(description = "第2步是否已提交")
private Integer followStep2Submitted;
@Schema(description = "第2步提交时间")
private String followStep2SubmittedAt;
@Schema(description = "第2步是否需要审核")
private Integer followStep2NeedApproval;
@Schema(description = "第2步是否审核通过")
private Integer followStep2Approved;
@Schema(description = "第2步审核时间")
private String followStep2ApprovedAt;
@Schema(description = "第2步审核人ID")
private Long followStep2ApprovedBy;
// 第3步案件办理字段
@Schema(description = "第3步是否已提交")
private Integer followStep3Submitted;
@Schema(description = "第3步提交时间")
private String followStep3SubmittedAt;
@Schema(description = "第3步是否需要审核")
private Integer followStep3NeedApproval;
@Schema(description = "第3步是否审核通过")
private Integer followStep3Approved;
@Schema(description = "第3步审核时间")
private String followStep3ApprovedAt;
@Schema(description = "第3步审核人ID")
private Long followStep3ApprovedBy;
// 第4步送达签收字段
@Schema(description = "第4步是否已提交")
private Integer followStep4Submitted;
@Schema(description = "第4步提交时间")
private String followStep4SubmittedAt;
@Schema(description = "第4步是否需要审核")
private Integer followStep4NeedApproval;
@Schema(description = "第4步是否审核通过")
private Integer followStep4Approved;
@Schema(description = "第4步审核时间")
private String followStep4ApprovedAt;
@Schema(description = "第4步审核人ID")
private Long followStep4ApprovedBy;
// 第5步合同签订字段
@Schema(description = "第5步是否已提交")
private Integer followStep5Submitted;
@Schema(description = "第5步提交时间")
private String followStep5SubmittedAt;
@Schema(description = "第5步合同信息JSON数组")
private String followStep5Contracts;
@Schema(description = "第5步是否需要审核")
private Integer followStep5NeedApproval;
@Schema(description = "第5步是否审核通过")
private Integer followStep5Approved;
@Schema(description = "第5步审核时间")
private String followStep5ApprovedAt;
@Schema(description = "第5步审核人ID")
private Long followStep5ApprovedBy;
// 第6步订单回款字段
@Schema(description = "第6步是否已提交")
private Integer followStep6Submitted;
@Schema(description = "第6步提交时间")
private String followStep6SubmittedAt;
@Schema(description = "第6步财务录入的回款记录JSON数组")
private String followStep6PaymentRecords;
@Schema(description = "第6步预计回款JSON数组")
private String followStep6ExpectedPayments;
@Schema(description = "第6步是否需要审核")
private Integer followStep6NeedApproval;
@Schema(description = "第6步是否审核通过")
private Integer followStep6Approved;
@Schema(description = "第6步审核时间")
private String followStep6ApprovedAt;
@Schema(description = "第6步审核人ID")
private Long followStep6ApprovedBy;
// 第7步电话回访字段
@Schema(description = "第7步是否已提交")
private Integer followStep7Submitted;
@Schema(description = "第7步提交时间")
private String followStep7SubmittedAt;
@Schema(description = "第7步回访记录JSON数组")
private String followStep7VisitRecords;
@Schema(description = "第7步是否需要审核")
private Integer followStep7NeedApproval;
@Schema(description = "第7步是否审核通过")
private Integer followStep7Approved;
@Schema(description = "第7步审核时间")
private String followStep7ApprovedAt;
@Schema(description = "第7步审核人ID")
private Long followStep7ApprovedBy;
// 流程结束字段
@Schema(description = "流程是否已结束")
private Integer followProcessEnded;
@Schema(description = "流程结束时间")
private String followProcessEndTime;
@Schema(description = "流程结束原因")
private String followProcessEndReason;
} }

View File

@@ -4,6 +4,8 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.gxwebsoft.credit.entity.CreditMpCustomer; import com.gxwebsoft.credit.entity.CreditMpCustomer;
import com.gxwebsoft.credit.param.CreditMpCustomerParam; import com.gxwebsoft.credit.param.CreditMpCustomerParam;
import com.gxwebsoft.credit.param.FollowStepQueryDTO;
import com.gxwebsoft.credit.vo.PendingApprovalStepVO;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import java.util.List; import java.util.List;
@@ -34,4 +36,12 @@ public interface CreditMpCustomerMapper extends BaseMapper<CreditMpCustomer> {
*/ */
List<CreditMpCustomer> selectListRel(@Param("param") CreditMpCustomerParam param); List<CreditMpCustomer> selectListRel(@Param("param") CreditMpCustomerParam param);
/**
* 获取待审核的跟进步骤列表
*
* @param query 查询参数
* @return 待审核步骤列表
*/
List<PendingApprovalStepVO> selectPendingApprovalSteps(@Param("param") FollowStepQueryDTO query);
} }

View File

@@ -92,4 +92,128 @@
<include refid="selectSql"></include> <include refid="selectSql"></include>
</select> </select>
<!-- 获取待审核的跟进步骤列表 -->
<select id="selectPendingApprovalSteps" resultType="com.gxwebsoft.credit.vo.PendingApprovalStepVO">
SELECT
c.id as customerId,
c.to_user as customerName,
1 as step,
'案件受理' as stepTitle,
c.follow_step1_submitted_at as submittedAt,
u.real_name as submittedBy,
c.comments as content
FROM credit_mp_customer c
LEFT JOIN sys_user u ON c.user_id = u.user_id
WHERE c.follow_step1_submitted = 1
AND c.follow_step1_approved = 0
AND c.deleted = 0
UNION ALL
SELECT
c.id as customerId,
c.to_user as customerName,
2 as step,
'材料准备' as stepTitle,
c.follow_step2_submitted_at as submittedAt,
u.real_name as submittedBy,
c.comments as content
FROM credit_mp_customer c
LEFT JOIN sys_user u ON c.user_id = u.user_id
WHERE c.follow_step2_submitted = 1
AND c.follow_step2_approved = 0
AND c.deleted = 0
UNION ALL
SELECT
c.id as customerId,
c.to_user as customerName,
3 as step,
'案件办理' as stepTitle,
c.follow_step3_submitted_at as submittedAt,
u.real_name as submittedBy,
c.comments as content
FROM credit_mp_customer c
LEFT JOIN sys_user u ON c.user_id = u.user_id
WHERE c.follow_step3_submitted = 1
AND c.follow_step3_approved = 0
AND c.deleted = 0
UNION ALL
SELECT
c.id as customerId,
c.to_user as customerName,
4 as step,
'送达签收' as stepTitle,
c.follow_step4_submitted_at as submittedAt,
u.real_name as submittedBy,
c.comments as content
FROM credit_mp_customer c
LEFT JOIN sys_user u ON c.user_id = u.user_id
WHERE c.follow_step4_submitted = 1
AND c.follow_step4_approved = 0
AND c.deleted = 0
UNION ALL
SELECT
c.id as customerId,
c.to_user as customerName,
5 as step,
'合同签订' as stepTitle,
c.follow_step5_submitted_at as submittedAt,
u.real_name as submittedBy,
c.follow_step5_contracts as content
FROM credit_mp_customer c
LEFT JOIN sys_user u ON c.user_id = u.user_id
WHERE c.follow_step5_submitted = 1
AND c.follow_step5_approved = 0
AND c.deleted = 0
UNION ALL
SELECT
c.id as customerId,
c.to_user as customerName,
6 as step,
'订单回款' as stepTitle,
c.follow_step6_submitted_at as submittedAt,
u.real_name as submittedBy,
c.follow_step6_expected_payments as content
FROM credit_mp_customer c
LEFT JOIN sys_user u ON c.user_id = u.user_id
WHERE c.follow_step6_submitted = 1
AND c.follow_step6_approved = 0
AND c.deleted = 0
UNION ALL
SELECT
c.id as customerId,
c.to_user as customerName,
7 as step,
'电话回访' as stepTitle,
c.follow_step7_submitted_at as submittedAt,
u.real_name as submittedBy,
c.follow_step7_visit_records as content
FROM credit_mp_customer c
LEFT JOIN sys_user u ON c.user_id = u.user_id
WHERE c.follow_step7_submitted = 1
AND c.follow_step7_approved = 0
AND c.deleted = 0
<if test="param.step != null">
HAVING step = #{param.step}
</if>
<if test="param.customerId != null">
HAVING customerId = #{param.customerId}
</if>
<if test="param.userId != null">
HAVING customerId IN (SELECT id FROM credit_mp_customer WHERE user_id = #{param.userId})
</if>
ORDER BY submittedAt DESC
</select>
</mapper> </mapper>

View File

@@ -0,0 +1,24 @@
package com.gxwebsoft.credit.param;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
* 批量跟进步骤审核DTO
*
* @author 科技小王子
* @since 2026-03-22
*/
@Data
@Schema(name = "BatchFollowStepApprovalDTO对象", description = "批量跟进步骤审核DTO")
public class BatchFollowStepApprovalDTO {
@Schema(description = "审核列表", required = true)
@NotNull(message = "审核列表不能为空")
@Valid
private List<FollowStepApprovalDTO> approvals;
}

View File

@@ -0,0 +1,24 @@
package com.gxwebsoft.credit.param;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* 结束跟进流程DTO
*
* @author 科技小王子
* @since 2026-03-22
*/
@Data
@Schema(name = "EndFollowProcessDTO对象", description = "结束跟进流程DTO")
public class EndFollowProcessDTO {
@Schema(description = "客户ID", required = true)
@NotNull(message = "客户ID不能为空")
private Long customerId;
@Schema(description = "结束原因")
private String reason;
}

View File

@@ -0,0 +1,36 @@
package com.gxwebsoft.credit.param;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Min;
import javax.validation.constraints.Max;
/**
* 跟进步骤审核DTO
*
* @author 科技小王子
* @since 2026-03-22
*/
@Data
@Schema(name = "FollowStepApprovalDTO对象", description = "跟进步骤审核DTO")
public class FollowStepApprovalDTO {
@Schema(description = "客户ID", required = true)
@NotNull(message = "客户ID不能为空")
private Long customerId;
@Schema(description = "步骤号", required = true, example = "5")
@NotNull(message = "步骤号不能为空")
@Min(value = 1, message = "步骤号最小为1")
@Max(value = 7, message = "步骤号最大为7")
private Integer step;
@Schema(description = "是否审核通过", required = true)
@NotNull(message = "审核状态不能为空")
private Boolean approved;
@Schema(description = "审核备注")
private String remark;
}

View File

@@ -0,0 +1,29 @@
package com.gxwebsoft.credit.param;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.Min;
import javax.validation.constraints.Max;
/**
* 跟进步骤查询DTO
*
* @author 科技小王子
* @since 2026-03-22
*/
@Data
@Schema(name = "FollowStepQueryDTO对象", description = "跟进步骤查询DTO")
public class FollowStepQueryDTO {
@Schema(description = "步骤号", example = "5")
@Min(value = 1, message = "步骤号最小为1")
@Max(value = 7, message = "步骤号最大为7")
private Integer step;
@Schema(description = "客户ID")
private Long customerId;
@Schema(description = "用户ID")
private Long userId;
}

View File

@@ -4,6 +4,12 @@ import com.baomidou.mybatisplus.extension.service.IService;
import com.gxwebsoft.common.core.web.PageResult; import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.credit.entity.CreditMpCustomer; import com.gxwebsoft.credit.entity.CreditMpCustomer;
import com.gxwebsoft.credit.param.CreditMpCustomerParam; import com.gxwebsoft.credit.param.CreditMpCustomerParam;
import com.gxwebsoft.credit.param.BatchFollowStepApprovalDTO;
import com.gxwebsoft.credit.param.EndFollowProcessDTO;
import com.gxwebsoft.credit.param.FollowStepApprovalDTO;
import com.gxwebsoft.credit.param.FollowStepQueryDTO;
import com.gxwebsoft.credit.vo.FollowStatisticsDTO;
import com.gxwebsoft.credit.vo.PendingApprovalStepVO;
import java.util.List; import java.util.List;
@@ -39,4 +45,41 @@ public interface CreditMpCustomerService extends IService<CreditMpCustomer> {
*/ */
CreditMpCustomer getByIdRel(Integer id); CreditMpCustomer getByIdRel(Integer id);
/**
* 审核跟进步骤
*
* @param dto 审核参数
*/
void approveFollowStep(FollowStepApprovalDTO dto);
/**
* 批量审核跟进步骤
*
* @param dto 批量审核参数
*/
void batchApproveFollowSteps(BatchFollowStepApprovalDTO dto);
/**
* 获取待审核的跟进步骤列表
*
* @param query 查询参数
* @return 待审核步骤列表
*/
List<PendingApprovalStepVO> getPendingApprovalSteps(FollowStepQueryDTO query);
/**
* 获取客户跟进统计
*
* @param customerId 客户ID
* @return 跟进统计信息
*/
FollowStatisticsDTO getFollowStatistics(Long customerId);
/**
* 结束客户跟进流程
*
* @param dto 结束流程参数
*/
void endFollowProcess(EndFollowProcessDTO dto);
} }

View File

@@ -1,14 +1,27 @@
package com.gxwebsoft.credit.service.impl; package com.gxwebsoft.credit.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gxwebsoft.common.core.exception.BusinessException;
import com.gxwebsoft.common.core.web.BaseController;
import com.gxwebsoft.credit.mapper.CreditMpCustomerMapper; import com.gxwebsoft.credit.mapper.CreditMpCustomerMapper;
import com.gxwebsoft.credit.service.CreditMpCustomerService; import com.gxwebsoft.credit.service.CreditMpCustomerService;
import com.gxwebsoft.credit.entity.CreditMpCustomer; import com.gxwebsoft.credit.entity.CreditMpCustomer;
import com.gxwebsoft.credit.param.CreditMpCustomerParam; import com.gxwebsoft.credit.param.CreditMpCustomerParam;
import com.gxwebsoft.credit.param.BatchFollowStepApprovalDTO;
import com.gxwebsoft.credit.param.EndFollowProcessDTO;
import com.gxwebsoft.credit.param.FollowStepApprovalDTO;
import com.gxwebsoft.credit.param.FollowStepQueryDTO;
import com.gxwebsoft.credit.vo.FollowStatisticsDTO;
import com.gxwebsoft.credit.vo.FollowStepDetailDTO;
import com.gxwebsoft.credit.vo.PendingApprovalStepVO;
import com.gxwebsoft.common.core.web.PageParam; import com.gxwebsoft.common.core.web.PageParam;
import com.gxwebsoft.common.core.web.PageResult; import com.gxwebsoft.common.core.web.PageResult;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
@@ -20,6 +33,8 @@ import java.util.List;
@Service @Service
public class CreditMpCustomerServiceImpl extends ServiceImpl<CreditMpCustomerMapper, CreditMpCustomer> implements CreditMpCustomerService { public class CreditMpCustomerServiceImpl extends ServiceImpl<CreditMpCustomerMapper, CreditMpCustomer> implements CreditMpCustomerService {
private final BaseController baseController = new BaseController();
@Override @Override
public PageResult<CreditMpCustomer> pageRel(CreditMpCustomerParam param) { public PageResult<CreditMpCustomer> pageRel(CreditMpCustomerParam param) {
PageParam<CreditMpCustomer, CreditMpCustomerParam> page = new PageParam<>(param); PageParam<CreditMpCustomer, CreditMpCustomerParam> page = new PageParam<>(param);
@@ -44,4 +59,201 @@ public class CreditMpCustomerServiceImpl extends ServiceImpl<CreditMpCustomerMap
return param.getOne(baseMapper.selectListRel(param)); return param.getOne(baseMapper.selectListRel(param));
} }
@Override
@Transactional
public void approveFollowStep(FollowStepApprovalDTO dto) {
CreditMpCustomer customer = getById(dto.getCustomerId());
if (customer == null) {
throw new BusinessException("客户不存在");
}
// 验证步骤是否已提交
if (!isStepSubmitted(customer, dto.getStep())) {
throw new BusinessException("该步骤尚未提交,无法审核");
}
// 更新审核状态
updateStepApproval(customer, dto);
// 如果审核通过,更新客户步骤状态
if (dto.getApproved()) {
updateCustomerStep(customer, dto.getStep());
}
}
@Override
@Transactional
public void batchApproveFollowSteps(BatchFollowStepApprovalDTO dto) {
for (FollowStepApprovalDTO approval : dto.getApprovals()) {
approveFollowStep(approval);
}
}
@Override
public List<PendingApprovalStepVO> getPendingApprovalSteps(FollowStepQueryDTO query) {
return baseMapper.selectPendingApprovalSteps(query);
}
@Override
public FollowStatisticsDTO getFollowStatistics(Long customerId) {
CreditMpCustomer customer = getById(customerId);
if (customer == null) {
throw new BusinessException("客户不存在");
}
FollowStatisticsDTO statistics = new FollowStatisticsDTO();
statistics.setTotalSteps(7);
List<FollowStepDetailDTO> stepDetails = new ArrayList<>();
int completedSteps = 0;
int currentStep = 1;
for (int i = 1; i <= 7; i++) {
FollowStepDetailDTO detail = getStepDetail(customer, i);
stepDetails.add(detail);
if ("approved".equals(detail.getStatus())) {
completedSteps++;
currentStep = i + 1;
} else if ("submitted".equals(detail.getStatus()) && currentStep == 1) {
currentStep = i;
}
}
statistics.setCompletedSteps(completedSteps);
statistics.setCurrentStep(Math.min(currentStep, 7));
statistics.setProgress((double) completedSteps / 7 * 100);
statistics.setStepDetails(stepDetails);
return statistics;
}
@Override
@Transactional
public void endFollowProcess(EndFollowProcessDTO dto) {
CreditMpCustomer customer = getById(dto.getCustomerId());
if (customer == null) {
throw new BusinessException("客户不存在");
}
customer.setFollowProcessEnded(1);
customer.setFollowProcessEndTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
customer.setFollowProcessEndReason(dto.getReason());
updateById(customer);
}
// 私有辅助方法
private boolean isStepSubmitted(CreditMpCustomer customer, Integer step) {
switch (step) {
case 1: return customer.getFollowStep1Submitted() != null && customer.getFollowStep1Submitted() == 1;
case 2: return customer.getFollowStep2Submitted() != null && customer.getFollowStep2Submitted() == 1;
case 3: return customer.getFollowStep3Submitted() != null && customer.getFollowStep3Submitted() == 1;
case 4: return customer.getFollowStep4Submitted() != null && customer.getFollowStep4Submitted() == 1;
case 5: return customer.getFollowStep5Submitted() != null && customer.getFollowStep5Submitted() == 1;
case 6: return customer.getFollowStep6Submitted() != null && customer.getFollowStep6Submitted() == 1;
case 7: return customer.getFollowStep7Submitted() != null && customer.getFollowStep7Submitted() == 1;
default: return false;
}
}
private void updateStepApproval(CreditMpCustomer customer, FollowStepApprovalDTO dto) {
String currentTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
Integer currentUserId = baseController.getLoginUserId();
switch (dto.getStep()) {
case 5:
customer.setFollowStep5Approved(dto.getApproved() ? 1 : 0);
customer.setFollowStep5ApprovedAt(currentTime);
customer.setFollowStep5ApprovedBy(currentUserId != null ? currentUserId.longValue() : null);
break;
case 6:
customer.setFollowStep6Approved(dto.getApproved() ? 1 : 0);
customer.setFollowStep6ApprovedAt(currentTime);
customer.setFollowStep6ApprovedBy(currentUserId != null ? currentUserId.longValue() : null);
break;
case 7:
customer.setFollowStep7Approved(dto.getApproved() ? 1 : 0);
customer.setFollowStep7ApprovedAt(currentTime);
customer.setFollowStep7ApprovedBy(currentUserId != null ? currentUserId.longValue() : null);
break;
}
updateById(customer);
}
private void updateCustomerStep(CreditMpCustomer customer, Integer step) {
// 更新客户的总体步骤状态
if (step >= customer.getStep()) {
customer.setStep(step + 1);
updateById(customer);
}
}
private FollowStepDetailDTO getStepDetail(CreditMpCustomer customer, Integer step) {
FollowStepDetailDTO detail = new FollowStepDetailDTO();
detail.setStep(step);
// 设置步骤标题
String[] stepTitles = {"", "案件受理", "材料准备", "案件办理", "送达签收", "合同签订", "订单回款", "电话回访"};
detail.setTitle(stepTitles[step]);
// 获取步骤状态
boolean submitted = isStepSubmitted(customer, step);
boolean approved = isStepApproved(customer, step);
if (approved) {
detail.setStatus("approved");
detail.setApprovedAt(getStepApprovedAt(customer, step));
} else if (submitted) {
detail.setStatus("submitted");
detail.setSubmittedAt(getStepSubmittedAt(customer, step));
} else {
detail.setStatus("pending");
}
return detail;
}
private boolean isStepApproved(CreditMpCustomer customer, Integer step) {
switch (step) {
case 1: return customer.getFollowStep1Approved() != null && customer.getFollowStep1Approved() == 1;
case 2: return customer.getFollowStep2Approved() != null && customer.getFollowStep2Approved() == 1;
case 3: return customer.getFollowStep3Approved() != null && customer.getFollowStep3Approved() == 1;
case 4: return customer.getFollowStep4Approved() != null && customer.getFollowStep4Approved() == 1;
case 5: return customer.getFollowStep5Approved() != null && customer.getFollowStep5Approved() == 1;
case 6: return customer.getFollowStep6Approved() != null && customer.getFollowStep6Approved() == 1;
case 7: return customer.getFollowStep7Approved() != null && customer.getFollowStep7Approved() == 1;
default: return false;
}
}
private LocalDateTime getStepSubmittedAt(CreditMpCustomer customer, Integer step) {
String submittedAt = null;
switch (step) {
case 1: submittedAt = customer.getFollowStep1SubmittedAt(); break;
case 2: submittedAt = customer.getFollowStep2SubmittedAt(); break;
case 3: submittedAt = customer.getFollowStep3SubmittedAt(); break;
case 4: submittedAt = customer.getFollowStep4SubmittedAt(); break;
case 5: submittedAt = customer.getFollowStep5SubmittedAt(); break;
case 6: submittedAt = customer.getFollowStep6SubmittedAt(); break;
case 7: submittedAt = customer.getFollowStep7SubmittedAt(); break;
}
return submittedAt != null ? LocalDateTime.parse(submittedAt, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) : null;
}
private LocalDateTime getStepApprovedAt(CreditMpCustomer customer, Integer step) {
String approvedAt = null;
switch (step) {
case 1: approvedAt = customer.getFollowStep1ApprovedAt(); break;
case 2: approvedAt = customer.getFollowStep2ApprovedAt(); break;
case 3: approvedAt = customer.getFollowStep3ApprovedAt(); break;
case 4: approvedAt = customer.getFollowStep4ApprovedAt(); break;
case 5: approvedAt = customer.getFollowStep5ApprovedAt(); break;
case 6: approvedAt = customer.getFollowStep6ApprovedAt(); break;
case 7: approvedAt = customer.getFollowStep7ApprovedAt(); break;
}
return approvedAt != null ? LocalDateTime.parse(approvedAt, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) : null;
}
} }

View File

@@ -0,0 +1,32 @@
package com.gxwebsoft.credit.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
/**
* 客户跟进统计DTO
*
* @author 科技小王子
* @since 2026-03-22
*/
@Data
@Schema(name = "FollowStatisticsDTO对象", description = "客户跟进统计DTO")
public class FollowStatisticsDTO {
@Schema(description = "总步骤数")
private Integer totalSteps;
@Schema(description = "已完成步骤数")
private Integer completedSteps;
@Schema(description = "当前步骤")
private Integer currentStep;
@Schema(description = "进度百分比")
private Double progress;
@Schema(description = "步骤详情列表")
private List<FollowStepDetailDTO> stepDetails;
}

View File

@@ -0,0 +1,32 @@
package com.gxwebsoft.credit.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 跟进步骤详情DTO
*
* @author 科技小王子
* @since 2026-03-22
*/
@Data
@Schema(name = "FollowStepDetailDTO对象", description = "跟进步骤详情DTO")
public class FollowStepDetailDTO {
@Schema(description = "步骤号")
private Integer step;
@Schema(description = "步骤标题")
private String title;
@Schema(description = "状态: pending, submitted, approved, rejected")
private String status;
@Schema(description = "提交时间")
private LocalDateTime submittedAt;
@Schema(description = "审核时间")
private LocalDateTime approvedAt;
}

View File

@@ -0,0 +1,38 @@
package com.gxwebsoft.credit.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 待审核跟进步骤VO
*
* @author 科技小王子
* @since 2026-03-22
*/
@Data
@Schema(name = "PendingApprovalStepVO对象", description = "待审核跟进步骤VO")
public class PendingApprovalStepVO {
@Schema(description = "客户ID")
private Long customerId;
@Schema(description = "客户名称")
private String customerName;
@Schema(description = "步骤号")
private Integer step;
@Schema(description = "步骤标题")
private String stepTitle;
@Schema(description = "提交时间")
private LocalDateTime submittedAt;
@Schema(description = "提交人")
private String submittedBy;
@Schema(description = "内容")
private String content;
}