Jelajahi Sumber

修复客户权限问题

沐梦. 3 hari lalu
induk
melakukan
5eb4c136b9

+ 181 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/CustomerPoolController.java

@@ -0,0 +1,181 @@
+package org.dromara.customer.controller;
+
+import jakarta.validation.constraints.NotNull;
+import lombok.RequiredArgsConstructor;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.customer.domain.bo.CustomerClaimBo;
+import org.dromara.customer.domain.bo.CustomerInfoBo;
+import org.dromara.customer.domain.bo.CustomerListBo;
+import org.dromara.customer.domain.dto.SetCustomerInfoTagDto;
+import org.dromara.customer.domain.dto.ReleaseToPoolDto;
+import org.dromara.customer.domain.vo.CustomerInfoVo;
+import org.dromara.customer.domain.vo.CustomerListVo;
+import org.dromara.customer.service.ICustomerPoolService;
+import org.dromara.system.api.RemoteComCompanyService;
+import org.dromara.system.api.RemoteComCustomerLevelService;
+import org.dromara.system.api.RemoteComStaffService;
+import org.dromara.system.api.domain.vo.RemoteComCompanyVo;
+import org.dromara.system.api.domain.vo.RemoteComCustomerLevelVo;
+import org.dromara.system.api.domain.vo.RemoteComStaffVo;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 客户公海与有效客户统一控制器
+ *
+ * @author Antigravity
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/customerPool")
+public class CustomerPoolController extends BaseController {
+
+    @DubboReference
+    private RemoteComCompanyService remoteComCompanyService;
+    @DubboReference
+    private RemoteComCustomerLevelService remoteComCustomerLevelService;
+    @DubboReference
+    private RemoteComStaffService remoteComStaffService;
+
+    private final ICustomerPoolService customerPoolService;
+
+    /**
+     * 查询客户公海/有效客户列表 (支持通用 isHighSeas 参数)
+     */
+    @GetMapping("/customerList")
+    public TableDataInfo<CustomerListVo> customerList(CustomerListBo bo, PageQuery pageQuery) {
+        return customerPoolService.queryCustomerListPage(bo, pageQuery);
+    }
+
+    /**
+     * 查询有效客户列表 (兼容原 /list,默认过滤有效客户,带数据权限)
+     */
+    @GetMapping("/list")
+    public TableDataInfo<CustomerListVo> list(CustomerListBo bo, PageQuery pageQuery) {
+        Long userId = LoginHelper.getLoginUser().getUserId();
+        RemoteComStaffVo remoteComStaffVo = remoteComStaffService.selectStaffByUserId(userId);
+        Long staffId = (remoteComStaffVo != null) ? remoteComStaffVo.getStaffId() : null;
+
+        boolean isAdmin = LoginHelper.isSuperAdmin() || LoginHelper.isTenantAdmin();
+        bo.getParams().put("isAdmin", isAdmin ? "true" : "false");
+        bo.getParams().put("createBy", userId);
+
+        if ("mine".equals(bo.getActiveTab())) {
+            bo.setSalesPersonId(staffId != null ? staffId : -1L);
+            bo.setServiceStaffId(null);
+            bo.setCreateBy(null);
+        } else if ("involved".equals(bo.getActiveTab())) {
+            bo.setServiceStaffId(staffId != null ? staffId : -1L);
+            bo.setSalesPersonId(null);
+            bo.setCreateBy(null);
+        } else {
+            // "all" tab
+            if (isAdmin) {
+                // 管理员在"全部客户"时查看所有数据,不加自己ID的限制
+                bo.setSalesPersonId(null);
+                bo.setServiceStaffId(null);
+                bo.setCreateBy(null);
+            } else {
+                // 普通用户在"全部客户"时仍需要限制查看自己创建的/负责的/参与的
+                bo.setCreateBy(userId);
+                bo.setSalesPersonId(staffId != null ? staffId : -1L);
+                bo.setServiceStaffId(staffId != null ? staffId : -1L);
+            }
+        }
+        bo.setIsHighSeas("false"); // 强制为有效客户
+
+        return customerPoolService.queryCustomerListPage(bo, pageQuery);
+    }
+
+    /**
+     * 获取客户详细信息
+     */
+    @GetMapping("/{id}")
+    public R<CustomerInfoVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable("id") Long id) {
+        return R.ok(customerPoolService.queryById(id));
+    }
+
+    /**
+     * 新增客户信息
+     */
+    @Log(title = "客户管理", businessType = BusinessType.INSERT)
+    @RepeatSubmit
+    @PostMapping
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody CustomerInfoBo bo) {
+        return toAjax(customerPoolService.insertByBo(bo));
+    }
+
+    /**
+     * 修改客户信息
+     */
+    @Log(title = "客户管理", businessType = BusinessType.UPDATE)
+    @RepeatSubmit
+    @PutMapping
+    public R<Void> edit(@RequestBody CustomerInfoBo bo) {
+        return toAjax(customerPoolService.updateByBo(bo));
+    }
+
+    /**
+     * 认领公海客户
+     */
+    @Log(title = "公海认领", businessType = BusinessType.UPDATE)
+    @PutMapping("/claimPool")
+    public R<Void> claimPool(@RequestBody CustomerClaimBo bo) {
+        return toAjax(customerPoolService.claimPool(bo));
+    }
+
+    /**
+     * 退回客户到公海池
+     */
+    @Log(title = "退回公海", businessType = BusinessType.UPDATE)
+    @PostMapping("/releaseToPool")
+    public R<Integer> releaseToPool(@RequestBody ReleaseToPoolDto bo) {
+        return R.ok(customerPoolService.releaseToPool(bo.getCustomerIds(), bo.getReason()));
+    }
+
+    /**
+     * 转移业务人员
+     */
+    @Log(title = "转移业务员", businessType = BusinessType.UPDATE)
+    @PutMapping("/transferSalesPerson")
+    public R<Integer> transferSalesPerson(@RequestBody SetCustomerInfoTagDto bo) {
+        return R.ok(customerPoolService.transferSalesPerson(bo.getCustomerIds(), bo.getSalesPersonId(), bo.getDeptId()));
+    }
+
+    /**
+     * 转移客服人员
+     */
+    @Log(title = "转移客服", businessType = BusinessType.UPDATE)
+    @PutMapping("/transferServiceStaff")
+    public R<Integer> transferServiceStaff(@RequestBody SetCustomerInfoTagDto bo) {
+        return R.ok(customerPoolService.transferServiceStaff(bo.getCustomerIds(), bo.getServiceStaffId()));
+    }
+
+    /**
+     * 获取公司下拉列表
+     */
+    @GetMapping("/companyOptionList")
+    public R<List<RemoteComCompanyVo>> companyOptionList() {
+        return R.ok(remoteComCompanyService.selectCompanyList());
+    }
+
+    /**
+     * 获取等级下拉列表
+     */
+    @GetMapping("/levelOptionList")
+    public R<List<RemoteComCustomerLevelVo>> levelOptionList() {
+        return R.ok(remoteComCustomerLevelService.selectCustomerLevelList());
+    }
+}

