Pārlūkot izejas kodu

客户管理代码

沐梦. 1 mēnesi atpakaļ
vecāks
revīzija
74dfda689e
34 mainītis faili ar 2037 papildinājumiem un 36 dzēšanām
  1. 11 0
      ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteComCustomerLevelService.java
  2. 44 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/annotation/CrmLog.java
  3. 125 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/aspect/CrmLogAspect.java
  4. 74 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/CustomerCareController.java
  5. 10 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/CustomerInfoController.java
  6. 94 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/SalesresultanalyzeController.java
  7. 28 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/WorkbenchController.java
  8. 194 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/CustomerCare.java
  9. 71 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/SalesResultAnalyze.java
  10. 138 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/bo/CustomerCareBo.java
  11. 3 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/bo/CustomerInfoBo.java
  12. 6 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/bo/CustomerListBo.java
  13. 70 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/bo/SalesresultanalyzeBo.java
  14. 2 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/dto/SetCustomerInfoTagDto.java
  15. 155 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/CustomerCareVo.java
  16. 14 1
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/CustomerInfoVo.java
  17. 36 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/CustomerListVo.java
  18. 85 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/SalesresultanalyzeVo.java
  19. 55 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/WorkbenchStatVo.java
  20. 15 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/CustomerCareMapper.java
  21. 7 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/CustomerInfoMapper.java
  22. 15 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/SalesresultanalyzeMapper.java
  23. 49 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/ICustomerCareService.java
  24. 9 4
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/ICustomerInfoService.java
  25. 53 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/ISalesresultanalyzeService.java
  26. 13 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/IWorkbenchService.java
  27. 117 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/CustomerCareServiceImpl.java
  28. 17 2
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/CustomerContactServiceImpl.java
  29. 100 28
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/CustomerInfoServiceImpl.java
  30. 118 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/SalesresultanalyzeServiceImpl.java
  31. 273 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/WorkbenchServiceImpl.java
  32. 7 0
      ruoyi-modules/ruoyi-customer/src/main/resources/mapper/customer/CrmCustomerCareMapper.xml
  33. 11 0
      ruoyi-modules/ruoyi-customer/src/main/resources/mapper/customer/CustomerInfoMapper.xml
  34. 18 1
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/dubbo/RemoteComCustomerLevelServiceImpl.java

+ 11 - 0
ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteComCustomerLevelService.java

@@ -2,7 +2,18 @@ package org.dromara.system.api;
 
 import org.dromara.system.api.domain.vo.RemoteComCustomerLevelVo;
 
+import java.util.Map;
+import java.util.Set;
+
 public interface RemoteComCustomerLevelService {
 
     RemoteComCustomerLevelVo selectCustomerLeveByLevelName(String levelName);
+
+    /**
+     * 根据ID批量查询客户等级名称
+     *
+     * @param ids 客户等级ID集合
+     * @return Map<ID, 名称>
+     */
+    Map<Long, String> selectCustomerLevelNameByIds(Set<Long> ids);
 }

+ 44 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/annotation/CrmLog.java

