沐梦. 15 часов назад
Родитель
Сommit
6dc79f014f

+ 87 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/BusinessTaskController.java

@@ -0,0 +1,87 @@
+package org.dromara.customer.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.excel.utils.ExcelUtil;
+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.BusinessTaskBo;
+import org.dromara.customer.domain.vo.BusinessTaskVo;
+import org.dromara.customer.service.IBusinessTaskService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import jakarta.servlet.http.HttpServletResponse;
+
+import java.util.List;
+
+/**
+ * 综合任务管理控制器
+ *
+ * @author Antigravity
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/businessTask")
+public class BusinessTaskController extends BaseController {
+
+    private final IBusinessTaskService businessTaskService;
+
+    /**
+     * 查询列表
+     */
+    @SaCheckPermission("customer:businessTask:list")
+    @GetMapping("/list")
+    public TableDataInfo<BusinessTaskVo> list(BusinessTaskBo bo, PageQuery pageQuery) {
+        return businessTaskService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 导出
+     */
+    @SaCheckPermission("customer:businessTask:export")
+    @PostMapping("/export")
+    public void export(BusinessTaskBo bo, HttpServletResponse response) {
+        List<BusinessTaskVo> list = businessTaskService.queryList(bo);
+        ExcelUtil.exportExcel(list, "综合任务数据", BusinessTaskVo.class, response);
+    }
+
+    /**
+     * 获取详情
+     */
+    @SaCheckPermission("customer:businessTask:query")
+    @GetMapping("/{id}")
+    public R<BusinessTaskVo> getInfo(@PathVariable Long id) {
+        return R.ok(businessTaskService.queryById(id));
+    }
+
+    /**
+     * 新增
+     */
+    @SaCheckPermission("customer:businessTask:add")
+    @PostMapping()
+    public R<Void> add(@RequestBody BusinessTaskBo bo) {
+        return toAjax(businessTaskService.insertByBo(bo));
+    }
+
+    /**
+     * 修改
+     */
+    @SaCheckPermission("customer:businessTask:edit")
+    @PutMapping()
+    public R<Void> edit(@RequestBody BusinessTaskBo bo) {
+        return toAjax(businessTaskService.updateByBo(bo));
+    }
+
+    /**
+     * 删除
+     */
+    @SaCheckPermission("customer:businessTask:remove")
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@PathVariable Long[] ids) {
+        return toAjax(businessTaskService.deleteWithValidByIds(List.of(ids), true));
+    }
+}

+ 141 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/BusinessTask.java

@@ -0,0 +1,141 @@
+package org.dromara.customer.domain;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+
+import java.io.Serial;
+import java.math.BigDecimal;
+
+/**
+ * 综合任务管理对象 crm_business_task
+ *
+ * @author Antigravity
+ * @date 2026-04-23
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("crm_business_task")
+public class BusinessTask extends BaseEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 任务类型 (0:年度, 1:季度, 2:月度)
+     */
+    private Integer taskType;
+
+    /**
+     * 父级ID
+     */
+    private Long parentId;
+
+    /**
+     * 年份
+     */
+    private Integer year;
+
+    /**
+     * 季度 (1-4)
+     */
+    private Integer quarter;
+
+    /**
+     * 月份 (1-12)
+     */
+    private Integer month;
+
+    /**
+     * 部门ID
+     */
+    private Long deptId;
+
+    /**
+     * 行业条线
+     */
+    private String industryLine;
+
+    /**
+     * 业务人员ID
+     */
+    private Long salesUserId;
+
+    /**
+     * 客服/服务人员ID
+     */
+    private Long serviceUserId;
+
+    /**
+     * 目标任务金额(元)
+     */
+    private BigDecimal targetTask;
+
+    /**
+     * 冲刺任务金额(元)
+     */
+    private BigDecimal sprintTask;
+
+    /**
+     * 实际完成金额(元)
+     */
+    private BigDecimal actualAchievement;
+
+    /**
+     * 占比(%)
+     */
+    private BigDecimal targetPercentage;
+
+    /**
+     * 核心客户
+     */
+    private String coreCustomer;
+
+    /**
+     * 重点项目
+     */
+    private String keyProject;
+
+    /**
+     * 潜在商机
+     */
+    private String potentialOpportunity;
+
+    /**
+     * 上年/同期目标(元)
+     */
+    private BigDecimal lastYearTarget;
+
+    /**
+     * 上年/同期实绩(元)
+     */
+    private BigDecimal lastYearActual;
+
+    /**
+     * 状态 (0正常 1停用)
+     */
+    private String status;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 租户ID
+     */
+    private String tenantId;
+
+    /**
+     * 平台标识
+     */
+    private String platformCode;
+
+}

