# 客户跟进7步骤后端实现指南

## 📋 概述

本指南详细说明如何实现客户跟进7个步骤功能的后端代码，包括数据库设计、Java后端实现和API接口。

## 🗄️ 数据库设计

### 1. 修改 credit_mp_customer 表结构

```sql
-- 为第5-7步添加字段（第1-4步基础字段已存在；如未包含审核时间/审核人字段，请先补齐 step1-4 的 approved_at/approved_by）
-- 参考：docs/sql/credit_mp_customer_step1_4_approval_columns.sql
-- 第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. **性能优化**：大量数据时考虑分页和索引优化

## 🔄 后续扩展

可以考虑的功能：
- 跟进模板和标准化流程
- 自动提醒和通知
- 数据统计和报表
- 跟进效率分析
- 客户满意度评估