+ 3 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/bo/CustomerListBo.java

@@ -49,4 +49,7 @@ public class CustomerListBo extends BaseEntity {
 
     /** 是否为公海客户 (true: 是, false: 否) */
     private String isHighSeas;
+
+    /** 当前选中的 Tab: mine / involved / all */
+    private String activeTab;
 }

+ 23 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/CustomerPoolMapper.java

@@ -0,0 +1,23 @@
+package org.dromara.customer.mapper;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.apache.ibatis.annotations.Param;
+import org.dromara.customer.domain.CustomerInfo;
+import org.dromara.customer.domain.bo.CustomerListBo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.customer.domain.vo.CustomerInfoVo;
+import org.dromara.customer.domain.vo.CustomerListVo;
+
+/**
+ * 客户公海与有效客户Mapper接口
+ *
+ * @author Antigravity
+ */
+public interface CustomerPoolMapper extends BaseMapperPlus<CustomerInfo, CustomerInfoVo> {
+
+    /**
+     * 客户列表-分页查询(包含公海与有效客户,通过 bo.isHighSeas 筛选)
+     */
+    Page<CustomerListVo> selectCustomerListPage(@Param("page") Page<CustomerListVo> page,
+                                                @Param("bo") CustomerListBo bo);
+}

+ 59 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/ICustomerPoolService.java