+ 77 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/bo/BusinessTaskBo.java

@@ -0,0 +1,77 @@
+package org.dromara.customer.domain.bo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * 综合任务业务对象
+ *
+ * @author Antigravity
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Schema(description = "综合任务业务对象")
+public class BusinessTaskBo extends BaseEntity {
+
+    @Schema(description = "主键ID")
+    private Long id;
+
+    @Schema(description = "任务类型 (0:年度, 1:季度, 2:月度)")
+    private Integer taskType;
+
+    @Schema(description = "父级ID")
+    private Long parentId;
+
+    @Schema(description = "年份")
+    private Integer year;
+
+    @Schema(description = "季度")
+    private Integer quarter;
+
+    @Schema(description = "月份")
+    private Integer month;
+
+    @Schema(description = "部门ID")
+    private Long deptId;
+
+    @Schema(description = "行业条线")
+    private String industryLine;
+
+    @Schema(description = "业务人员ID")
+    private Long salesUserId;
+
+    @Schema(description = "客服人员ID")
+    private Long serviceUserId;
+
+    @Schema(description = "目标任务金额")
+    private BigDecimal targetTask;
+
+    @Schema(description = "冲刺任务金额")
+    private BigDecimal sprintTask;
+
+    @Schema(description = "实际完成金额")
+    private BigDecimal actualAchievement;
+
+    @Schema(description = "核心客户")
+    private String coreCustomer;
+
+    @Schema(description = "重点项目")
+    private String keyProject;
+
+    @Schema(description = "备注")
+    private String remark;
+
+    @Schema(description = "子任务/任务分解")
+    private List<BusinessTaskBo> details;
+
+    /** 搜索辅助字段 */
+    @Schema(description = "部门名称")
+    private String deptName;
+    @Schema(description = "业务员名称")
+    private String salesName;
+}

+ 121 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/BusinessTaskVo.java