@@ -0,0 +1,44 @@
+package org.dromara.customer.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * CRM 业务日志注解
+ * @author tys
+ */
+@Target({ ElementType.PARAMETER, ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface CrmLog {
+    /**
+     * 数据类型 (1-线索, 3-联系人等)
+     */
+    int dataType() default 0;
+
+    /**
+     * 操作类型 (1-新增, 2-修改, 3-删除, 4-认领, 5-转移)
+     */
+    int actionType() default 0;
+
+    /**
+     * 操作模块 (1-销售中心等)
+     */
+    int actionModule() default 1;
+
+    /**
+     * 详情描述
+     */
+    String details() default "";
+
+    /**
+     * 对象编号在参数中的表达式 (支持 SpEL)
+     * 例如: "#bo.id" 或 "#ids"
+     */
+    String objectNo() default "";
+
+    /**
+     * 目标对象在参数中的表达式
+     * 例如: "#bo.projectName"
+     */
+    String targetObject() default "";
+}

+ 125 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/aspect/CrmLogAspect.java

@@ -0,0 +1,125 @@
+package org.dromara.customer.aspect;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.AfterReturning;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.customer.annotation.CrmLog;
+import org.dromara.customer.service.IOperationLogService;
+import org.springframework.core.DefaultParameterNameDiscoverer;
+import org.springframework.expression.EvaluationContext;
+import org.springframework.expression.Expression;
+import org.springframework.expression.ExpressionParser;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.Method;
+import java.util.Collection;
+
+/**
+ * CRM 日志切面处理
+ * @author tys
+ */
+@Slf4j
+@Aspect
+@Component
+@RequiredArgsConstructor
+public class CrmLogAspect {
+
+    private final IOperationLogService operationLogService;
+
+    private final ExpressionParser parser = new SpelExpressionParser();
+    private final DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
+
+    @AfterReturning(pointcut = "@annotation(crmLog)", returning = "jsonResult")
+    public void doAfterReturning(JoinPoint joinPoint, CrmLog crmLog, Object jsonResult) {
+        handleLog(joinPoint, crmLog, jsonResult);
+    }
+
+    protected void handleLog(final JoinPoint joinPoint, CrmLog crmLog, Object jsonResult) {
+        try {
+            // 获取方法参数
+            Object[] args = joinPoint.getArgs();
+            Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
+            String[] parameterNames = discoverer.getParameterNames(method);
+
+            // 设置 SpEL 上下文
+            EvaluationContext context = new StandardEvaluationContext();
+            if (parameterNames != null) {
+                for (int i = 0; i < parameterNames.length; i++) {
+                    context.setVariable(parameterNames[i], args[i]);
+                }
+            }
+
+            // 解析 ObjectNo
+            String objectNo = "";
+            if (StringUtils.isNotBlank(crmLog.objectNo())) {
+                Object val = parseExpression(crmLog.objectNo(), context);
+                if (val instanceof Collection) {
+                    // 如果是批量删除,则循环记录或记录 ID 串
+                    for (Object id : (Collection<?>) val) {
+                        if (id != null) saveLog(crmLog, String.valueOf(id), context);
+                    }
+                    return;
+                } else if (val != null && val.getClass().isArray()) {
+                    for (Object id : (Object[]) val) {
+                        if (id != null) saveLog(crmLog, String.valueOf(id), context);
+                    }
+                    return;
+                }
+                objectNo = (val != null) ? String.valueOf(val) : "";
+            }
+
+            // 兜底策略:如果 objectNo 为空且有 bo 参数,尝试获取 bo.id
+            if (StringUtils.isBlank(objectNo) || "null".equals(objectNo)) {
+                if (parameterNames != null) {
+                    for (int i = 0; i < parameterNames.length; i++) {
+                        if ("bo".equals(parameterNames[i]) && args[i] != null) {
+                            try {
+                                Method getId = args[i].getClass().getMethod("getId");
+                                Object id = getId.invoke(args[i]);
+                                if (id != null) objectNo = String.valueOf(id);
+                            } catch (Exception ignored) {}
+                        }
+                    }
+                }
+            }
+
+            if (StringUtils.isNotBlank(objectNo) && !"null".equals(objectNo)) {
+                saveLog(crmLog, objectNo, context);
+            }
+
+        } catch (Exception exp) {
+            log.error("CRM日志记录异常: {}", exp.getMessage());
+        }
+    }
+
+    private void saveLog(CrmLog crmLog, String objectNo, EvaluationContext context) {
+        String targetObject = "";
+        if (StringUtils.isNotBlank(crmLog.targetObject())) {
+            Object val = parseExpression(crmLog.targetObject(), context);
+            targetObject = val != null ? String.valueOf(val) : "";
+        }
+
+        operationLogService.recordLog(
+            crmLog.dataType(),
+            objectNo,
+            crmLog.actionType(),
+            crmLog.actionModule(),
+            crmLog.details(),
+            targetObject
+        );
+    }
+
+    private Object parseExpression(String expressionStr, EvaluationContext context) {
+        if (expressionStr.startsWith("#")) {
+            Expression expression = parser.parseExpression(expressionStr);
+            return expression.getValue(context);
+        }
+        return expressionStr;
+    }
+}

+ 74 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/CustomerCareController.java

@@ -0,0 +1,74 @@
+package org.dromara.customer.controller;
+
+import cn.dev33.satoken.annotation.SaIgnore;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.customer.domain.bo.CustomerCareBo;
+import org.dromara.customer.domain.vo.CustomerCareVo;
+import org.dromara.customer.service.ICustomerCareService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Arrays;
+
+/**
+ * 客户关怀Controller
+ *
+ * @author tys
+ * @date 2026-04-08
+ */
+@SaIgnore
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/crm/care")
+public class CustomerCareController extends BaseController {
+
+    private final ICustomerCareService crmCustomerCareService;
+
+    /**
+     * 查询客户关怀分页列表
+     */
+    @GetMapping("/list")
+    public TableDataInfo<CustomerCareVo> list(CustomerCareBo bo, PageQuery pageQuery) {
+        return crmCustomerCareService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 获取客户关怀详细信息
+     */
+    @GetMapping("/{id}")
+    public R<CustomerCareVo> getInfo(@PathVariable Long id) {
+        return R.ok(crmCustomerCareService.queryById(id));
+    }
+
+    /**
+     * 新增客户关怀
+     */
+    @PostMapping
+    public R<Void> add(@RequestBody CustomerCareBo bo) {
+        return toAjax(crmCustomerCareService.insertByBo(bo));
+    }
+
+    /**
+     * 修改客户关怀
+     */
+    @PutMapping
+    public R<Void> edit(@RequestBody CustomerCareBo bo) {
+        return toAjax(crmCustomerCareService.updateByBo(bo));
+    }
+
+    /**
+     * 删除客户关怀
+     */
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@PathVariable Long[] ids) {
+        return toAjax(crmCustomerCareService.deleteWithValidByIds(Arrays.asList(ids), true));
+    }
+
+}
+
+

+ 10 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/CustomerInfoController.java

@@ -16,6 +16,7 @@ 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.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.bo.CustomerSalesInfoBo;
@@ -227,6 +228,15 @@ public class CustomerInfoController extends BaseController {
         return R.ok(customerInfoService.transferServiceStaff(bo.getCustomerIds(), bo.getServiceStaffId()));
     }
 
+    /**
+     * 认领公海客户
+     */
+    @Log(title = "客户信息", businessType = BusinessType.UPDATE)
+    @PutMapping("/claimPool")
+    public R<Void> claimPool(@RequestBody CustomerClaimBo bo) {
+        return toAjax(customerInfoService.claimPool(bo));
+    }
+
     /**
      * 导入数据
      *

+ 94 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/SalesresultanalyzeController.java

@@ -0,0 +1,94 @@
+package org.dromara.customer.controller;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+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.web.core.BaseController;
+import org.dromara.customer.annotation.CrmLog;
+import org.dromara.customer.domain.bo.SalesresultanalyzeBo;
+import org.dromara.customer.domain.vo.SalesresultanalyzeVo;
+import org.dromara.customer.service.ISalesresultanalyzeService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 销售结果分析控制器
+ *
+ * @author tys
+ * @date 2026-04-21
+ */
+@Validated
+@Tag(name = "销售结果分析控制器", description = "销售结果分析相关接口")
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/salesresultanalyze")
+public class SalesresultanalyzeController extends BaseController {
+
+    private final ISalesresultanalyzeService salesresultanalyzeService;
+
+    /**
+     * 查询销售结果分析列表
+     */
+    @Operation(summary = "查询销售结果分析列表")
+    @GetMapping("/list")
+    public TableDataInfo<SalesresultanalyzeVo> list(SalesresultanalyzeBo bo, PageQuery pageQuery) {
+        return salesresultanalyzeService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 获取销售结果分析详细信息
+     */
+    @Operation(summary = "获取销售结果分析详细信息")
+    @GetMapping("/{id}")
+    public R<SalesresultanalyzeVo> getInfo(@Parameter(description = "主键") @PathVariable Long id) {
+        return R.ok(salesresultanalyzeService.queryById(id));
+    }
+
+    /**
+     * 根据对象编号获取销售结果分析详细信息
+     */
+    @Operation(summary = "根据对象编号获取销售结果分析")
+    @GetMapping("/objectNo/{objectNo}")
+    public R<SalesresultanalyzeVo> getInfoByObjectNo(@Parameter(description = "对象编号") @PathVariable String objectNo) {
+        return R.ok(salesresultanalyzeService.queryByObjectNo(objectNo));
+    }
+
+    /**
+     * 新增销售结果分析
+     */
+    @Operation(summary = "新增销售结果分析")
+    @CrmLog(dataType = 1, actionType = 6, details = "新增成交结果分析", objectNo = "#bo.objectNo")
+    @Log(title = "销售结果分析", businessType = BusinessType.INSERT)
+    @PostMapping
+    public R<Void> add(@RequestBody SalesresultanalyzeBo bo) {
+        return toAjax(salesresultanalyzeService.insertByBo(bo));
+    }
+
+    /**
+     * 修改销售结果分析
+     */
+    @Operation(summary = "修改销售结果分析")
+    @Log(title = "销售结果分析", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public R<Void> edit(@RequestBody SalesresultanalyzeBo bo) {
+        return toAjax(salesresultanalyzeService.updateByBo(bo));
+    }
+
+    /**
+     * 删除销售结果分析
+     */
+    @Operation(summary = "删除销售结果分析")
+    @Log(title = "销售结果分析", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@Parameter(description = "主键串") @PathVariable Long[] ids) {
+        return toAjax(salesresultanalyzeService.deleteWithValidByIds(List.of(ids), true));
+    }
+}

+ 28 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/WorkbenchController.java

@@ -0,0 +1,28 @@
+package org.dromara.customer.controller;
+
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.customer.domain.vo.WorkbenchStatVo;
+import org.dromara.customer.service.IWorkbenchService;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 工作台控制器
+ */
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/workbench")
+public class WorkbenchController {
+
+    private final IWorkbenchService workbenchService;
+
+    /**
+     * 获取统计数据
+     */
+    @GetMapping("/stat")
+    public R<WorkbenchStatVo> getStat() {
+        return R.ok(workbenchService.getStat());
+    }
+}

+ 194 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/CustomerCare.java

@@ -0,0 +1,194 @@
+package org.dromara.customer.domain;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.tenant.core.TenantEntity;
+
+import java.io.Serial;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 客户关怀信息对象
+ *
+ * @author tys
+ * @date 2026-04-08
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("customerconcern")
+public class CustomerCare extends TenantEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @TableId(value = "Id")
+    private Long id;
+
+    /**
+     * 收据/单据编号
+     */
+    @TableField("ReceiptId")
+    private String receiptId;
+
+    /**
+     * 客户ID
+     */
+    @TableField("CustomerId")
+    private String customerId;
+
+    /**
+     * 客户名称
+     */
+    @TableField("CustomerName")
+    private String customerName;
+
+    /**
+     * 行业
+     */
+    @TableField("Profession")
+    private String profession;
+
+    /**
+     * 部门
+     */
+    @TableField("Department")
+    private String department;
+
+    /**
+     * 销售员
+     */
+    @TableField("Salesman")
+    private String salesman;
+
+    /**
+     * 人员编号
+     */
+    @TableField("PersonnelNumber")
+    private String personnelNumber;
+
+    /**
+     * 联系人
+     */
+    @TableField("ContactPerson")
+    private String contactPerson;
+
+    /**
+     * 电话
+     */
+    @TableField("Phone")
+    private String phone;
+
+    /**
+     * 固定电话
+     */
+    @TableField("Telephone")
+    private String telephone;
+
+    /**
+     * 关注类型
+     */
+    @TableField("ConcernType")
+    private String concernType;
+
+    /**
+     * 金额
+     */
+    @TableField("Amount")
+    private BigDecimal amount;
+
+    /**
+     * 需求日期
+     */
+    @TableField("RequirementDate")
+    private Date requirementDate;
+
+    /**
+     * 文件编号
+     */
+    @TableField("FileNo")
+    private String fileNo;
+
+    /**
+     * 礼品描述
+     */
+    @TableField("GiftDesc")
+    private String giftDesc;
+
+    /**
+     * 关注论据
+     */
+    @TableField("ConcernArgument")
+    private String concernArgument;
+
+    /**
+     * 审核状态(0-待审核,1-已通过,2-已驳回)
+     */
+    @TableField("AuditStatus")
+    private Integer auditStatus;
+
+    /**
+     * 审核人ID
+     */
+    @TableField("AuditPeople")
+    private Long auditPeople;
+
+    /**
+     * 审核日期
+     */
+    @TableField("AuditDate")
+    private Date auditDate;
+
+    /**
+     * 创建组织ID(对应数据库 CreateOrgId,由 createDept 填充)
+     */
+    @TableField(value = "CreateOrgId", fill = FieldFill.INSERT)
+    private Long createOrgId;
+
+    /**
+     * 创建部门 (表无此列,排除;setter 同步到 createOrgId)
+     */
+    @TableField(exist = false)
+    private Long createDept;
+
+    public void setCreateDept(Long createDept) {
+        this.createDept = createDept;
+        this.createOrgId = createDept;
+    }
+
+    /**
+     * 创建者 (覆盖 BaseEntity)
+     */
+    @TableField(value = "CreateUserId", fill = FieldFill.INSERT)
+    private Long createBy;
+
+    /**
+     * 创建时间 (覆盖 BaseEntity)
+     */
+    @TableField(value = "CreateTime", fill = FieldFill.INSERT)
+    private Date createTime;
+
+    /**
+     * 更新者 (覆盖 BaseEntity)
+     */
+    @TableField(value = "UpdateUserId", fill = FieldFill.INSERT_UPDATE)
+    private Long updateBy;
+
+    /**
+     * 更新时间 (覆盖 BaseEntity)
+     */
+    @TableField(value = "UpdateTime", fill = FieldFill.INSERT_UPDATE)
+    private Date updateTime;
+
+    /**
+     * 是否删除 (0-未删除, 1-已删除)
+     */
+    @TableLogic(value = "0", delval = "1")
+    @TableField("IsDelete")
+    private Boolean isDelete = false;
+
+}

+ 71 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/SalesResultAnalyze.java

@@ -0,0 +1,71 @@
+package org.dromara.customer.domain;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.tenant.core.TenantEntity;
+
+/**
+ * 销售结果分析对象 salesresultanalyze
+ *
+ * @author Antigravity
+ * @date 2026-04-21
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("salesresultanalyze")
+public class SalesResultAnalyze extends TenantEntity {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键,自增ID
+     */
+    @TableId(value = "Id")
+    private Long id;
+
+    /**
+     * 数据类型(1:线索, 2:商机, 等)
+     */
+    @TableField("DataType")
+    private Integer dataType;
+
+    /**
+     * 对象编号
+     */
+    @TableField("ObjectNo")
+    private String objectNo;
+
+    /**
+     * 成交结果(1:赢单, 2:丢单)
+     */
+    @TableField("DealResult")
+    private Integer dealResult;
+
+    /**
+     * 胜利总结
+     */
+    @TableField("WinSumUp")
+    private String winSumUp;
+
+    /**
+     * 失败原因
+     */
+    @TableField("LoseReason")
+    private String loseReason;
+
+    /**
+     * 文件编号
+     */
+    @TableField("FileNo")
+    private String fileNo;
+
+    /**
+     * 是否删除(0:否, 1:是)
+     */
+    @TableField("IsDelete")
+    private Integer isDelete;
+
+}

+ 138 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/bo/CustomerCareBo.java

@@ -0,0 +1,138 @@
+package org.dromara.customer.domain.bo;
+
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.customer.domain.CustomerCare;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 客户关怀业务对象
+ *
+ * @author tys
+ * @date 2026-04-08
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = CustomerCare.class, reverseConvertGenerate = true)
+public class CustomerCareBo extends BaseEntity {
+
+    /**
+     * 主键ID
+     */
+    private Long id;
+
+    /**
+     * 收据/单据编号
+     */
+    private String receiptId;
+
+    /**
+     * 客户ID
+     */
+    private String customerId;
+
+    /**
+     * 客户名称
+     */
+    private String customerName;
+
+    /**
+     * 行业
+     */
+    private String profession;
+
+    /**
+     * 部门
+     */
+    private String department;
+
+    /**
+     * 销售员
+     */
+    private String salesman;
+
+    /**
+     * 人员编号
+     */
+    private String personnelNumber;
+
+    /**
+     * 联系人
+     */
+    private String contactPerson;
+
+    /**
+     * 电话
+     */
+    private String phone;
+
+    /**
+     * 固定电话
+     */
+    private String telephone;
+
+    /**
+     * 关注类型
+     */
+    private String concernType;
+
+    /**
+     * 金额
+     */
+    private BigDecimal amount;
+
+    /**
+     * 需求日期
+     */
+    private Date requirementDate;
+
+    /**
+     * 文件编号
+     */
+    private String fileNo;
+
+    /**
+     * 礼品描述
+     */
+    private String giftDesc;
+
+    /**
+     * 关注论据
+     */
+    private String concernArgument;
+
+    /**
+     * 审核状态(0-待审核,1-已通过,2-已驳回)
+     */
+    private Integer auditStatus;
+
+    /**
+     * 审核人ID
+     */
+    private Long auditPeople;
+
+    /**
+     * 审核日期
+     */
+    private Date auditDate;
+
+    /**
+     * 创建组织ID
+     */
+    private Long createOrgId;
+
+    /**
+     * 平台编码
+     */
+    private String platformCode;
+
+    /**
+     * 查询范围(mine:我负责的,all:全部)
+     */
+    private String scope;
+
+}

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

@@ -199,6 +199,9 @@ public class CustomerInfoBo extends BaseEntity {
      */
     private String remark;
 
+    /** 是否公海 (true/false) */
+    private String isHighSeas;
+
     private CustomerBusinessInfoBo customerBusinessBo;
 
     private CustomerSalesInfoBo customerSalesInfoBo;

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

@@ -29,6 +29,12 @@ public class CustomerListBo extends BaseEntity {
     /** 业务员ID */
     private Long salesPersonId;
 
+    /** 客服支持ID */
+    private Long serviceStaffId;
+
     /** 客户等级ID */
     private Long customerLevelId;
+
+    /** 是否为公海客户 (true: 是, false: 否) */
+    private String isHighSeas;
 }

+ 70 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/bo/SalesresultanalyzeBo.java

@@ -0,0 +1,70 @@
+package org.dromara.customer.domain.bo;
+
+import io.github.linpeilie.annotations.AutoMapper;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.customer.domain.SalesResultAnalyze;
+
+/**
+ * 销售结果分析业务对象 salesresultanalyze
+ *
+ * @author Antigravity
+ * @date 2026-04-21
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = SalesResultAnalyze.class, reverseConvertGenerate = true)
+@Schema(description = "销售结果分析业务对象")
+public class SalesresultanalyzeBo extends BaseEntity {
+
+    /**
+     * 主键,自增ID
+     */
+    @Schema(description = "主键,自增ID")
+    private Long id;
+
+    /**
+     * 数据类型(1:线索, 2:商机)
+     */
+    @Schema(description = "数据类型(1:线索, 2:商机)")
+    private Integer dataType;
+
+    /**
+     * 对象编号
+     */
+    @Schema(description = "对象编号")
+    private String objectNo;
+
+    /**
+     * 成交结果(1:赢单, 2:丢单)
+     */
+    @Schema(description = "成交结果(1:赢单, 2:丢单)")
+    private Integer dealResult;
+
+    /**
+     * 胜利总结
+     */
+    @Schema(description = "胜利总结")
+    private String winSumUp;
+
+    /**
+     * 失败原因
+     */
+    @Schema(description = "失败原因")
+    private String loseReason;
+
+    /**
+     * 文件编号
+     */
+    @Schema(description = "文件编号")
+    private String fileNo;
+
+    /**
+     * 平台标识
+     */
+    @Schema(description = "平台标识")
+    private String platformCode;
+
+}

+ 2 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/dto/SetCustomerInfoTagDto.java

@@ -16,4 +16,6 @@ public class SetCustomerInfoTagDto implements Serializable {
     private Long salesPersonId;
 
     private Long serviceStaffId;
+
+    private String keepOwner;
 }

+ 155 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/CustomerCareVo.java

@@ -0,0 +1,155 @@
+package org.dromara.customer.domain.vo;
+
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.customer.domain.CustomerCare;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 客户关怀视图对象
+ *
+ * @author tys
+ * @date 2026-04-08
+ */
+@Data
+@AutoMapper(target = CustomerCare.class)
+public class CustomerCareVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    private Long id;
+
+    /**
+     * 收据/单据编号
+     */
+    private String receiptId;
+
+    /**
+     * 客户ID
+     */
+    private String customerId;
+
+    /**
+     * 客户名称
+     */
+    private String customerName;
+
+    /**
+     * 行业
+     */
+    private String profession;
+
+    /**
+     * 部门
+     */
+    private String department;
+
+    /**
+     * 销售员
+     */
+    private String salesman;
+
+    /**
+     * 人员编号
+     */
+    private String personnelNumber;
+
+    /**
+     * 联系人
+     */
+    private String contactPerson;
+
+    /**
+     * 电话
+     */
+    private String phone;
+
+    /**
+     * 固定电话
+     */
+    private String telephone;
+
+    /**
+     * 关注类型
+     */
+    private String concernType;
+
+    /**
+     * 金额
+     */
+    private BigDecimal amount;
+
+    /**
+     * 需求日期
+     */
+    private Date requirementDate;
+
+    /**
+     * 文件编号
+     */
+    private String fileNo;
+
+    /**
+     * 礼品描述
+     */
+    private String giftDesc;
+
+    /**
+     * 关注论据
+     */
+    private String concernArgument;
+
+    /**
+     * 审核状态(0-待审核,1-已通过,2-已驳回)
+     */
+    private Integer auditStatus;
+
+    /**
+     * 审核人ID
+     */
+    private Long auditPeople;
+
+    /**
+     * 审核日期
+     */
+    private Date auditDate;
+
+    /**
+     * 创建组织ID
+     */
+    private Long createOrgId;
+
+    /**
+     * 平台编码
+     */
+    private String platformCode;
+
+    /**
+     * 创建者
+     */
+    private Long createUserId;
+
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+
+    /**
+     * 更新者
+     */
+    private Long updateUserId;
+
+    /**
+     * 更新时间
+     */
+    private Date updateTime;
+
+}

