瀏覽代碼

feat(course): 新增会员营期转移功能

- 在 CustomerTransferApproval 实体中增加转移类型3 的注释说明- FsUserAdminController 中新增会员营期转移相关接口与业务逻辑
- 添加 PeriodUserTransferParam 参数类用于接收转移请求数据- 在 FsUserCompanyUserMapper 中新增根据营期和销售查询会员 ID 的方法
- 实现会员营期转移的完整流程,包括参数校验、数据验证、记录更新及审批创建
- 提供获取营期转移下拉选项和加载指定条件下的会员列表接口
- 支持批量转移会员并记录操作日志与审批状态
xw 3 周之前
父節點
當前提交
3caf93a49b

+ 295 - 1
fs-company/src/main/java/com/fs/user/FsUserAdminController.java

@@ -8,11 +8,20 @@ import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
+import com.fs.common.exception.ServiceException;
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.company.cache.ICompanyUserCacheService;
+import com.fs.company.domain.CompanyUser;
+import com.fs.company.service.ICompanyUserService;
+import com.fs.course.domain.FsUserCompanyUser;
+import com.fs.course.mapper.FsUserCompanyUserMapper;
+import com.fs.course.domain.FsUserCoursePeriod;
 import com.fs.course.dto.BatchSendCourseDTO;
 import com.fs.course.param.FsCourseLinkCreateParam;
+import com.fs.course.param.PeriodUserTransferParam;
+import com.fs.course.service.IFsUserCompanyUserService;
+import com.fs.course.service.IFsUserCoursePeriodService;
 import com.fs.course.service.IFsUserCourseService;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.service.TokenService;
@@ -24,6 +33,7 @@ import com.fs.im.dto.OpenImResponseDTO;
 import com.fs.im.service.OpenIMService;
 import com.fs.qw.domain.CustomerTransferApproval;
 import com.fs.qw.dto.FsUserTransferParamDTO;
+import com.fs.qw.dto.UserProjectDTO;
 import com.fs.qw.service.ICustomerTransferApprovalService;
 import com.fs.store.param.h5.FsUserPageListParam;
 import io.swagger.annotations.Api;
@@ -35,7 +45,11 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
+import java.util.ArrayList;
 import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 import static com.fs.his.utils.PhoneUtil.encryptPhone;
 
@@ -43,7 +57,6 @@ import static com.fs.his.utils.PhoneUtil.encryptPhone;
 @RestController
 @Slf4j
 @RequestMapping("/user/fsUser")