@@ -0,0 +1,121 @@
+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 lombok.Data;
+import org.dromara.common.translation.annotation.Translation;
+import org.dromara.common.translation.constant.TransConstant;
+import org.dromara.customer.domain.BusinessTask;
+
+import java.math.BigDecimal;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 综合任务视图对象
+ *
+ * @author Antigravity
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = BusinessTask.class)
+public class BusinessTaskVo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ExcelProperty(value = "ID")
+    private Long id;
+
+    @ExcelProperty(value = "任务类型")
+    private Integer taskType;
+
+    /** 父级ID */
+    private Long parentId;
+
+    @ExcelProperty(value = "年份")
+    private Integer year;
+
+    @ExcelProperty(value = "季度")
+    private Integer quarter;
+
+    @ExcelProperty(value = "月份")
+    private Integer month;
+
+    private Long deptId;
+    @ExcelProperty(value = "部门")
+    @Translation(type = TransConstant.DEPT_ID_TO_NAME, mapper = "deptId")
+    private String deptName;
+
+    @ExcelProperty(value = "行业条线")
+    private String industryLine;
+
+    private Long salesUserId;
+    @ExcelProperty(value = "业务人员")
+    @Translation(type = TransConstant.USER_ID_TO_NICKNAME, mapper = "salesUserId")
+    private String salesUserName;
+
+    private Long serviceUserId;
+    @ExcelProperty(value = "客服人员")
+    @Translation(type = TransConstant.USER_ID_TO_NICKNAME, mapper = "serviceUserId")
+    private String serviceUserName;
+
+    @ExcelProperty(value = "目标任务(元)")
+    private BigDecimal targetTask;
+
+    @ExcelProperty(value = "冲刺任务(元)")
+    private BigDecimal sprintTask;
+
+    @ExcelProperty(value = "实际完成(元)")
+    private BigDecimal actualAchievement;
+
+    /** 关联年度任务金额 (季度任务展示用) */
+    private BigDecimal annualTargetTask;
+    private BigDecimal annualSprintTask;
+
+    /** 月度实绩明细 (季度任务展示用) */
+    private BigDecimal month1Actual;
+    private BigDecimal month2Actual;
+    private BigDecimal month3Actual;
+
+    @ExcelProperty(value = "核心客户")
+    private String coreCustomer;
+
+    @ExcelProperty(value = "重点项目")
+    private String keyProject;
+
+    @ExcelProperty(value = "潜在商机")
+    private String potentialOpportunity;
+
+    @ExcelProperty(value = "同期目标")
+    private BigDecimal lastYearTarget;
+
+    @ExcelProperty(value = "同期实绩")
+    private BigDecimal lastYearActual;
+
+    @ExcelProperty(value = "备注")
+    private String remark;
+
+    /** 任务分解明细 */
+    private List<BusinessTaskVo> details;
+
+    /** 达成率 = 实绩 / 目标 (计算属性) */
+    public String getAchievementRate() {
+        if (targetTask == null || targetTask.compareTo(BigDecimal.ZERO) <= 0 || actualAchievement == null) {
+            return "";
+        }
+        BigDecimal rate = actualAchievement.divide(targetTask, 4, BigDecimal.ROUND_HALF_UP).multiply(new BigDecimal(100));
+        return rate.setScale(2, BigDecimal.ROUND_HALF_UP).toString() + "%";
+    }
+
+    /** 同比 (YOY) = (今年实绩 - 去年实绩) / 去年实绩 */
+    public String getYoy() {
+        if (lastYearActual == null || lastYearActual.compareTo(BigDecimal.ZERO) <= 0 || actualAchievement == null) {
+            return "";
+        }
+        BigDecimal diff = actualAchievement.subtract(lastYearActual);
+        BigDecimal rate = diff.divide(lastYearActual, 4, BigDecimal.ROUND_HALF_UP).multiply(new BigDecimal(100));
+        String sign = diff.compareTo(BigDecimal.ZERO) >= 0 ? "+" : "";
+        return sign + rate.setScale(2, BigDecimal.ROUND_HALF_UP).toString() + "%";
+    }
+}

+ 13 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/BusinessTaskMapper.java

@@ -0,0 +1,13 @@
+package org.dromara.customer.mapper;
+
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.customer.domain.BusinessTask;
+import org.dromara.customer.domain.vo.BusinessTaskVo;
+
+/**
+ * 综合任务存储层
+ *
+ * @author Antigravity
+ */
+public interface BusinessTaskMapper extends BaseMapperPlus<BusinessTask, BusinessTaskVo> {
+}

+ 47 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/IBusinessTaskService.java

@@ -0,0 +1,47 @@
+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.BusinessTaskBo;
+import org.dromara.customer.domain.vo.BusinessTaskVo;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 综合任务服务接口
+ *
+ * @author Antigravity
+ */
+public interface IBusinessTaskService {
+
+    /**
+     * 查询综合任务列表
+     */
+    TableDataInfo<BusinessTaskVo> queryPageList(BusinessTaskBo bo, PageQuery pageQuery);
+
+    /**
+     * 查询综合任务列表 (无分页)
+     */
+    List<BusinessTaskVo> queryList(BusinessTaskBo bo);
+
+    /**
+     * 查询综合任务详情
+     */
+    BusinessTaskVo queryById(Long id);
+
+    /**
+     * 新增综合任务
+     */
+    Boolean insertByBo(BusinessTaskBo bo);
+
+    /**
+     * 修改综合任务
+     */
+    Boolean updateByBo(BusinessTaskBo bo);
+
+    /**
+     * 批量删除
+     */
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+}