@@ -0,0 +1,59 @@
+package org.dromara.customer.service;
+
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.customer.domain.bo.CustomerClaimBo;
+import org.dromara.customer.domain.bo.CustomerInfoBo;
+import org.dromara.customer.domain.bo.CustomerListBo;
+import org.dromara.customer.domain.vo.CustomerInfoVo;
+import org.dromara.customer.domain.vo.CustomerListVo;
+
+import java.util.List;
+
+/**
+ * 客户公海与有效客户Service接口
+ *
+ * @author Antigravity
+ */
+public interface ICustomerPoolService {
+
+    /**
+     * 分页查询客户列表(公海与有效客户公用)
+     */
+    TableDataInfo<CustomerListVo> queryCustomerListPage(CustomerListBo bo, PageQuery pageQuery);
+
+    /**
+     * 获取客户详细信息
+     */
+    CustomerInfoVo queryById(Long id);
+
+    /**
+     * 认领公海客户
+     */
+    Boolean claimPool(CustomerClaimBo claimBo);
+
+    /**
+     * 退回客户到公海池
+     */
+    int releaseToPool(List<Long> customerIds, String reason);
+
+    /**
+     * 转移业务人员
+     */
+    int transferSalesPerson(List<Long> customerIds, Long salesPersonId, Long deptId);
+
+    /**
+     * 转移客服人员
+     */
+    int transferServiceStaff(List<Long> customerIds, Long serviceStaffId);
+
+    /**
+     * 新增客户信息
+     */
+    Boolean insertByBo(CustomerInfoBo bo);
+
+    /**
+     * 修改客户信息
+     */
+    Boolean updateByBo(CustomerInfoBo bo);
+}

+ 33 - 71
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/CustomerInfoServiceImpl.java

@@ -329,12 +329,21 @@ public class CustomerInfoServiceImpl extends ServiceImpl<CustomerInfoMapper, Cus
                 vo.setCreateByName(name);
             }
             if (vo.getUpdateBy() != null) {
-                vo.setUpdateByName(userMap.get(Long.parseLong(vo.getUpdateBy())));
+                String name = userMap.get(Long.parseLong(vo.getUpdateBy()));
+                if (name == null && "-1".equals(vo.getUpdateBy())) name = "系统";
+                vo.setUpdateByName(name);
             }
         }
         return vo;
     }
 
+    /**
+     * 分页查询客户信息列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 客户信息分页列表
+     */
     @Override
     public TableDataInfo<CustomerInfoVo> queryPageList(CustomerInfoBo bo, PageQuery pageQuery) {
         LambdaQueryWrapper<CustomerInfo> lqw = buildQueryWrapper(bo);
@@ -359,11 +368,7 @@ public class CustomerInfoServiceImpl extends ServiceImpl<CustomerInfoMapper, Cus
                     IndustryCategoryVo::getIndustryCategoryName,
                     (e, r) -> e
                 ));
-            records.forEach(vo -> {
-                String name = industryMap.get(vo.getIndustryCategoryId());
-                vo.setIndustryCategory(name);
-                vo.setIndustryName(name);
-            });
+            records.forEach(vo -> vo.setIndustryCategory(industryMap.get(vo.getIndustryCategoryId())));
         }
 
         // === 2. 提取客户ID,查询销售信息 ===
@@ -377,12 +382,22 @@ public class CustomerInfoServiceImpl extends ServiceImpl<CustomerInfoMapper, Cus
             .filter(Objects::nonNull)
             .collect(Collectors.toCollection(() -> new HashSet<>(records.size())));
 