-@AllArgsConstructor
 public class FsUserAdminController extends BaseController {
 
     @Autowired
@@ -52,6 +65,9 @@ public class FsUserAdminController extends BaseController {
     @Autowired
     private ICompanyUserCacheService companyUserCacheService;
 
+    @Autowired
+    private ICompanyUserService companyUserService;
+
     @Autowired
     private TokenService tokenService;
 
@@ -64,6 +80,15 @@ public class FsUserAdminController extends BaseController {
     @Autowired
     private OpenIMService openIMService;
 
+    @Autowired
+    private IFsUserCompanyUserService fsUserCompanyUserService;
+
+    @Autowired
+    private FsUserCompanyUserMapper fsUserCompanyUserMapper;
+
+    @Autowired
+    private IFsUserCoursePeriodService fsUserCoursePeriodService;
+
     @PreAuthorize("@ss.hasPermi('user:fsUser:list')")
     @PostMapping("/list")
     @ApiOperation("会员列表(与移动端使用的相同查询)")
@@ -171,4 +196,273 @@ public class FsUserAdminController extends BaseController {
         return openIMService.batchSendCourse(batchSendCourseDTO);
     }
 
+    /**
+     * 获取会员营期转移的下拉框数据
+     * 包括:当前公司下的营期列表和销售列表
+     *
+     * @return 营期列表和销售列表
+     */
+    @GetMapping("/getPeriodTransferOptions")
+    @ApiOperation("获取会员营期转移下拉框数据")
+    public R getPeriodTransferOptions() {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long companyId = loginUser.getCompany().getCompanyId();
+
+        // 查询当前公司下的营期列表
+        FsUserCoursePeriod periodQuery = new FsUserCoursePeriod();
+        periodQuery.setCompanyId(companyId.toString());
+        List<FsUserCoursePeriod> periodList = fsUserCoursePeriodService.selectFsUserCoursePeriodList(periodQuery);
+
+        // 查询当前公司下的销售列表
+        CompanyUser userQuery = new CompanyUser();
+        userQuery.setCompanyId(companyId);
+        List<CompanyUser> companyUserList = companyUserService.selectCompanyUserList(userQuery);
+
+        return R.ok()
+                .put("periodList", periodList)
+                .put("companyUserList", companyUserList);
+    }
+
+    /**
+     * 加载指定源营期和源销售下的会员列表
+     * 用于会员营期转移功能中选择需要转移的会员
+     *
+     * @param sourcePeriodId 源营期ID
+     * @param sourceCompanyUserId 源销售ID
+     * @return 会员列表
+     */
+    @GetMapping("/loadPeriodUserList")
+    @ApiOperation("加载指定源营期和源销售下的会员列表")
+    public R loadPeriodUserList(@RequestParam Long sourcePeriodId,
+                                @RequestParam Long sourceCompanyUserId) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long companyId = loginUser.getCompany().getCompanyId();
+
+        try {
+            // 1. 验证营期是否存在且属于当前公司
+            FsUserCoursePeriod period = fsUserCoursePeriodService.selectFsUserCoursePeriodById(sourcePeriodId);
+            if (period == null) {
+                return R.error("营期不存在");
+            }
+            if (!period.getCompanyId().contains(companyId.toString())) {
+                return R.error("营期不属于当前公司");
+            }
+
+            // 2. 验证销售是否存在且属于当前公司
+            CompanyUser companyUser = companyUserCacheService.selectCompanyUserById(sourceCompanyUserId);
+            if (companyUser == null) {
+                return R.error("销售不存在");
+            }
+            if (!companyUser.getCompanyId().equals(companyId)) {
+                return R.error("销售不属于当前公司");
+            }
+
+            // 3. 查询该营期和销售下的会员ID列表
+            List<Long> userIds = fsUserCompanyUserMapper
+                    .selectUserIdsByPeriodIdAndCompanyUserId(sourcePeriodId, sourceCompanyUserId);
+
+            log.info("查询营期ID:{}, 销售ID:{} 下的会员数量:{}", 
+                    sourcePeriodId, sourceCompanyUserId, userIds.size());
+
+            // 4. 查询会员详细信息
+            List<Map<String, Object>> userList = new ArrayList<>();
+            for (Long userId : userIds) {
+                FsUser fsUser = fsUserService.selectFsUserById(userId);
+                if (fsUser != null && fsUser.getIsDel() == 0) {
+                    Map<String, Object> userInfo = new HashMap<>();
+                    userInfo.put("userId", fsUser.getUserId());
+                    userInfo.put("nickName", fsUser.getNickName());
+                    userInfo.put("phone", fsUser.getPhone());
+                    userInfo.put("avatar", fsUser.getAvatar());
+                    userInfo.put("createTime", fsUser.getCreateTime());
+                    userList.add(userInfo);
+                }
+            }
+
+            return R.ok()
+                    .put("total", userList.size())
+                    .put("userList", userList);
+
+        } catch (Exception e) {
+            log.error("加载会员列表失败", e);
+            return R.error("加载会员列表失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 会员营期转移
+     * 功能:将从源营期的某个销售下的会员,转移到目标营期的某个销售下
+     * 例如:香江0808某销售下的会员 需要转到 百脳1002期内的某个销售下
+     *
+     * @param param 转移参数
+     * @return 结果
+     */
+    @PreAuthorize("@ss.hasPermi('user:fsUser:periodTransfer')")
+    @Log(title = "会员营期转移", businessType = BusinessType.UPDATE)
+    @PostMapping("/periodTransfer")
+    @ApiOperation("会员营期转移")
+    public R periodTransfer(@RequestBody PeriodUserTransferParam param) {
+        log.info("会员营期转移请求:{}", JSON.toJSONString(param));
+
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long companyId = loginUser.getCompany().getCompanyId();
+        Long currentUserId = loginUser.getUser().getUserId();
+
+        try {
+            // 1. 参数校验
+            if (param.getUserIds() == null || param.getUserIds().isEmpty()) {
+                return R.error("请选择需要转移的会员");
+            }
+
+            // 2. 验证源营期和目标营期是否存在
+            FsUserCoursePeriod sourcePeriod = fsUserCoursePeriodService.selectFsUserCoursePeriodById(param.getSourcePeriodId());
+            if (sourcePeriod == null) {
+                return R.error("源营期不存在");
+            }
+
+            FsUserCoursePeriod targetPeriod = fsUserCoursePeriodService.selectFsUserCoursePeriodById(param.getTargetPeriodId());
+            if (targetPeriod == null) {
+                return R.error("目标营期不存在");
+            }
+
+            // 3. 验证营期归属公司
+            if (!sourcePeriod.getCompanyId().contains(companyId.toString())) {
+                return R.error("源营期不属于当前公司");
+            }
+
+            if (!targetPeriod.getCompanyId().contains(companyId.toString())) {
+                return R.error("目标营期不属于当前公司");
+            }
+
+            // 4. 验证销售是否存在
+            CompanyUser sourceCompanyUser = companyUserCacheService.selectCompanyUserById(param.getSourceCompanyUserId());
+            if (sourceCompanyUser == null) {
+                return R.error("源销售不存在");
+            }
+
+            CompanyUser targetCompanyUser = companyUserCacheService.selectCompanyUserById(param.getTargetCompanyUserId());
+            if (targetCompanyUser == null) {
+                return R.error("目标销售不存在");
+            }
+
+            // 5. 批量转移会员营期关系
+            int successCount = 0;
+            int failCount = 0;
+            StringBuilder failReasons = new StringBuilder();
+
+            // 先查询源营期和源销售下的所有有效会员ID(通过观看记录表关联)
+            List<Long> validUserIds = fsUserCompanyUserMapper
+                    .selectUserIdsByPeriodIdAndCompanyUserId(param.getSourcePeriodId(), param.getSourceCompanyUserId());
+            
+            log.info("源营期ID:{}, 源销售ID:{} 下查询到有效会员数:{}", 
+                    param.getSourcePeriodId(), param.getSourceCompanyUserId(), validUserIds.size());
+
+            for (Long userId : param.getUserIds()) {
+                try {
+                    // 验证会员是否在源营期和源销售下
+                    if (!validUserIds.contains(userId)) {
+                        failReasons.append("会员ID:").append(userId).append("在源营期和源销售下不存在; ");
+                        failCount++;
+                        continue;
+                    }
+
+                    // 先删除该会员的所有fs_user_company_user记录(不管哪个营期)
+                    FsUserCompanyUser deleteParam = new FsUserCompanyUser();
+                    deleteParam.setUserId(userId);
+                    List<FsUserCompanyUser> existingRecords = fsUserCompanyUserService.selectFsUserCompanyUserList(deleteParam);
+                    for (FsUserCompanyUser record : existingRecords) {
+                        fsUserCompanyUserService.deleteFsUserCompanyUserById(record.getId());
+                        log.info("删除旧的fs_user_company_user记录 - 会员ID:{}, 记录ID:{}, 营期ID:{}, 销售ID:{}",
+                                userId, record.getId(), record.getProjectId(), record.getCompanyUserId());
+                    }
+
+                    // 创建新的目标营期记录
+                    fsUserCompanyUserService.bindRelationship(userId, param.getTargetPeriodId(), companyId, param.getTargetCompanyUserId(), 1);
+                    log.info("创建目标营期新记录 - 会员ID:{}, 营期ID:{}, 公司ID:{}, 销售ID:{}",
+                            userId, param.getTargetPeriodId(), companyId, param.getTargetCompanyUserId());
+
+                    // 更新fs_user表的companyId和companyUserId
+                    FsUser fsUser = fsUserService.selectFsUserById(userId);
+                    if (fsUser != null) {
+                        fsUser.setCompanyId(companyId);
+                        fsUser.setCompanyUserId(param.getTargetCompanyUserId());
+                        fsUserService.updateFsUser(fsUser);
+                        log.info("更新fs_user表 - 会员ID:{}, 公司ID:{}, 销售ID:{}",
+                                userId, companyId, param.getTargetCompanyUserId());
+                    }
+
+                    successCount++;
+                    log.info("会员ID:{} 从营期ID:{} 销售ID:{} 转移到 营期ID:{} 销售ID:{} 成功",
+                            userId, param.getSourcePeriodId(), param.getSourceCompanyUserId(),
+                            param.getTargetPeriodId(), param.getTargetCompanyUserId());
+
+                } catch (Exception e) {
+                    log.error("会员ID:{} 转移失败", userId, e);
+                    failReasons.append("会员ID:").append(userId).append("转移失败:").append(e.getMessage()).append("; ");
+                    failCount++;
+                }
+            }
+
+            // 6. 创建审批记录并记录到审批列表
+            List<UserProjectDTO> userProjectList = new ArrayList<>();
+            for (Long userId : param.getUserIds()) {
+                UserProjectDTO dto = new UserProjectDTO();
+                dto.setUserId(userId);
+                dto.setProjectId(param.getTargetPeriodId()); // 使用目标营期ID
+                userProjectList.add(dto);
+            }
+
+            CustomerTransferApproval approval = new CustomerTransferApproval();
+            approval.setCorpId(String.valueOf(companyId));
+            approval.setTransferType(3); // 3=会员营期转移
+            approval.setCustomerIds(JSON.toJSONString(userProjectList));
+            approval.setCustomerList(null); // 避免自动反序列化错误
+            approval.setOriginalUserId(param.getSourceCompanyUserId());
+            approval.setTargetUserId(param.getTargetCompanyUserId());
+            approval.setInitiatorUserId(currentUserId);
+            
+
+            // 根据转移结果设置审批状态
+            if (successCount > 0 && failCount == 0) {
+                // 全部成功,自动审批通过
+                approval.setApprovalStatus(1);
+                approval.setApprovalRemark("会员营期转移成功");
+            } else if (successCount == 0 && failCount > 0) {
+                // 全部失败,审批驳回
+                approval.setApprovalStatus(2);
+                approval.setApprovalRemark("会员营期转移全部失败:" + failReasons.toString());
+            } else {
+                // 部分成功,设为待审批状态供管理员查看
+                approval.setApprovalStatus(0);
+                approval.setApprovalRemark(String.format("部分成功,成功%d人,失败%d人。失败原因:%s", 
+                    successCount, failCount, failReasons.toString()));
+            }
+            approval.setContent(param.getReason()); // 转移原因
+            approval.setApproverUserId(currentUserId);
+            approval.setProcessedAt(new Date());
+            approval.setCreatedAt(new Date());
+            approval.setUpdatedAt(new Date());
+            
+            // 保存审批记录
+            transferApprovalService.insertCustomerTransferApproval(approval);
+            log.info("会员营期转移审批记录已创建,审批ID:{}", approval.getId());
+
+            // 7. 返回结果
+            String resultMsg = String.format("转移完成!成功:%d人,失败:%d人", successCount, failCount);
+            if (failCount > 0) {
+                resultMsg += ". 失败原因:" + failReasons.toString();
+            }
+
+            log.info("会员营期转移结果:{}", resultMsg);
+            return R.ok(resultMsg)
+                    .put("successCount", successCount)
+                    .put("failCount", failCount)
+                    .put("approvalId", approval.getId());
+
+        } catch (Exception e) {
+            log.error("会员营期转移异常", e);
+            return R.error("会员营期转移失败:" + e.getMessage());
+        }
+    }
+
 }

+ 9 - 0
fs-service/src/main/java/com/fs/course/mapper/FsUserCompanyUserMapper.java

@@ -95,4 +95,13 @@ public interface FsUserCompanyUserMapper extends BaseMapper<FsUserCompanyUser>{
     List<Long> selectFsUserCompanyUserListByMap(@Param("param") Map<String, Object> param);
 
     List<FsUserCompanyUser> selectFsUserCompanyUserByIds(@Param("param") Map<String, Object> param);
+
+    /**
+     * 根据营期ID和销售ID查询会员ID列表
+     * @param periodId 营期ID
+     * @param companyUserId 销售ID
+     * @return 会员ID列表
+     */
+    List<Long> selectUserIdsByPeriodIdAndCompanyUserId(@Param("periodId") Long periodId, 
+                                                        @Param("companyUserId") Long companyUserId);
 }

+ 43 - 0
fs-service/src/main/java/com/fs/course/param/PeriodUserTransferParam.java

@@ -0,0 +1,43 @@
+package com.fs.course.param;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+/**
+ * 会员营期转移参数
+ *
+ * @author fs
+ * @date 2025-01-XX
+ */
+@Data
+@ApiModel("会员营期转移参数")
+public class PeriodUserTransferParam {
+
+    @ApiModelProperty(value = "源营期ID", required = true)
+    @NotNull(message = "源营期ID不能为空")
+    private Long sourcePeriodId;
+
+    @ApiModelProperty(value = "源销售ID", required = true)
+    @NotNull(message = "源销售ID不能为空")
+    private Long sourceCompanyUserId;
+
+    @ApiModelProperty(value = "目标营期ID", required = true)
+    @NotNull(message = "目标营期ID不能为空")
+    private Long targetPeriodId;
+
+    @ApiModelProperty(value = "目标销售ID", required = true)
+    @NotNull(message = "目标销售ID不能为空")
+    private Long targetCompanyUserId;
+
+    @ApiModelProperty(value = "需要转移的会员ID列表", required = true)
+    @NotEmpty(message = "会员ID列表不能为空")
+    private List<Long> userIds;
+
+    @ApiModelProperty(value = "转移原因")
+    private String reason;
+}

+ 1 - 1
fs-service/src/main/java/com/fs/qw/domain/CustomerTransferApproval.java

@@ -35,7 +35,7 @@ public class CustomerTransferApproval extends BaseEntity
     private String companyName;
 
     /** 转移类型: 1=离职继承, 2=在职转接 */
-    @Excel(name = "转移类型: 1=离职继承, 2=在职转接")
+    @Excel(name = "转移类型: 1=离职继承, 2=在职转接 3 = 会员营期转移")
     private Integer transferType;
     private String transferTypeText;
 

+ 17 - 0
fs-service/src/main/resources/mapper/course/FsUserCompanyUserMapper.xml

@@ -194,4 +194,21 @@
         </where>
     </select>
 
+    <!--
+        根据营期ID和销售ID查询会员ID列表
+        通过关联 fs_course_watch_log 表来确保会员在该营期下有观看记录
+    -->
+    <select id="selectUserIdsByPeriodIdAndCompanyUserId" resultType="java.lang.Long">
+        SELECT
+            a.user_id
+        FROM
+            fs_user_company_user a
+            INNER JOIN fs_course_watch_log b ON a.user_id = b.user_id
+        WHERE
+            a.company_user_id = #{companyUserId}
+            AND b.period_id = #{periodId}
+        GROUP BY
+            a.user_id
+    </select>
+
 </mapper>