+ 14 - 1
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/CustomerInfoVo.java

@@ -244,5 +244,18 @@ public class CustomerInfoVo implements Serializable {
 
     private List<CustomerInvoiceInfoVo> customerInvoiceInfoVoList;
 
-
+    /** 行业名称 */
+    private String industryName;
+    /** 企业类型名称 */
+    private String enterpriseTypeName;
+    /** 客户等级名称 */
+    private String customerLevelName;
+    /** 业务负责人名称 */
+    private String salesPersonName;
+    /** 客服支持名称 */
+    private String serviceStaffName;
+    /** 部门名称 */
+    private String deptName;
+    /** 合作状态名称 */
+    private String cooperationName;
 }

+ 36 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/CustomerListVo.java

@@ -50,6 +50,42 @@ public class CustomerListVo implements Serializable {
     /** 业务员名称 */
     private String salesPersonName;
 
+    /** 客服支持姓名 */
+    private String serviceStaffName;
+
+    /** 部门名称 */
+    private String deptName;
+
+    /** 企业类型名称 */
+    private String enterpriseTypeName;
+
+    /** 合作等级名称 */
+    private String customerLevelName;
+
+    /** 合作状态名称 */
+    private String cooperationName;
+
+    /** 归属公司名称 */
+    private String companyName;
+
+    /** 归属公司ID */
+    private Long belongCompanyId;
+
+    /** 客服支持ID */
+    private Long serviceStaffId;
+
+    /** 部门ID */
+    private Long belongingDepartmentId;
+
+    /** 企业类型ID */
+    private Long enterpriseTypeId;
+
+    /** 合作等级ID */
+    private Long customerLevelId;
+
+    /** 合作状态(原始码) */
+    private String cooperationStatus;
+
     /** 状态 */
     private String status;
 }