-        List<CustomerSalesInfoVo> salesInfoVos = Collections.emptyList();
-        if (!infoIds.isEmpty()) {
-            salesInfoVos = customerSalesInfoMapper.selectVoList(
-                new LambdaQueryWrapper<CustomerSalesInfo>()
-                    .in(CustomerSalesInfo::getCustomerId, infoIds)
-            );
+        if (infoIds.isEmpty()) {
+            return TableDataInfo.build(result);
+        }
+
+        if (companyIds.isEmpty()) {
+            return TableDataInfo.build(result);
+        }
+
+        List<CustomerSalesInfoVo> salesInfoVos = customerSalesInfoMapper.selectVoList(
+            new LambdaQueryWrapper<CustomerSalesInfo>()
+                .in(CustomerSalesInfo::getCustomerId, infoIds)
+        );
+
+        if (CollUtil.isEmpty(salesInfoVos)) {
+            // 即使没有销售信息,也要返回客户列表(只是销售字段为空)
+            return TableDataInfo.build(result);
         }
 
         // === 3. 构建 customerId -> CustomerSalesInfoVo 映射
@@ -424,73 +439,20 @@ public class CustomerInfoServiceImpl extends ServiceImpl<CustomerInfoMapper, Cus
         // 系统人员与部门,以及客户等级
         Set<Long> comStaffIds = new HashSet<>();
         Set<Long> comDeptIds = new HashSet<>();
-        Set<Long> customerLevelIds = new HashSet<>();
 
         for (CustomerInfoVo vo : records) {
             if (vo.getSalesPersonId() != null) comStaffIds.add(vo.getSalesPersonId());
             if (vo.getServiceStaffId() != null) comStaffIds.add(vo.getServiceStaffId());
             if (vo.getBelongingDepartmentId() != null) comDeptIds.add(vo.getBelongingDepartmentId());
-            if (vo.getCustomerLevelId() != null) customerLevelIds.add(vo.getCustomerLevelId());
         }
 
         // === 远程调用获取名称 ===
-        List<RemoteComStaffVo> comStaffList = comStaffIds.isEmpty()
-            ? Collections.emptyList()
-            : remoteComStaffService.selectStaffByIds(comStaffIds);
-        Map<Long, RemoteComStaffVo> comStaffMap = comStaffList.stream()
-            .collect(Collectors.toMap(RemoteComStaffVo::getStaffId, Function.identity(), (v1, v2) -> v1));
-
-        Map<Long, String> comDeptMap = comDeptIds.isEmpty()
-            ? Collections.emptyMap()
-            : remoteDeptService.selectDeptNameByIds(comDeptIds);
-
-        Map<Long, String> customerLevelMap = customerLevelIds.isEmpty()
-            ? Collections.emptyMap()
-            : remoteComCustomerLevelService.selectCustomerLevelNameByIds(customerLevelIds);
-
-        // 字典翻译
-        List<RemoteDictDataVo> enterpriseTypeDicts = remoteDictService.selectDictDataByType("enterprise_type");
-        Map<String, String> enterpriseTypeMap = enterpriseTypeDicts == null ? Collections.emptyMap() : enterpriseTypeDicts.stream()
-            .collect(Collectors.toMap(RemoteDictDataVo::getDictValue, RemoteDictDataVo::getDictLabel, (k1, k2) -> k1));
-        List<RemoteDictDataVo> q0001Dicts = remoteDictService.selectDictDataByType("Q0001");
-        Map<String, String> q0001Map = q0001Dicts == null ? Collections.emptyMap() : q0001Dicts.stream()
-            .collect(Collectors.toMap(RemoteDictDataVo::getDictValue, RemoteDictDataVo::getDictLabel, (k1, k2) -> k1));
-
-        List<RemoteDictDataVo> cooperationDicts = remoteDictService.selectDictDataByType("cooperation_status");
-        Map<String, String> cooperationMap = cooperationDicts == null ? Collections.emptyMap() : cooperationDicts.stream()
-            .collect(Collectors.toMap(RemoteDictDataVo::getDictValue, RemoteDictDataVo::getDictLabel, (k1, k2) -> k1));
-
-        // === 回填系统人员、部门名称、企业类型、等级、合作状态到 customerInfoVo ===
+        Map<Long, String> comStaffMap = staffIds.isEmpty() ? Collections.emptyMap() : remoteComStaffService.selectStaffNameByIds(comStaffIds);
+        Map<Long, String> comDeptMap = deptIds.isEmpty() ? Collections.emptyMap() : remoteDeptService.selectDeptNameByIds(comDeptIds);
         records.forEach(v -> {
-            RemoteComStaffVo salesPerson = comStaffMap.get(v.getSalesPersonId());
-            RemoteComStaffVo serviceStaff = comStaffMap.get(v.getServiceStaffId());
-
-            if (salesPerson != null) {
-                v.setSalesPersonName(salesPerson.getStaffName());
-                v.setDeptName(salesPerson.getDeptName());
-            }
-            if (serviceStaff != null) {
-                v.setServiceStaffName(serviceStaff.getStaffName());
-            }
-
-            String dName = comDeptMap.get(v.getBelongingDepartmentId());
-            v.setBelongingDepartmentName(dName);
-            if (v.getDeptName() == null) {
-                v.setDeptName(dName);
-            }
-
-            v.setCustomerLevelName(customerLevelMap.get(v.getCustomerLevelId()));
-
-            String typeValue = v.getCustomerTypeId() != null ? String.valueOf(v.getCustomerTypeId()) : null;
-            if (typeValue != null) {
-                String typeName = enterpriseTypeMap.get(typeValue);
-                if (typeName == null) {
-                    typeName = q0001Map.get(typeValue);
-                }
-                v.setEnterpriseTypeName(typeName);
-            }
-
-            v.setCooperationName(cooperationMap.get(v.getStatus()));
+            v.setSalesPersonName(comStaffMap.get(v.getSalesPersonId()));
+            v.setServiceStaffName(comStaffMap.get(v.getServiceStaffId()));
+            v.setBelongingDepartmentName(comDeptMap.get(v.getBelongingDepartmentId()));
         });
 
         // === 7. 将销售信息回填到客户VO ===
@@ -1796,7 +1758,7 @@ public class CustomerInfoServiceImpl extends ServiceImpl<CustomerInfoMapper, Cus
             );
         } else {
             TeamMember member = new TeamMember();
-            member.setDataType(12); 
+            member.setDataType(12);
             member.setObjectNo(objectNo);
             member.setUserNo(userNo);
             member.setRealName(realName);

+ 247 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/CustomerPoolServiceImpl.java

@@ -0,0 +1,247 @@
+package org.dromara.customer.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.dromara.common.core.context.PlatformContext;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.customer.domain.CustomerInfo;
+import org.dromara.customer.domain.CrmStaff;
+import org.dromara.customer.domain.TeamMember;
+import org.dromara.customer.domain.bo.CustomerClaimBo;
+import org.dromara.customer.domain.bo.CustomerInfoBo;
+import org.dromara.customer.domain.bo.CustomerListBo;
+import org.dromara.customer.domain.vo.CustomerInfoVo;
+import org.dromara.customer.domain.vo.CustomerListVo;
+import org.dromara.customer.domain.vo.TeamMemberVo;
+import org.dromara.customer.mapper.CustomerPoolMapper;
+import org.dromara.customer.mapper.TeamMemberMapper;
+import org.dromara.customer.mapper.CrmStaffMapper;
+import org.dromara.customer.service.ICustomerPoolService;
+import org.dromara.customer.service.ICustomerInfoService;
+import org.dromara.system.api.*;
+import org.dromara.system.api.domain.vo.RemoteDictDataVo;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 客户公海与有效客户Service业务层处理
+ *
+ * @author Antigravity
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class CustomerPoolServiceImpl implements ICustomerPoolService {
+
+    @DubboReference
+    private RemoteComCompanyService remoteComCompanyService;
+    @DubboReference
+    private RemoteComCustomerLevelService remoteComCustomerLevelService;
+    @DubboReference
+    private RemoteComStaffService remoteComStaffService;
+    @DubboReference
+    private RemoteDeptService remoteDeptService;
+    @DubboReference
+    private RemoteCreditLevelService remoteCreditLevelService;
+    @DubboReference
+    private RemoteDictService remoteDictService;
+
+    private final CustomerPoolMapper customerPoolMapper;
+    private final TeamMemberMapper teamMemberMapper;
+    private final CrmStaffMapper crmStaffMapper;
+    private final ICustomerInfoService customerInfoService;
+
+    @Override
+    public TableDataInfo<CustomerListVo> queryCustomerListPage(CustomerListBo bo, PageQuery pageQuery) {
+        // 强行填充默认值
+        if (StringUtils.isBlank(bo.getIsHighSeas())) {
+            Object isHighSeasObj = bo.getParams().get("isHighSeas");
+            bo.setIsHighSeas(isHighSeasObj != null ? String.valueOf(isHighSeasObj) : "false");
+        }
+
+        Page<CustomerListVo> page = customerPoolMapper.selectCustomerListPage(pageQuery.build(), bo);
+        List<CustomerListVo> records = page.getRecords();
+
+        if (CollUtil.isNotEmpty(records)) {
+            Set<Long> staffIds = new HashSet<>();
+            Set<Long> creditLevelIds = new HashSet<>();
+            Set<Long> companyIds = new HashSet<>();
+            Set<Long> customerLevelIds = new HashSet<>();
+            Set<Long> deptIds = new HashSet<>();
+
+            records.forEach(vo -> {
+                if (!"true".equals(bo.getIsHighSeas())) {
+                    if (vo.getSalesPersonId() != null) staffIds.add(vo.getSalesPersonId());
+                    if (vo.getServiceStaffId() != null) staffIds.add(vo.getServiceStaffId());
+                    if (vo.getCreditLevelId() != null) creditLevelIds.add(vo.getCreditLevelId());
+                }
+                if (vo.getBelongCompanyId() != null) companyIds.add(vo.getBelongCompanyId());
+                if (vo.getCustomerLevelId() != null) customerLevelIds.add(vo.getCustomerLevelId());
+                if (vo.getBelongingDepartmentId() != null) deptIds.add(vo.getBelongingDepartmentId());
+            });
+
+            Map<Long, String> staffMap = staffIds.isEmpty() ? Collections.emptyMap() : remoteComStaffService.selectStaffNameByIds(staffIds);
+            Map<Long, String> creditLevelMap = creditLevelIds.isEmpty() ? Collections.emptyMap() : remoteCreditLevelService.selectCreditLevelNameByIds(creditLevelIds);
+            Map<Long, String> companyMap = companyIds.isEmpty() ? Collections.emptyMap() : remoteComCompanyService.selectCompanyNameByIds(companyIds);
+            Map<Long, String> customerLevelMap = customerLevelIds.isEmpty() ? Collections.emptyMap() : remoteComCustomerLevelService.selectCustomerLevelNameByIds(customerLevelIds);
+            Map<Long, String> deptMap = deptIds.isEmpty() ? Collections.emptyMap() : remoteDeptService.selectDeptNameByIds(deptIds);
+
+            List<RemoteDictDataVo> enterpriseTypeDicts = remoteDictService.selectDictDataByType("enterprise_type");
+            Map<String, String> enterpriseTypeMap = enterpriseTypeDicts == null ? Collections.emptyMap() : enterpriseTypeDicts.stream()
+                .collect(Collectors.toMap(RemoteDictDataVo::getDictValue, RemoteDictDataVo::getDictLabel, (k1, k2) -> k1));
+
+            List<RemoteDictDataVo> cooperationDicts = remoteDictService.selectDictDataByType("cooperation_status");
+            Map<String, String> cooperationMap = cooperationDicts == null ? Collections.emptyMap() : cooperationDicts.stream()
+                .collect(Collectors.toMap(RemoteDictDataVo::getDictValue, RemoteDictDataVo::getDictLabel, (k1, k2) -> k1));
+
+            records.forEach(vo -> {
+                if (!"true".equals(bo.getIsHighSeas())) {
+                    if (StringUtils.isBlank(vo.getSalesPersonName())) {
+                        vo.setSalesPersonName(staffMap.get(vo.getSalesPersonId()));
+                    }
+                    if (StringUtils.isBlank(vo.getServiceStaffName())) {
+                        vo.setServiceStaffName(staffMap.get(vo.getServiceStaffId()));
+                    }
+                    vo.setCreditLevelName(creditLevelMap.get(vo.getCreditLevelId()));
+                }
+                vo.setCompanyName(companyMap.get(vo.getBelongCompanyId()));
+                vo.setCustomerLevelName(customerLevelMap.get(vo.getCustomerLevelId()));
+                vo.setDeptName(deptMap.get(vo.getBelongingDepartmentId()));
+
+                String typeValue = vo.getEnterpriseTypeId() != null ? String.valueOf(vo.getEnterpriseTypeId()) : null;
+                vo.setEnterpriseTypeName(enterpriseTypeMap.get(typeValue));
+                vo.setCooperationName(cooperationMap.get(vo.getStatus()));
+            });
+        }
+
+        return TableDataInfo.build(page);
+    }
+
+    @Override
+    public CustomerInfoVo queryById(Long id) {
+        return customerInfoService.queryById(id);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean claimPool(CustomerClaimBo claimBo) {
+        Long customerId = claimBo.getId();
+        CustomerInfo customer = customerPoolMapper.selectById(customerId);
+        if (customer == null) {
+            return false;
+        }
+
+        // 1. 认领操作更新客户主表
+        customer.setSalesPersonId(claimBo.getSalesPersonId());
+        customer.setServiceStaffId(claimBo.getServiceStaffId());
+        customer.setBelongingDepartmentId(claimBo.getDeptId());
+        customer.setStatus("0"); // 认领成功后,将客户主表状态改为有效
+        boolean updated = customerPoolMapper.updateById(customer) > 0;
+
+        if (updated) {
+            // 2. 将业务员加入团队成员
+            if (claimBo.getSalesPersonId() != null) {
+                saveOrUpdateTeamMember(customer.getCustomerNo(), claimBo.getSalesPersonId(), "1", 1, 1);
+            }
+            // 3. 将客服支持加入团队成员
+            if (claimBo.getServiceStaffId() != null) {
+                saveOrUpdateTeamMember(customer.getCustomerNo(), claimBo.getServiceStaffId(), "3", 0, 0);
+            }
+        }
+
+        return updated;
+    }
+
+    private void saveOrUpdateTeamMember(String objectNo, Long userNo, String roleCode, Integer izManager, Integer updateAccredit) {
+        TeamMemberVo existing = teamMemberMapper.selectByObjectNoAndUserNoWithDeleted(objectNo, userNo);
+        String realName = "";
+        CrmStaff staff = crmStaffMapper.selectById(userNo);
+        if (staff != null) {
+            realName = staff.getStaffName();
+        }
+
+        if (existing != null) {
+            teamMemberMapper.restoreMemberById(
+                existing.getId(),
+                userNo,
+                realName,
+                roleCode,
+                updateAccredit,
+                izManager,
+                LoginHelper.getUserId(),
+                LoginHelper.getDeptId(),
+                PlatformContext.getPlatform()
+            );
+        } else {
+            TeamMember member = new TeamMember();
+            member.setDataType(12);
+            member.setObjectNo(objectNo);
+            member.setUserNo(userNo);
+            member.setRealName(realName);
+            member.setRoleCode(roleCode);
+            member.setIzManager(izManager);
+            member.setUpdateAccredit(updateAccredit);
+            member.setPlatformCode(PlatformContext.getPlatform());
+            member.setCreateUserId(LoginHelper.getUserId());
+            member.setCreateOrgId(LoginHelper.getDeptId());
+            member.setIsDelete(0);
+            teamMemberMapper.insert(member);
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int releaseToPool(List<Long> customerIds, String reason) {
+        if (customerIds == null || customerIds.isEmpty()) {
+            return 0;
+        }
+
+        // 1. 获取要退回客户的 customer_no 列表
+        List<CustomerInfo> customers = customerPoolMapper.selectBatchIds(customerIds);
+        List<String> customerNos = customers.stream()
+            .map(CustomerInfo::getCustomerNo)
+            .filter(StringUtils::isNotBlank)
+            .collect(Collectors.toList());
+
+        // 2. 如果存在客户编号,则逻辑删除对应客户(dataType = 12)的所有团队成员
+        if (!customerNos.isEmpty()) {
+            teamMemberMapper.delete(new LambdaQueryWrapper<TeamMember>()
+                .eq(TeamMember::getDataType, 12)
+                .in(TeamMember::getObjectNo, customerNos)
+            );
+        }
+
+        // 3. 调用原有的 releaseToPool 方法更新客户主表状态
+        return customerInfoService.releaseToPool(customerIds, reason);
+    }
+
+    @Override
+    public int transferSalesPerson(List<Long> customerIds, Long salesPersonId, Long deptId) {
+        return customerInfoService.transferSalesPerson(customerIds, salesPersonId, deptId);
+    }
+
+    @Override
+    public int transferServiceStaff(List<Long> customerIds, Long serviceStaffId) {
+        return customerInfoService.transferServiceStaff(customerIds, serviceStaffId);
+    }
+
+    @Override
+    public Boolean insertByBo(CustomerInfoBo bo) {
+        return customerInfoService.insertByBo(bo);
+    }
+
+    @Override
+    public Boolean updateByBo(CustomerInfoBo bo) {
+        return customerInfoService.updateByBo(bo);
+    }
+}

+ 0 - 1
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/TeamMemberServiceImpl.java

@@ -81,7 +81,6 @@ public class TeamMemberServiceImpl implements ITeamMemberService {
             }
         }
         
-        // 翻译权限 (team_permission)
         if (vo.getUpdateAccredit() != null) {
             List<RemoteDictDataVo> permDicts = remoteDictService.selectDictDataByType("team_permission");
             if (permDicts != null) {

+ 109 - 0
ruoyi-modules/ruoyi-customer/src/main/resources/mapper/customer/CustomerPoolMapper.xml

@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.customer.mapper.CustomerPoolMapper">
+
+    <!-- 客户列表-分页查询(包含公海与有效客户,通过 bo.isHighSeas 筛选) -->
+    <select id="selectCustomerListPage" resultType="org.dromara.customer.domain.vo.CustomerListVo">
+        SELECT
+            ci.id,
+            ci.customer_no AS customerNo,
+            IFNULL(ci.customer_name, ci.business_customer_name) AS customerName,
+            ci.industry_category_id AS industryCategoryId,
+            ic.industry_category_name AS industryName,
+            csi.credit_amount AS creditAmount,
+            csi.credit_management_id AS creditLevelId,
+            csi.accounts_receivable AS accountsReceivable,
+            ci.sales_person_id AS salesPersonId,
+            sp.staff_name AS salesPersonName,
+            ci.belong_company_id AS belongCompanyId,
+            ci.customer_type_id AS enterpriseTypeId,
+            ci.customer_level_id AS customerLevelId,
+            ci.service_staff_id AS serviceStaffId,
+            ss.staff_name AS serviceStaffName,
+            ci.belonging_department_id AS belongingDepartmentId,
+            sd.dept_name AS deptName,
+            ci.status
+        FROM customer_info ci
+        LEFT JOIN customer_sales_info csi ON ci.id = csi.customer_id AND csi.del_flag = '0'
+        LEFT JOIN industry_category ic ON ci.industry_category_id = ic.id AND ic.del_flag = '0'
+        LEFT JOIN com_staff sp ON ci.sales_person_id = sp.staff_id AND sp.del_flag = '0'
+        LEFT JOIN com_staff ss ON ci.service_staff_id = ss.staff_id AND ss.del_flag = '0'
+        LEFT JOIN sys_dept sd ON sp.dept_id = sd.dept_id
+        <where>
+            ci.del_flag = '0'
+            <choose>
+                <when test="bo.isHighSeas == 'true'">
+                    AND (ci.sales_person_id IS NULL OR ci.sales_person_id = 0)
+                    AND (ci.service_staff_id IS NULL OR ci.service_staff_id = 0)
+                </when>
+                <otherwise>
+                    AND ( (ci.sales_person_id IS NOT NULL AND ci.sales_person_id != 0) 
+                       OR (ci.service_staff_id IS NOT NULL AND ci.service_staff_id != 0) )
+                    <if test="bo.activeTab == 'mine' and bo.salesPersonId != null">
+                        AND ci.sales_person_id = #{bo.salesPersonId}
+                    </if>
+                    <if test="bo.activeTab == 'involved' and bo.serviceStaffId != null">
+                        AND (ci.sales_person_id IS NULL OR ci.sales_person_id != #{bo.serviceStaffId})
+                        AND (ci.service_staff_id IS NULL OR ci.service_staff_id != #{bo.serviceStaffId})
+                        AND ci.customer_no IN (
+                            SELECT tm.object_no FROM team_member tm
+                            WHERE tm.user_no = #{bo.serviceStaffId}
+                              AND tm.data_type = 12
+                              AND tm.is_delete = 0
+                        )
+                    </if>
+                    <if test="(bo.activeTab == null or bo.activeTab == 'all' or bo.activeTab == '') and bo.params.isAdmin == 'false'">
+                        AND (
+                            ci.create_by = #{bo.params.createBy}
+                            OR ci.sales_person_id = #{bo.salesPersonId}
+                            OR ci.service_staff_id = #{bo.serviceStaffId}
+                            OR ci.customer_no IN (
+                                SELECT tm.object_no FROM team_member tm 
+                                WHERE tm.user_no = #{bo.serviceStaffId} 
+                                  AND tm.data_type = 12 
+                                  AND tm.is_delete = 0
+                            )
+                        )
+                    </if>
+                </otherwise>
+            </choose>
+            <if test="bo.platformCode != null and bo.platformCode != ''">
+                AND ci.platform_code = #{bo.platformCode}
+            </if>
+            <if test="bo.id != null">
+                AND ci.id = #{bo.id}
+            </if>
+            <if test="bo.customerNo != null and bo.customerNo != ''">
+                AND ci.customer_no LIKE CONCAT('%', #{bo.customerNo}, '%')
+            </if>
+            <if test="bo.industryCategoryId != null">
+                AND ci.industry_category_id = #{bo.industryCategoryId}
+            </if>
+            <if test="bo.customerLevelId != null">
+                AND ci.customer_level_id = #{bo.customerLevelId}
+            </if>
+            <if test="bo.salesPersonId != null and bo.activeTab != 'mine' and (bo.activeTab != 'all' or bo.params.isAdmin == 'true')">
+                AND ci.sales_person_id = #{bo.salesPersonId}
+            </if>
+            <if test="bo.serviceStaffId != null and bo.activeTab != 'involved' and (bo.activeTab != 'all' or bo.params.isAdmin == 'true')">
+                AND ci.service_staff_id = #{bo.serviceStaffId}
+            </if>
+            <if test="bo.belongingDepartmentId != null">
+                AND ci.belonging_department_id = #{bo.belongingDepartmentId}
+            </if>
+            <if test="bo.status != null and bo.status != ''">
+                AND ci.status = #{bo.status}
+            </if>
+            <if test="bo.customerName != null and bo.customerName != ''">
+                AND (ci.customer_name LIKE CONCAT('%', #{bo.customerName}, '%') OR ci.business_customer_name LIKE CONCAT('%', #{bo.customerName}, '%'))
+            </if>
+            <if test="bo.enterpriseTypeId != null">
+                AND ci.customer_type_id = #{bo.enterpriseTypeId}
+            </if>
+        </where>
+        ORDER BY ci.create_time DESC
+    </select>
+
+</mapper>