+ 255 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/BusinessTaskServiceImpl.java

@@ -0,0 +1,255 @@
+package org.dromara.customer.service.impl;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.util.StrUtil;
+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.BusinessTask;
+import org.dromara.customer.domain.bo.BusinessTaskBo;
+import org.dromara.customer.domain.vo.BusinessTaskVo;
+import org.dromara.customer.mapper.BusinessTaskMapper;
+import org.dromara.customer.service.IBusinessTaskService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 综合任务服务实现类
+ *
+ * @author Antigravity
+ */
+@RequiredArgsConstructor
+@Service
+public class BusinessTaskServiceImpl implements IBusinessTaskService {
+
+    private final BusinessTaskMapper baseMapper;
+
+    @Override
+    public TableDataInfo<BusinessTaskVo> queryPageList(BusinessTaskBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<BusinessTask> lqw = buildQueryWrapper(bo);
+        Page<BusinessTaskVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        
+        // 如果查询的是季度任务,则需要填充年度聚合与月度明细
+        if (Integer.valueOf(1).equals(bo.getTaskType())) {
+            fillQuarterlyExtraData(result.getRecords());
+        }
+        
+        // 汇总子任务的核心客户和重点项目
+        aggregateChildData(result.getRecords());
+        
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 为季度任务填充年度目标及月度明细
+     */
+    private void fillQuarterlyExtraData(List<BusinessTaskVo> list) {
+        if (list == null || list.isEmpty()) return;
+        
+        for (BusinessTaskVo vo : list) {
+            // 1. 填充年度数据 (Parent)
+            if (vo.getParentId() != null) {
+                BusinessTask annual = baseMapper.selectById(vo.getParentId());
+                if (annual != null) {
+                    vo.setAnnualTargetTask(annual.getTargetTask());
+                    vo.setAnnualSprintTask(annual.getSprintTask());
+                }
+            }
+            
+            // 2. 填充月度数据 (Children)
+            List<BusinessTask> months = baseMapper.selectList(Wrappers.lambdaQuery(BusinessTask.class)
+                .eq(BusinessTask::getTaskType, 2)
+                .eq(BusinessTask::getParentId, vo.getId())
+                .orderByAsc(BusinessTask::getMonth));
+            
+            for (BusinessTask monthTask : months) {
+                BigDecimal actual = monthTask.getActualAchievement();
+                Integer m = monthTask.getMonth();
+                if (m == null) continue;
+                
+                // 判断属于该季度的第几个月
+                int monthInQuarter = (m - 1) % 3 + 1;
+                if (monthInQuarter == 1) vo.setMonth1Actual(actual);
+                else if (monthInQuarter == 2) vo.setMonth2Actual(actual);
+                else if (monthInQuarter == 3) vo.setMonth3Actual(actual);
+            }
+        }
+    }
+
+    /**
+     * 汇总子任务(分解项)的数据到主任务显示
+     */
+    private void aggregateChildData(List<BusinessTaskVo> list) {
+        if (list == null || list.isEmpty()) return;
+        
+        for (BusinessTaskVo vo : list) {
+            // 查询该任务下的所有子记录
+            List<BusinessTask> children = baseMapper.selectList(Wrappers.lambdaQuery(BusinessTask.class)
+                .eq(BusinessTask::getParentId, vo.getId()));
+            
+            if (children != null && !children.isEmpty()) {
+                // 拼接核心客户
+                String customers = children.stream()
+                    .map(BusinessTask::getCoreCustomer)
+                    .filter(StrUtil::isNotBlank)
+                    .distinct()
+                    .collect(java.util.stream.Collectors.joining(", "));
+                vo.setCoreCustomer(customers);
+                
+                // 拼接重点项目
+                String projects = children.stream()
+                    .map(BusinessTask::getKeyProject)
+                    .filter(StrUtil::isNotBlank)
+                    .distinct()
+                    .collect(java.util.stream.Collectors.joining(", "));
+                vo.setKeyProject(projects);
+            }
+        }
+    }
+
+    @Override
+    public List<BusinessTaskVo> queryList(BusinessTaskBo bo) {
+        LambdaQueryWrapper<BusinessTask> lqw = buildQueryWrapper(bo);
+        List<BusinessTaskVo> list = baseMapper.selectVoList(lqw);
+        
+        // 季度数据填充
+        if (Integer.valueOf(1).equals(bo.getTaskType())) {
+            fillQuarterlyExtraData(list);
+        }
+        // 子任务汇总填充
+        aggregateChildData(list);
+        
+        return list;
+    }
+
+    @Override
+    public BusinessTaskVo queryById(Long id) {
+        BusinessTaskVo vo = baseMapper.selectVoById(id);
+        if (vo != null) {
+            List<BusinessTaskVo> details = baseMapper.selectVoList(Wrappers.lambdaQuery(BusinessTask.class)
+                .eq(BusinessTask::getParentId, id));
+            vo.setDetails(details);
+        }
+        return vo;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean insertByBo(BusinessTaskBo bo) {
+        findAndSetParentId(bo);
+        BusinessTask add = BeanUtil.toBean(bo, BusinessTask.class);
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag && bo.getDetails() != null && !bo.getDetails().isEmpty()) {
+            for (BusinessTaskBo detail : bo.getDetails()) {
+                BusinessTask child = BeanUtil.toBean(detail, BusinessTask.class);
+                child.setParentId(add.getId());
+                child.setTaskType(bo.getTaskType());
+                child.setYear(bo.getYear());
+                child.setQuarter(bo.getQuarter());
+                child.setMonth(bo.getMonth());
+                child.setDeptId(bo.getDeptId());
+                baseMapper.insert(child);
+            }
+        }
+        return flag;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean updateByBo(BusinessTaskBo bo) {
+        findAndSetParentId(bo);
+        BusinessTask update = BeanUtil.toBean(bo, BusinessTask.class);
+        boolean flag = baseMapper.updateById(update) > 0;
+        if (flag) {
+            // 先删除旧的明细
+            baseMapper.delete(Wrappers.lambdaQuery(BusinessTask.class)
+                .eq(BusinessTask::getParentId, bo.getId()));
+            // 保存新的明细
+            if (bo.getDetails() != null && !bo.getDetails().isEmpty()) {
+                for (BusinessTaskBo detail : bo.getDetails()) {
+                    BusinessTask child = BeanUtil.toBean(detail, BusinessTask.class);
+                    child.setParentId(bo.getId());
+                    child.setTaskType(bo.getTaskType());
+                    child.setYear(bo.getYear());
+                    child.setQuarter(bo.getQuarter());
+                    child.setMonth(bo.getMonth());
+                    child.setDeptId(bo.getDeptId());
+                    baseMapper.insert(child);
+                }
+            }
+        }
+        return flag;
+    }
+
+    /**
+     * 自动寻找并设置父级ID
+     */
+    private void findAndSetParentId(BusinessTaskBo bo) {
+        if (bo.getParentId() != null) return;
+        
+        // 季度任务 (1) 找 年度任务 (0)
+        if (Integer.valueOf(1).equals(bo.getTaskType())) {
+            BusinessTask parent = baseMapper.selectOne(Wrappers.lambdaQuery(BusinessTask.class)
+                .eq(BusinessTask::getTaskType, 0)
+                .eq(BusinessTask::getYear, bo.getYear())
+                .eq(BusinessTask::getDeptId, bo.getDeptId())
+                .isNull(BusinessTask::getParentId) // 找主任务,非分解项
+                .last("limit 1"));
+            if (parent != null) bo.setParentId(parent.getId());
+        }
+        // 月度任务 (2) 找 季度任务 (1)
+        else if (Integer.valueOf(2).equals(bo.getTaskType())) {
+            // 计算季度
+            Integer quarter = (bo.getMonth() - 1) / 3 + 1;
+            BusinessTask parent = baseMapper.selectOne(Wrappers.lambdaQuery(BusinessTask.class)
+                .eq(BusinessTask::getTaskType, 1)
+                .eq(BusinessTask::getYear, bo.getYear())
+                .eq(BusinessTask::getQuarter, quarter)
+                .eq(BusinessTask::getDeptId, bo.getDeptId())
+                .isNull(BusinessTask::getParentId)
+                .last("limit 1"));
+            if (parent != null) bo.setParentId(parent.getId());
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if (ids == null || ids.isEmpty()) return false;
+        
+        // 1. 删除所有关联的子任务
+        baseMapper.delete(Wrappers.lambdaQuery(BusinessTask.class)
+            .in(BusinessTask::getParentId, ids));
+            
+        // 2. 删除主任务
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+
+    private LambdaQueryWrapper<BusinessTask> buildQueryWrapper(BusinessTaskBo bo) {
+        LambdaQueryWrapper<BusinessTask> lqw = Wrappers.lambdaQuery();
+        lqw.eq(bo.getTaskType() != null, BusinessTask::getTaskType, bo.getTaskType());
+        lqw.eq(bo.getYear() != null, BusinessTask::getYear, bo.getYear());
+        lqw.eq(bo.getQuarter() != null, BusinessTask::getQuarter, bo.getQuarter());
+        lqw.eq(bo.getMonth() != null, BusinessTask::getMonth, bo.getMonth());
+        lqw.eq(bo.getDeptId() != null, BusinessTask::getDeptId, bo.getDeptId());
+        lqw.eq(bo.getSalesUserId() != null, BusinessTask::getSalesUserId, bo.getSalesUserId());
+        lqw.eq(StrUtil.isNotBlank(bo.getIndustryLine()), BusinessTask::getIndustryLine, bo.getIndustryLine());
+        
+        // 关键修复:默认只查询主记录,不查询任务分解产生的子记录
+        if (bo.getParentId() != null) {
+            lqw.eq(BusinessTask::getParentId, bo.getParentId());
+        } else {
+            lqw.isNull(BusinessTask::getParentId);
+        }
+        
+        lqw.orderByDesc(BusinessTask::getId);
+        return lqw;
+    }
+}

+ 37 - 0
ruoyi-modules/ruoyi-customer/src/main/resources/mapper/customer/BusinessTaskMapper.xml

@@ -0,0 +1,37 @@
+<?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.BusinessTaskMapper">
+
+    <resultMap type="org.dromara.customer.domain.BusinessTask" id="BusinessTaskResult">
+        <result property="id" column="id"/>
+        <result property="taskType" column="task_type"/>
+        <result property="parentId" column="parent_id"/>
+        <result property="year" column="year"/>
+        <result property="quarter" column="quarter"/>
+        <result property="month" column="month"/>
+        <result property="deptId" column="dept_id"/>
+        <result property="industryLine" column="industry_line"/>
+        <result property="salesUserId" column="sales_user_id"/>
+        <result property="serviceUserId" column="service_user_id"/>
+        <result property="targetTask" column="target_task"/>
+        <result property="sprintTask" column="sprint_task"/>
+        <result property="actualAchievement" column="actual_achievement"/>
+        <result property="targetPercentage" column="target_percentage"/>
+        <result property="coreCustomer" column="core_customer"/>
+        <result property="keyProject" column="key_project"/>
+        <result property="potentialOpportunity" column="potential_opportunity"/>
+        <result property="lastYearTarget" column="last_year_target"/>
+        <result property="lastYearActual" column="last_year_actual"/>
+        <result property="status" column="status"/>
+        <result property="remark" column="remark"/>
+        <result property="tenantId" column="tenant_id"/>
+        <result property="platformCode" column="platform_code"/>
+        <result property="createBy" column="create_by"/>
+        <result property="createTime" column="create_time"/>
+        <result property="updateBy" column="update_by"/>
+        <result property="updateTime" column="update_time"/>
+    </resultMap>
+
+</mapper>