+ 85 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/SalesresultanalyzeVo.java

@@ -0,0 +1,85 @@
+package org.dromara.customer.domain.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.github.linpeilie.annotations.AutoMapper;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.dromara.customer.domain.SalesResultAnalyze;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 销售结果分析视图对象 salesresultanalyze
+ *
+ * @author tys
+ * @date 2026-04-21
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = SalesResultAnalyze.class)
+@Schema(description = "销售结果分析视图对象")
+public class SalesresultanalyzeVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键,自增ID
+     */
+    @ExcelProperty(value = "主键,自增ID")
+    @Schema(description = "主键,自增ID")
+    private Long id;
+
+    /**
+     * 数据类型
+     */
+    @ExcelProperty(value = "数据类型")
+    @Schema(description = "数据类型")
+    private Integer dataType;
+
+    /**
+     * 对象编号
+     */
+    @ExcelProperty(value = "对象编号")
+    @Schema(description = "对象编号")
+    private String objectNo;
+
+    /**
+     * 成交结果
+     */
+    @ExcelProperty(value = "成交结果")
+    @Schema(description = "成交结果")
+    private Integer dealResult;
+
+    /**
+     * 胜利总结
+     */
+    @ExcelProperty(value = "胜利总结")
+    @Schema(description = "胜利总结")
+    private String winSumUp;
+
+    /**
+     * 失败原因
+     */
+    @ExcelProperty(value = "失败原因")
+    @Schema(description = "失败原因")
+    private String loseReason;
+
+    /**
+     * 文件编号
+     */
+    @ExcelProperty(value = "文件编号")
+    @Schema(description = "文件编号")
+    private String fileNo;
+
+    /**
+     * 创建时间
+     */
+    @ExcelProperty(value = "创建时间")
+    @Schema(description = "创建时间")
+    private Date createTime;
+
+}

+ 55 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/WorkbenchStatVo.java

@@ -0,0 +1,55 @@
+package org.dromara.customer.domain.vo;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 工作台统计VO
+ */
+@Data
+public class WorkbenchStatVo {
+
+    /**
+     * 商机统计
+     */
+    private List<StatItem> opportunityStats;
+
+    /**
+     * 年度入围统计
+     */
+    private List<StatItem> selectionStats;
+
+    /**
+     * 商机漏斗
+     */
+    private List<StatItem> opportunityFunnel;
+
+    /**
+     * 客户漏斗
+     */
+    private List<StatItem> customerFunnel;
+
+    /**
+     * 访销统计
+     */
+    private List<StatItem> visitStats;
+
+    @Data
+    public static class StatItem {
+        private String label;
+        private String value;
+        private String unit;
+
+        public StatItem(String label, String value) {
+            this.label = label;
+            this.value = value;
+        }
+
+        public StatItem(String label, String value, String unit) {
+            this.label = label;
+            this.value = value;
+            this.unit = unit;
+        }
+    }
+}

+ 15 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/CustomerCareMapper.java

@@ -0,0 +1,15 @@
+package org.dromara.customer.mapper;
+
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.customer.domain.CustomerCare;
+import org.dromara.customer.domain.vo.CustomerCareVo;
+
+/**
+ * 客户关怀Mapper接口
+ *
+ * @author yoe
+ * @date 2026-04-08
+ */
+public interface CustomerCareMapper extends BaseMapperPlus<CustomerCare, CustomerCareVo> {
+
+}

+ 7 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/CustomerInfoMapper.java

@@ -3,6 +3,7 @@ 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.CustomerInfoBo;
 import org.dromara.customer.domain.bo.CustomerListBo;
 import org.dromara.customer.domain.bo.MessagePublishCustomerBo;
 import org.dromara.customer.domain.vo.CustomerInfoVo;
@@ -29,4 +30,10 @@ public interface CustomerInfoMapper extends BaseMapperPlus<CustomerInfo, Custome
      */
     Page<CustomerListVo> selectCustomerListPage(@Param("page") Page<CustomerListVo> page,
                                                  @Param("bo") CustomerListBo bo);
+
+    /**
+     * 公海客户列表-分页查询(基于 customer_info 表,筛选无业务负责人的客户)
+     */
+    Page<CustomerInfoVo> selectHighSeasVoPage(@Param("page") Page<CustomerInfoVo> page,
+                                                @Param("bo") CustomerInfoBo bo);
 }

+ 15 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/SalesresultanalyzeMapper.java

@@ -0,0 +1,15 @@
+package org.dromara.customer.mapper;
+
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.customer.domain.SalesResultAnalyze;
+import org.dromara.customer.domain.vo.SalesresultanalyzeVo;
+
+/**
+ * 销售结果分析Mapper接口
+ *
+ * @author Antigravity
+ * @date 2026-04-21
+ */
+public interface SalesresultanalyzeMapper extends BaseMapperPlus<SalesResultAnalyze, SalesresultanalyzeVo> {
+
+}

+ 49 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/ICustomerCareService.java

@@ -0,0 +1,49 @@
+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.CustomerCareBo;
+import org.dromara.customer.domain.vo.CustomerCareVo;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 客户关怀Service接口
+ *
+ * @author tys
+ * @date 2026-04-08
+ */
+public interface ICustomerCareService {
+
+    /**
+     * 查询客户关怀
+     */
+    CustomerCareVo queryById(Long id);
+
+    /**
+     * 查询客户关怀分页列表
+     */
+    TableDataInfo<CustomerCareVo> queryPageList(CustomerCareBo bo, PageQuery pageQuery);
+
+    /**
+     * 查询客户关怀列表
+     */
+    List<CustomerCareVo> queryList(CustomerCareBo bo);
+
+    /**
+     * 新增客户关怀
+     */
+    Boolean insertByBo(CustomerCareBo bo);
+
+    /**
+     * 修改客户关怀
+     */
+    Boolean updateByBo(CustomerCareBo bo);
+
+    /**
+     * 批量删除客户关怀
+     */
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+
+}

+ 9 - 4
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/ICustomerInfoService.java

@@ -4,10 +4,7 @@ import com.baomidou.mybatisplus.extension.service.IService;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.customer.domain.CustomerInfo;
-import org.dromara.customer.domain.bo.CustomerInfoBo;
-import org.dromara.customer.domain.bo.CustomerListBo;
-import org.dromara.customer.domain.bo.CustomerRegisterBo;
-import org.dromara.customer.domain.bo.MessagePublishCustomerBo;
+import org.dromara.customer.domain.bo.*;
 import org.dromara.customer.domain.vo.ContractVo;
 import org.dromara.customer.domain.vo.CustomerInfoVo;
 import org.dromara.customer.domain.vo.CustomerListVo;
@@ -121,6 +118,14 @@ public interface ICustomerInfoService extends IService<CustomerInfo> {
     /*客户转移客服人员*/
     int transferServiceStaff(List<Long> customerIds, Long serviceStaffId);
 
+    /**
+     * 认领公海客户
+     *
+     * @param claimBo 认领信息
+     * @return 结果
+     */
+    Boolean claimPool(CustomerClaimBo claimBo);
+
 
     Map<Long, String> selectCustomerNameByIds(Set<Long> ids);
 

+ 53 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/ISalesresultanalyzeService.java

@@ -0,0 +1,53 @@
+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.SalesresultanalyzeBo;
+import org.dromara.customer.domain.vo.SalesresultanalyzeVo;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 销售结果分析Service接口
+ *
+ * @author tys
+ * @date 2026-04-21
+ */
+public interface ISalesresultanalyzeService {
+
+    /**
+     * 查询销售结果分析
+     */
+    SalesresultanalyzeVo queryById(Long id);
+
+    /**
+     * 查询销售结果分析列表
+     */
+    TableDataInfo<SalesresultanalyzeVo> queryPageList(SalesresultanalyzeBo bo, PageQuery pageQuery);
+
+    /**
+     * 查询销售结果分析列表
+     */
+    List<SalesresultanalyzeVo> queryList(SalesresultanalyzeBo bo);
+
+    /**
+     * 新增销售结果分析
+     */
+    Boolean insertByBo(SalesresultanalyzeBo bo);
+
+    /**
+     * 修改销售结果分析
+     */
+    Boolean updateByBo(SalesresultanalyzeBo bo);
+
+    /**
+     * 校验并批量删除销售结果分析信息
+     */
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+
+    /**
+     * 根据对象编号查询结果分析
+     */
+    SalesresultanalyzeVo queryByObjectNo(String objectNo);
+}

+ 13 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/IWorkbenchService.java

@@ -0,0 +1,13 @@
+package org.dromara.customer.service;
+
+import org.dromara.customer.domain.vo.WorkbenchStatVo;
+
+/**
+ * 工作台Service接口
+ */
+public interface IWorkbenchService {
+    /**
+     * 获取工作台统计数据
+     */
+    WorkbenchStatVo getStat();
+}

+ 117 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/CustomerCareServiceImpl.java

@@ -0,0 +1,117 @@
+package org.dromara.customer.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+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.customer.domain.CustomerCare;
+import org.dromara.customer.domain.bo.CustomerCareBo;
+import org.dromara.customer.domain.vo.CustomerCareVo;
+import org.dromara.customer.mapper.CustomerCareMapper;
+import org.dromara.customer.service.ICustomerCareService;
+import org.springframework.stereotype.Service;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 客户关怀Service业务层处理
+ *
+ * @author yoe
+ * @date 2026-04-08
+ */
+@RequiredArgsConstructor
+@Service
+public class CustomerCareServiceImpl implements ICustomerCareService {
+
+    private final CustomerCareMapper baseMapper;
+
+    /**
+     * 查询客户关怀
+     */
+    @Override
+    public CustomerCareVo queryById(Long id) {
+        return baseMapper.selectVoById(id);
+    }
+
+    /**
+     * 查询客户关怀分页列表
+     */
+    @Override
+    public TableDataInfo<CustomerCareVo> queryPageList(CustomerCareBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<CustomerCare> lqw = buildQueryWrapper(bo);
+        Page<CustomerCareVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 查询客户关怀列表
+     */
+    @Override
+    public List<CustomerCareVo> queryList(CustomerCareBo bo) {
+        LambdaQueryWrapper<CustomerCare> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<CustomerCare> buildQueryWrapper(CustomerCareBo bo) {
+        LambdaQueryWrapper<CustomerCare> lqw = Wrappers.lambdaQuery();
+        lqw.like(StringUtils.isNotBlank(bo.getReceiptId()), CustomerCare::getReceiptId, bo.getReceiptId());
+        lqw.like(StringUtils.isNotBlank(bo.getCustomerName()), CustomerCare::getCustomerName, bo.getCustomerName());
+        lqw.like(StringUtils.isNotBlank(bo.getContactPerson()), CustomerCare::getContactPerson, bo.getContactPerson());
+        lqw.eq(StringUtils.isNotBlank(bo.getConcernType()), CustomerCare::getConcernType, bo.getConcernType());
+        lqw.eq(bo.getAuditStatus() != null, CustomerCare::getAuditStatus, bo.getAuditStatus());
+        lqw.eq(StringUtils.isNotBlank(bo.getPlatformCode()), CustomerCare::getPlatformCode, bo.getPlatformCode());
+        if ("mine".equals(bo.getScope())) {
+            lqw.eq(CustomerCare::getCreateBy, LoginHelper.getUserId());
+        }
+        lqw.orderByDesc(CustomerCare::getId);
+        return lqw;
+    }
+
+    /**
+     * 新增客户关怀
+     */
+    @Override
+    public Boolean insertByBo(CustomerCareBo bo) {
+        CustomerCare add = MapstructUtils.convert(bo, CustomerCare.class);
+        validEntityBeforeSave(add);
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            bo.setId(add.getId());
+        }
+        return flag;
+    }
+
+    /**
+     * 修改客户关怀
+     */
+    @Override
+    public Boolean updateByBo(CustomerCareBo bo) {
+        CustomerCare update = MapstructUtils.convert(bo, CustomerCare.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    /**
+     * 保存前的数据校验
+     */
+    private void validEntityBeforeSave(CustomerCare entity) {
+        // TODO 做一些数据校验,如唯一约束
+    }
+
+    /**
+     * 批量删除客户关怀
+     */
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if (isValid) {
+            // TODO 做一些业务上的校验,判断是否需要校验
+        }
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+}

+ 17 - 2
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/CustomerContactServiceImpl.java

@@ -88,11 +88,24 @@ public class CustomerContactServiceImpl extends ServiceImpl<CustomerContactMappe
         List<CustomerContactVo> records = result.getRecords();
         if (ObjectUtil.isNotEmpty(records)) {
             Set<Long> customerIds = records.stream().map(CustomerContactVo::getCustomerId).collect(Collectors.toSet());
-            List<CustomerInfo> customerInfos = customerInfoMapper.selectByIds(customerIds);
+            List<CustomerInfo> customerInfos = customerInfoMapper.selectList(new LambdaQueryWrapper<CustomerInfo>().in(CustomerInfo::getId, customerIds));
             Map<Long, String> customerNameMap = customerInfos.stream().collect(Collectors.toMap(CustomerInfo::getId, CustomerInfo::getCustomerName));
 
+            // 获取部门 ID 列表
+            Set<Long> deptIds = records.stream()
+                .map(CustomerContactVo::getDeptId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+
+            Map<Long, String> deptNameMap = deptIds.isEmpty() 
+                ? new HashMap<>() 
+                : remoteDeptService.selectDeptNameByIds(deptIds);
+
             records.forEach(item -> {
                 item.setCustomerName(customerNameMap.get(item.getCustomerId()));
+                if (item.getDeptId() != null) {
+                    item.setDeptName(deptNameMap.get(item.getDeptId()));
+                }
             });
         }
         return TableDataInfo.build(result);
@@ -129,6 +142,8 @@ public class CustomerContactServiceImpl extends ServiceImpl<CustomerContactMappe
         lqw.eq(StringUtils.isNotBlank(bo.getAddressCounty()), CustomerContact::getAddressCounty, bo.getAddressCounty());
         lqw.eq(StringUtils.isNotBlank(bo.getProvincialCityCounty()), CustomerContact::getProvincialCityCounty, bo.getProvincialCityCounty());
         lqw.eq(StringUtils.isNotBlank(bo.getStatus()), CustomerContact::getStatus, bo.getStatus());
+        lqw.like(StringUtils.isNotBlank(bo.getDeptName()), CustomerContact::getDeptName, bo.getDeptName());
+        lqw.like(StringUtils.isNotBlank(bo.getRoleName()), CustomerContact::getRoleName, bo.getRoleName());
 
         if (ObjectUtil.isNotEmpty(bo.getCustomerName())) {
             CustomerInfoVo customerInfoVo = customerInfoMapper.selectVoOne(new LambdaQueryWrapper<CustomerInfo>().eq(CustomerInfo::getCustomerName, bo.getCustomerName()));
@@ -295,7 +310,7 @@ public class CustomerContactServiceImpl extends ServiceImpl<CustomerContactMappe
         }
 
         // 3. 【关键优化】批量查询
-        List<CustomerContact> contactList = baseMapper.selectBatchIds(ids);
+        List<CustomerContact> contactList = baseMapper.selectList(new LambdaQueryWrapper<CustomerContact>().in(CustomerContact::getId, ids));
 
         if (contactList.isEmpty()) {
             return false;

+ 100 - 28
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/CustomerInfoServiceImpl.java

@@ -34,6 +34,7 @@ import org.dromara.customer.utils.qcc.domain.CompanyInfoResponse;
 import org.dromara.system.api.*;
 import org.dromara.system.api.domain.bo.RemoteUserBo;
 import org.dromara.system.api.domain.vo.RemoteDeptVo;
+import org.dromara.system.api.domain.vo.RemoteDictDataVo;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -75,6 +76,12 @@ public class CustomerInfoServiceImpl extends ServiceImpl<CustomerInfoMapper, Cus
     @DubboReference
     private RemoteCreditLevelService remoteCreditLevelService;
 
+    @DubboReference
+    private RemoteComCustomerLevelService remoteComCustomerLevelService;
+
+    @DubboReference
+    private RemoteDictService remoteDictService;
+
     private static final String CUSTOMER_NO_KEY = "customer_info:customer_no";
     private static final String CONTACT_NO_KEY = "customer_contact:contact_no";
 
@@ -88,6 +95,7 @@ public class CustomerInfoServiceImpl extends ServiceImpl<CustomerInfoMapper, Cus
     private final CustomerInfoTagMapper customerInfoTagMapper;
     private final CustomerContractMapper customerContractMapper;
     private final CustomerDeptMapper customerDeptMapper;
+    private final CustomerDictMapper customerDictMapper;
 
 
     /**
@@ -231,7 +239,6 @@ public class CustomerInfoServiceImpl extends ServiceImpl<CustomerInfoMapper, Cus
                     IndustryCategoryVo::getIndustryCategoryName,
                     (e, r) -> e
                 ));
-            records.forEach(vo -> vo.setIndustryCategory(industryMap.get(vo.getIndustryCategoryId())));
         }
 
         // === 2. 提取客户ID,查询销售信息 ===
@@ -271,7 +278,6 @@ public class CustomerInfoServiceImpl extends ServiceImpl<CustomerInfoMapper, Cus
                 (existing, replacement) -> existing // 若有重复,保留第一个
             ));
 
-        // === 4. 收集人员和部门ID ===
         Set<Long> staffIds = new HashSet<>();
         Set<Long> deptIds = new HashSet<>();
 
@@ -465,38 +471,73 @@ public class CustomerInfoServiceImpl extends ServiceImpl<CustomerInfoMapper, Cus
      */
     @Override
     public TableDataInfo<CustomerListVo> queryCustomerListPage(CustomerListBo bo, PageQuery pageQuery) {
-        // 设置平台标识(暂时注释掉)
-        // if (StringUtils.isBlank(bo.getPlatformCode())) {
-        //     bo.setPlatformCode(PlatformContext.getPlatform());
-        // }
         Page<CustomerListVo> page = baseMapper.selectCustomerListPage(pageQuery.build(), bo);
 
         // 补充业务员名称和信用等级名称
         List<CustomerListVo> records = page.getRecords();
         if (CollUtil.isNotEmpty(records)) {
-            // 收集业务员ID
-            Set<Long> staffIds = records.stream()
-                .map(CustomerListVo::getSalesPersonId)
-                .filter(Objects::nonNull)
-                .collect(Collectors.toSet());
-
-            // 收集信用等级ID(从 credit_management_id 字段)
-            Set<Long> creditLevelIds = records.stream()
-                .map(CustomerListVo::getCreditLevelId)
-                .filter(Objects::nonNull)
-                .collect(Collectors.toSet());
-
-            // 批量查询业务员名称
-            if (!staffIds.isEmpty()) {
-                Map<Long, String> staffMap = remoteComStaffService.selectStaffNameByIds(staffIds);
-                records.forEach(vo -> vo.setSalesPersonName(staffMap.get(vo.getSalesPersonId())));
-            }
+            // 收集各种ID
+            Set<Long> staffIds = new HashSet<>();
+            Set<Long> creditLevelIds = new HashSet<>();
+            Set<Long> companyIds = new HashSet<>();
+            Set<Long> enterpriseTypeIds = new HashSet<>();
+            Set<Long> customerLevelIds = new HashSet<>();
+            Set<Long> deptIds = new HashSet<>();
+
+            records.forEach(vo -> {
+                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.getEnterpriseTypeId() != null) enterpriseTypeIds.add(vo.getEnterpriseTypeId());
+                if (vo.getCustomerLevelId() != null) customerLevelIds.add(vo.getCustomerLevelId());
+                if (vo.getBelongingDepartmentId() != null) deptIds.add(vo.getBelongingDepartmentId());
+            });
 
-            // 批量查询信用等级名称
-            if (!creditLevelIds.isEmpty()) {
-                Map<Long, String> creditLevelMap = remoteCreditLevelService.selectCreditLevelNameByIds(creditLevelIds);
-                records.forEach(vo -> vo.setCreditLevelName(creditLevelMap.get(vo.getCreditLevelId())));
-            }
+            // 1. 批量查询业务员和客服名称 (远程调用)
+            Map<Long, String> staffMap = staffIds.isEmpty() ? Collections.emptyMap() : remoteErpStaffService.selectStaffNameByIds(staffIds);
+
+            // 2. 批量查询信用等级名称 (远程调用)
+            Map<Long, String> creditLevelMap = creditLevelIds.isEmpty() ? Collections.emptyMap() : remoteCreditLevelService.selectCreditLevelNameByIds(creditLevelIds);
+
+            // 3. 批量查询公司名称 (远程调用)
+            Map<Long, String> companyMap = companyIds.isEmpty() ? Collections.emptyMap() : remoteComCompanyService.selectCompanyNameByIds(companyIds);
+
+            // 4. 批量查询企业类型名称 (从客户字典表查询)
+            Map<Long, String> enterpriseTypeMap = enterpriseTypeIds.isEmpty() ? Collections.emptyMap() :
+                customerDictMapper.selectList(new LambdaQueryWrapper<CustomerDict>().in(CustomerDict::getId, enterpriseTypeIds)).stream()
+                    .collect(Collectors.toMap(d -> Long.valueOf(d.getId()), CustomerDict::getName, (k1, k2) -> k1));
+
+            // 5. 批量查询客户/合作等级名称 (远程调用)
+            Map<Long, String> customerLevelMap = customerLevelIds.isEmpty() ? Collections.emptyMap() : remoteComCustomerLevelService.selectCustomerLevelNameByIds(customerLevelIds);
+
+            // 6. 批量查询部门名称 (远程调用)
+            Map<Long, String> deptMap = deptIds.isEmpty() ? Collections.emptyMap() : remoteErpDeptService.selectDeptNameByIds(deptIds);
+
+            // 7. 批量查询合作状态名称 (从客户字典表查询 code='COOPERATION_STATUS')
+            List<CustomerDict> cooperationDicts = customerDictMapper.selectList(new LambdaQueryWrapper<CustomerDict>()
+                .eq(CustomerDict::getCode, "COOPERATION_STATUS"));
+            Map<String, String> cooperationMap = cooperationDicts.stream()
+                .collect(Collectors.toMap(
+                    d -> {
+                        String val = d.getValue();
+                        return (val == null || val.isEmpty()) ? (d.getCodeIndex() != null ? String.valueOf(d.getCodeIndex()) : "") : val;
+                    },
+                    CustomerDict::getName,
+                    (k1, k2) -> k1
+                ));
+
+            // 8. 填充数据
+            records.forEach(vo -> {
+                vo.setSalesPersonName(staffMap.get(vo.getSalesPersonId()));
+                vo.setServiceStaffName(staffMap.get(vo.getServiceStaffId()));
+                vo.setCreditLevelName(creditLevelMap.get(vo.getCreditLevelId()));
+                vo.setCompanyName(companyMap.get(vo.getBelongCompanyId()));
+                vo.setEnterpriseTypeName(enterpriseTypeMap.get(vo.getEnterpriseTypeId()));
+                vo.setCustomerLevelName(customerLevelMap.get(vo.getCustomerLevelId()));
+                vo.setDeptName(deptMap.get(vo.getBelongingDepartmentId()));
+                vo.setCooperationName(cooperationMap.get(vo.getStatus()));
+            });
         }
 
         return TableDataInfo.build(page);
@@ -1318,6 +1359,37 @@ public class CustomerInfoServiceImpl extends ServiceImpl<CustomerInfoMapper, Cus
         return flag;
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean claimPool(CustomerClaimBo claimBo) {
+        Long customerId = claimBo.getId();
+        CustomerSalesInfo salesInfo = customerSalesInfoMapper.selectOne(new LambdaQueryWrapper<CustomerSalesInfo>()
+            .eq(CustomerSalesInfo::getCustomerId, customerId));
+
+        if (salesInfo == null) {
+            salesInfo = new CustomerSalesInfo();
+            salesInfo.setCustomerId(customerId);
+            salesInfo.setSalesPersonId(claimBo.getSalesPersonId());
+            salesInfo.setServiceStaffId(claimBo.getServiceStaffId());
+            salesInfo.setStatus("0");
+            return customerSalesInfoMapper.insert(salesInfo) > 0;
+        }
+
+        // 更新销售负责人和客服
+        salesInfo.setSalesPersonId(claimBo.getSalesPersonId());
+        salesInfo.setServiceStaffId(claimBo.getServiceStaffId());
+        boolean flag = customerSalesInfoMapper.updateById(salesInfo) > 0;
+
+        if (flag) {
+            // 认领成功后,将客户主表状态改为有效 (通常 0 代表有效/正常)
+            CustomerInfo customer = new CustomerInfo();
+            customer.setId(customerId);
+            customer.setStatus("0"); // 设置为有效客户
+            baseMapper.updateById(customer);
+        }
+        return flag;
+    }
+
     @Override
     public CustomerInfoVo selectCustomerByName(String customerName) {
         return baseMapper.selectVoOne(new LambdaQueryWrapper<CustomerInfo>().eq(CustomerInfo::getCustomerName, customerName));

+ 118 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/SalesresultanalyzeServiceImpl.java

@@ -0,0 +1,118 @@
+package org.dromara.customer.service.impl;
+
+import cn.hutool.core.bean.BeanUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.customer.domain.SalesResultAnalyze;
+import org.dromara.customer.domain.bo.SalesresultanalyzeBo;
+import org.dromara.customer.domain.vo.SalesresultanalyzeVo;
+import org.dromara.customer.mapper.SalesresultanalyzeMapper;
+import org.dromara.customer.service.ISalesresultanalyzeService;
+import org.springframework.stereotype.Service;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 销售结果分析Service业务层处理
+ *
+ * @author Antigravity
+ * @date 2026-04-21
+ */
+@RequiredArgsConstructor
+@Service
+public class SalesresultanalyzeServiceImpl implements ISalesresultanalyzeService {
+
+    private final SalesresultanalyzeMapper baseMapper;
+
+    /**
+     * 查询销售结果分析
+     */
+    @Override
+    public SalesresultanalyzeVo queryById(Long id){
+        return baseMapper.selectVoById(id);
+    }
+
+    /**
+     * 查询销售结果分析列表
+     */
+    @Override
+    public TableDataInfo<SalesresultanalyzeVo> queryPageList(SalesresultanalyzeBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<SalesResultAnalyze> lqw = buildQueryWrapper(bo);
+        Page<SalesresultanalyzeVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 查询销售结果分析列表
+     */
+    @Override
+    public List<SalesresultanalyzeVo> queryList(SalesresultanalyzeBo bo) {
+        LambdaQueryWrapper<SalesResultAnalyze> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<SalesResultAnalyze> buildQueryWrapper(SalesresultanalyzeBo bo) {
+        LambdaQueryWrapper<SalesResultAnalyze> lqw = Wrappers.lambdaQuery();
+        lqw.eq(bo.getDataType() != null, SalesResultAnalyze::getDataType, bo.getDataType());
+        lqw.eq(bo.getObjectNo() != null, SalesResultAnalyze::getObjectNo, bo.getObjectNo());
+        lqw.eq(bo.getDealResult() != null, SalesResultAnalyze::getDealResult, bo.getDealResult());
+        return lqw;
+    }
+
+    /**
+     * 新增销售结果分析
+     */
+    @Override
+    public Boolean insertByBo(SalesresultanalyzeBo bo) {
+        SalesResultAnalyze add = BeanUtil.toBean(bo, SalesResultAnalyze.class);
+        validEntityBeforeSave(add);
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            bo.setId(add.getId());
+        }
+        return flag;
+    }
+
+    /**
+     * 修改销售结果分析
+     */
+    @Override
+    public Boolean updateByBo(SalesresultanalyzeBo bo) {
+        SalesResultAnalyze update = BeanUtil.toBean(bo, SalesResultAnalyze.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    /**
+     * 保存前的数据校验
+     */
+    private void validEntityBeforeSave(SalesResultAnalyze entity){
+        // 在此处添加一些校验逻辑
+    }
+
+    /**
+     * 批量删除销售结果分析
+     */
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if(isValid){
+            // 在此处添加一些校验逻辑
+        }
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+
+    /**
+     * 根据对象编号查询结果分析
+     */
+    @Override
+    public SalesresultanalyzeVo queryByObjectNo(String objectNo) {
+        return baseMapper.selectVoOne(Wrappers.lambdaQuery(SalesResultAnalyze.class)
+            .eq(SalesResultAnalyze::getObjectNo, objectNo)
+            .last("limit 1"));
+    }
+}

+ 273 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/WorkbenchServiceImpl.java

@@ -0,0 +1,273 @@
+package org.dromara.customer.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import lombok.RequiredArgsConstructor;
+import org.dromara.customer.domain.*;
+import org.dromara.customer.domain.vo.WorkbenchStatVo;
+import org.dromara.customer.mapper.*;
+import org.dromara.customer.service.IWorkbenchService;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 工作台Service业务层处理
+ */
+@RequiredArgsConstructor
+@Service
+public class WorkbenchServiceImpl implements IWorkbenchService {
+
+    private final SalesleadsMapper salesleadsMapper;
+    private final SalesAnnualFinalizationMapper salesAnnualFinalizationMapper;
+    private final FollowUpLogMapper followUpLogMapper;
+    private final CustomerInfoMapper customerInfoMapper;
+    private final CustomerSalesInfoMapper customerSalesInfoMapper;
+    private final SalesresultanalyzeMapper salesresultanalyzeMapper;
+
+    @Override
+    public WorkbenchStatVo getStat() {
+        WorkbenchStatVo vo = new WorkbenchStatVo();
+        vo.setOpportunityStats(getOpportunityStats());
+        vo.setSelectionStats(getSelectionStats());
+        vo.setOpportunityFunnel(getOpportunityFunnel());
+        vo.setCustomerFunnel(getCustomerFunnel());
+        vo.setVisitStats(getVisitStats());
+        return vo;
+    }
+
+    private List<WorkbenchStatVo.StatItem> getOpportunityFunnel() {
+        // 1. 销售线索:iz_clue = 1
+        Long clueCount = salesleadsMapper.selectCount(new LambdaQueryWrapper<Salesleads>()
+            .eq(Salesleads::getIzClue, 1)
+            .eq(Salesleads::getDelFlag, "0"));
+
+        // 2. 商机:iz_clue = 0
+        Long opportunityCount = salesleadsMapper.selectCount(new LambdaQueryWrapper<Salesleads>()
+            .eq(Salesleads::getIzClue, 0)
+            .eq(Salesleads::getDelFlag, "0"));
+
+        // 3. 赢单:salesresultanalyze 中 dealResult = 1, dataType = 2 (商机)
+        Long winCount = salesresultanalyzeMapper.selectCount(new LambdaQueryWrapper<SalesResultAnalyze>()
+            .eq(SalesResultAnalyze::getDealResult, 1)
+            .eq(SalesResultAnalyze::getDataType, 2)
+            .eq(SalesResultAnalyze::getIsDelete, 0));
+
+        List<WorkbenchStatVo.StatItem> stats = new ArrayList<>();
+        stats.add(new WorkbenchStatVo.StatItem("销售线索", String.valueOf(clueCount), "个"));
+        stats.add(new WorkbenchStatVo.StatItem("商机", String.valueOf(opportunityCount), "个"));
+        stats.add(new WorkbenchStatVo.StatItem("赢单", String.valueOf(winCount), "个"));
+        return stats;
+    }
+
+    private List<WorkbenchStatVo.StatItem> getCustomerFunnel() {
+        // 1. 正式客户:在 sales_info 表中 sales_person_id 和 service_staff_id 都不为空
+        Long officialCount = customerSalesInfoMapper.selectCount(new LambdaQueryWrapper<CustomerSalesInfo>()
+            .isNotNull(CustomerSalesInfo::getSalesPersonId)
+            .isNotNull(CustomerSalesInfo::getServiceStaffId)
+            .eq(CustomerSalesInfo::getDelFlag, "0"));
+
+        // 2. 公海客户:总客户数 - 正式客户数 (或者只要有一个为空即为公海)
+        Long totalCount = customerInfoMapper.selectCount(new LambdaQueryWrapper<CustomerInfo>()
+            .eq(CustomerInfo::getDelFlag, "0"));
+        
+        Long publicCount = Math.max(0L, totalCount - officialCount);
+
+        // 3. 已成交:这里采用 Salesresultanalyze 中已成交的记录数作为“已成交”阶段的量度
+        Long dealCount = salesresultanalyzeMapper.selectCount(new LambdaQueryWrapper<SalesResultAnalyze>()
+            .eq(SalesResultAnalyze::getDealResult, 1)
+            .eq(SalesResultAnalyze::getIsDelete, 0));
+
+        List<WorkbenchStatVo.StatItem> stats = new ArrayList<>();
+        stats.add(new WorkbenchStatVo.StatItem("公海客户数", String.valueOf(publicCount), "个"));
+        stats.add(new WorkbenchStatVo.StatItem("正式客户", String.valueOf(officialCount), "个"));
+        stats.add(new WorkbenchStatVo.StatItem("已成交", String.valueOf(dealCount), "个"));
+        return stats;
+    }
+
+    private List<WorkbenchStatVo.StatItem> getVisitStats() {
+        // 统计本月数据
+        Calendar calendar = Calendar.getInstance();
+        calendar.set(Calendar.DAY_OF_MONTH, 1);
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        Date monthStart = calendar.getTime();
+
+        // 1. 拜访客户数:从 followuplog 中统计本月去重的客户
+        List<FollowUpLog> visitRecords = followUpLogMapper.selectList(new LambdaQueryWrapper<FollowUpLog>()
+            .ge(FollowUpLog::getCallDate, monthStart)
+            .eq(FollowUpLog::getIsDelete, 0));
+        long visitCustomerCount = visitRecords.stream()
+            .map(FollowUpLog::getCustomerName)
+            .filter(Objects::nonNull)
+            .distinct()
+            .count();
+
+        // 2. 拜访次数
+        int visitCount = visitRecords.size();
+
+        // 3. 销售金额:赢单项目的预算总和
+        List<SalesResultAnalyze> winList = salesresultanalyzeMapper.selectList(new LambdaQueryWrapper<SalesResultAnalyze>()
+            .eq(SalesResultAnalyze::getDealResult, 1)
+            .eq(SalesResultAnalyze::getIsDelete, 0));
+
+        BigDecimal totalAmount = BigDecimal.ZERO;
+        if (!winList.isEmpty()) {
+            List<String> winNos = winList.stream().map(SalesResultAnalyze::getObjectNo).collect(Collectors.toList());
+            List<Salesleads> wins = salesleadsMapper.selectList(new LambdaQueryWrapper<Salesleads>()
+                .in(Salesleads::getProjectNo, winNos)
+                .eq(Salesleads::getDelFlag, "0"));
+            totalAmount = wins.stream().map(Salesleads::getProjectBudget).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
+        }
+
+        List<WorkbenchStatVo.StatItem> stats = new ArrayList<>();
+        stats.add(new WorkbenchStatVo.StatItem("拜访客户数", String.valueOf(visitCustomerCount), ""));
+        stats.add(new WorkbenchStatVo.StatItem("拜访次数", String.valueOf(visitCount), ""));
+        stats.add(new WorkbenchStatVo.StatItem("销售金额", totalAmount.setScale(0, BigDecimal.ROUND_HALF_UP).toString(), ""));
+        return stats;
+    }
+
+    private List<WorkbenchStatVo.StatItem> getOpportunityStats() {
+        // 1. 查询所有商机 (izClue = 0)
+        List<Salesleads> list = salesleadsMapper.selectList(
+            new LambdaQueryWrapper<Salesleads>()
+                .eq(Salesleads::getIzClue, 0)
+                .eq(Salesleads::getDelFlag, "0")
+        );
+
+        // 2. 获取所有项目编号
+        List<String> projectNos = list.stream().map(Salesleads::getProjectNo).filter(Objects::nonNull).collect(Collectors.toList());
+
+        // 3. 查询跟进记录
+        Map<String, Date> lastFollowUpMap = new HashMap<>();
+        if (!projectNos.isEmpty()) {
+            List<FollowUpLog> logs = followUpLogMapper.selectList(
+                new LambdaQueryWrapper<FollowUpLog>()
+                    .in(FollowUpLog::getObjectNo, projectNos)
+                    .orderByDesc(FollowUpLog::getCallDate)
+            );
+            // 每个项目保留最后一次跟进时间
+            for (FollowUpLog log : logs) {
+                lastFollowUpMap.putIfAbsent(log.getObjectNo(), log.getCallDate());
+            }
+        }
+
+        // 4. 统计
+        int count = list.size();
+        BigDecimal amount = list.stream().map(Salesleads::getProjectBudget).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
+        int unFollow3 = 0;
+        int unFollow7 = 0;
+        int unFollowOver7 = 0;
+        int retention30 = 0;
+
+        Date now = new Date();
+        long nowMs = now.getTime();
+        long dayMs = 24 * 60 * 60 * 1000L;
+
+        for (Salesleads item : list) {
+            Date lastDate = lastFollowUpMap.get(item.getProjectNo());
+            if (lastDate == null) {
+                lastDate = item.getCreateTime(); // 如果没有跟进记录,则以创建时间为准
+            }
+
+            if (lastDate != null) {
+                long diffDays = (nowMs - lastDate.getTime()) / dayMs;
+                if (diffDays >= 3 && diffDays < 7) {
+                    unFollow3++;
+                } else if (diffDays == 7) {
+                    unFollow7++;
+                } else if (diffDays > 7) {
+                    unFollowOver7++;
+                }
+            }
+
+            if (item.getCreateTime() != null) {
+                long stayDays = (nowMs - item.getCreateTime().getTime()) / dayMs;
+                if (stayDays > 30) {
+                    retention30++;
+                }
+            }
+        }
+
+        List<WorkbenchStatVo.StatItem> stats = new ArrayList<>();
+        stats.add(new WorkbenchStatVo.StatItem("商机数", String.valueOf(count)));
+        stats.add(new WorkbenchStatVo.StatItem("金额(万)", amount.setScale(2, BigDecimal.ROUND_HALF_UP).toString()));
+        stats.add(new WorkbenchStatVo.StatItem("3天未跟进", String.valueOf(unFollow3)));
+        stats.add(new WorkbenchStatVo.StatItem("7天未跟进", String.valueOf(unFollow7)));
+        stats.add(new WorkbenchStatVo.StatItem("7天以上未跟进", String.valueOf(unFollowOver7)));
+        stats.add(new WorkbenchStatVo.StatItem("滞留超过30天", String.valueOf(retention30)));
+        return stats;
+    }
+
+    private List<WorkbenchStatVo.StatItem> getSelectionStats() {
+        // 1. 查询所有年度入围
+        List<SalesAnnualFinalization> list = salesAnnualFinalizationMapper.selectList(
+            new LambdaQueryWrapper<SalesAnnualFinalization>()
+                .eq(SalesAnnualFinalization::getIsDelete, 0)
+        );
+
+        // 2. 获取所有项目编号
+        List<String> projectNos = list.stream().map(SalesAnnualFinalization::getProjectNo).filter(Objects::nonNull).collect(Collectors.toList());
+
+        // 3. 查询跟进记录
+        Map<String, Date> lastFollowUpMap = new HashMap<>();
+        if (!projectNos.isEmpty()) {
+            List<FollowUpLog> logs = followUpLogMapper.selectList(
+                new LambdaQueryWrapper<FollowUpLog>()
+                    .in(FollowUpLog::getObjectNo, projectNos)
+                    .orderByDesc(FollowUpLog::getCallDate)
+            );
+            for (FollowUpLog log : logs) {
+                lastFollowUpMap.putIfAbsent(log.getObjectNo(), log.getCallDate());
+            }
+        }
+
+        // 4. 统计
+        int count = list.size();
+        BigDecimal amount = list.stream().map(SalesAnnualFinalization::getAmount).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
+        int unFollow3 = 0;
+        int unFollowOver3 = 0;
+        int unFollowOver7 = 0;
+        int retention30 = 0;
+
+        Date now = new Date();
+        long nowMs = now.getTime();
+        long dayMs = 24 * 60 * 60 * 1000L;
+
+        for (SalesAnnualFinalization item : list) {
+            Date lastDate = lastFollowUpMap.get(item.getProjectNo());
+            if (lastDate == null) {
+                lastDate = item.getCreateTime();
+            }
+
+            if (lastDate != null) {
+                long diffDays = (nowMs - lastDate.getTime()) / dayMs;
+                if (diffDays >= 3 && diffDays < 7) {
+                    unFollow3++;
+                    unFollowOver3++;
+                } else if (diffDays >= 7) {
+                    unFollowOver3++;
+                    unFollowOver7++;
+                }
+            }
+
+            if (item.getCreateTime() != null) {
+                long stayDays = (nowMs - item.getCreateTime().getTime()) / dayMs;
+                if (stayDays > 30) {
+                    retention30++;
+                }
+            }
+        }
+
+        List<WorkbenchStatVo.StatItem> stats = new ArrayList<>();
+        stats.add(new WorkbenchStatVo.StatItem("入围项目数", String.valueOf(count)));
+        stats.add(new WorkbenchStatVo.StatItem("金额(万)", amount.setScale(2, BigDecimal.ROUND_HALF_UP).toString()));
+        stats.add(new WorkbenchStatVo.StatItem("3天未跟进", String.valueOf(unFollow3)));
+        stats.add(new WorkbenchStatVo.StatItem("3天以上未跟进", String.valueOf(unFollowOver3)));
+        stats.add(new WorkbenchStatVo.StatItem("7天以上未跟进", String.valueOf(unFollowOver7)));
+        stats.add(new WorkbenchStatVo.StatItem("滞留超过30天", String.valueOf(retention30)));
+        return stats;
+    }
+}

+ 7 - 0
ruoyi-modules/ruoyi-customer/src/main/resources/mapper/customer/CrmCustomerCareMapper.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/xml/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.customer.mapper.CustomerCareMapper">
+
+</mapper>

+ 11 - 0
ruoyi-modules/ruoyi-customer/src/main/resources/mapper/customer/CustomerInfoMapper.xml

@@ -62,6 +62,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             csi.credit_management_id AS creditLevelId,
             csi.accounts_receivable AS accountsReceivable,
             csi.sales_person_id AS salesPersonId,
+            ci.belong_company_id AS belongCompanyId,
+            ci.customer_type_id AS enterpriseTypeId,
+            ci.customer_level_id AS customerLevelId,
+            csi.service_staff_id AS serviceStaffId,
+            csi.belonging_department_id AS belongingDepartmentId,
             ci.status
         FROM customer_info ci
         LEFT JOIN customer_sales_info csi ON ci.id = csi.customer_id AND csi.del_flag = '0'
@@ -86,12 +91,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="bo.salesPersonId != null">
                 AND csi.sales_person_id = #{bo.salesPersonId}
             </if>
+            <if test="bo.serviceStaffId != null">
+                AND csi.service_staff_id = #{bo.serviceStaffId}
+            </if>
             <if test="bo.yearSalesMin != null">
                 AND csi.credit_amount &gt;= #{bo.yearSalesMin}
             </if>
             <if test="bo.yearSalesMax != null">
                 AND csi.credit_amount &lt;= #{bo.yearSalesMax}
             </if>
+            <if test="bo.isHighSeas != null and bo.isHighSeas == 'true'">
+                AND (csi.sales_person_id IS NULL AND csi.service_staff_id IS NULL)
+            </if>
         </where>
         ORDER BY ci.id DESC
     </select>

+ 18 - 1
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/dubbo/RemoteComCustomerLevelServiceImpl.java

@@ -3,13 +3,19 @@ package org.dromara.system.dubbo;
 import cn.hutool.core.bean.BeanUtil;
 import lombok.RequiredArgsConstructor;
 import org.apache.dubbo.config.annotation.DubboService;
-import org.dromara.common.core.utils.MapstructUtils;
 import org.dromara.system.api.RemoteComCustomerLevelService;
 import org.dromara.system.api.domain.vo.RemoteComCustomerLevelVo;
 import org.dromara.system.domain.vo.ComCustomerLevelVo;
 import org.dromara.system.service.IComCustomerLevelService;
 import org.springframework.stereotype.Service;
 
+import cn.hutool.core.collection.CollUtil;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
 @RequiredArgsConstructor
 @Service
 @DubboService
@@ -22,4 +28,15 @@ public class RemoteComCustomerLevelServiceImpl implements RemoteComCustomerLevel
         ComCustomerLevelVo comCustomerLevelVo = comCustomerLevelService.selectCustomerLevelByName(levelName);
         return BeanUtil.toBean(comCustomerLevelVo, RemoteComCustomerLevelVo.class);
     }
+
+    @Override
+    public Map<Long, String> selectCustomerLevelNameByIds(Set<Long> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return Collections.emptyMap();
+        }
+        List<ComCustomerLevelVo> list = comCustomerLevelService.listByIds(ids).stream()
+            .map(entity -> BeanUtil.toBean(entity, ComCustomerLevelVo.class))
+            .collect(Collectors.toList());
+        return list.stream().collect(Collectors.toMap(ComCustomerLevelVo::getId, ComCustomerLevelVo::getLevelName, (v1, v2) -> v1));
+    }
 }