Parcourir la source

销售端新增sop任务处理详情+统计看板功能

cgp il y a 2 semaines
Parent
commit
8c57269261

+ 145 - 0
fs-company/src/main/java/com/fs/company/controller/qw/FsSopCompanyUserTaskController.java

@@ -0,0 +1,145 @@
+package com.fs.company.controller.qw;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+
+import com.fs.common.core.domain.R;
+import com.fs.common.utils.StringUtils;
+import com.fs.qw.dto.SopCompanyUserTaskDto;
+import com.fs.qw.vo.SopCompanyUserTaskVo;
+import org.apache.commons.collections4.CollectionUtils;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.enums.BusinessType;
+import com.fs.qw.domain.FsSopCompanyUserTask;
+import com.fs.qw.service.IFsSopCompanyUserTaskService;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+import static com.fs.his.utils.PhoneUtil.decryptPhone;
+
+/**
+ * 销售处理sop任务Controller
+ * 
+ * @author fs
+ * @date 2025-12-23
+ */
+@RestController
+@RequestMapping("/qw/companyUserTask")
+public class FsSopCompanyUserTaskController extends BaseController
+{
+    @Autowired
+    private IFsSopCompanyUserTaskService fsSopCompanyUserTaskService;
+
+    /**
+     * 查询销售处理sop任务列表
+     */
+    @PreAuthorize("@ss.hasPermi('qw:companyUserTask:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(SopCompanyUserTaskDto queryDto)
+    {
+        // 强制限定创建时间为今天
+        String todayStart = new SimpleDateFormat("yyyy-MM-dd 00:00:00").format(new Date());
+        String todayEnd = new SimpleDateFormat("yyyy-MM-dd 23:59:59").format(new Date());
+        queryDto.setBeginTime(todayStart);
+        queryDto.setEndTime(todayEnd);
+        startPage();
+        List<SopCompanyUserTaskVo> list = fsSopCompanyUserTaskService.selectFsSopCompanyUserTaskVoList(queryDto);
+        for (SopCompanyUserTaskVo vo : list) {
+            if (StringUtils.isNotBlank(vo.getPhone())){
+                vo.setPhone(vo.getPhone().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
+            }
+
+        }
+        return getDataTable(list);
+    }
+
+    /**
+     * 查询解密后的客户联系电话
+     * */
+    @GetMapping("/getUserPhone/{id}")
+    public R getUserPhone(@PathVariable("id") Long id){
+        SopCompanyUserTaskDto queryDto= new SopCompanyUserTaskDto();
+        queryDto.setId(id);
+        List<SopCompanyUserTaskVo> list = fsSopCompanyUserTaskService.selectFsSopCompanyUserTaskVoList(queryDto);
+        if (CollectionUtils.isNotEmpty(list)&&list.size()==1){
+            String userPhone=list.get(0).getPhone();
+            if (userPhone!=null&&userPhone.length()>11){
+                userPhone=decryptPhone(userPhone);
+            }
+            return R.ok().put("userPhone",userPhone);
+        }
+        return R.ok().put("userPhone","");
+    }
+    /**
+     * 导出销售处理sop任务列表
+     */
+    @PreAuthorize("@ss.hasPermi('qw:companyUserTask:export')")
+    @Log(title = "销售处理sop任务", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(SopCompanyUserTaskDto queryDto)
+    {
+        List<SopCompanyUserTaskVo> list = fsSopCompanyUserTaskService.selectFsSopCompanyUserTaskVoList(queryDto);
+        ExcelUtil<SopCompanyUserTaskVo> util = new ExcelUtil<SopCompanyUserTaskVo>(SopCompanyUserTaskVo.class);
+        return util.exportExcel(list, "销售处理sop任务数据");
+    }
+
+    /**
+     * 获取销售处理sop任务详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('qw:companyUserTask:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        SopCompanyUserTaskVo vo = fsSopCompanyUserTaskService.selectFsSopCompanyUserTaskVoById(id);
+        if (vo!=null&& StringUtils.isNotBlank(vo.getPhone())){
+            vo.setPhone(vo.getPhone().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
+        }
+        return AjaxResult.success(vo);
+    }
+
+    /**
+     * 新增销售处理sop任务
+     */
+    @PreAuthorize("@ss.hasPermi('qw:companyUserTask:add')")
+    @Log(title = "销售处理sop任务", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsSopCompanyUserTask fsSopCompanyUserTask)
+    {
+        return toAjax(fsSopCompanyUserTaskService.insertFsSopCompanyUserTask(fsSopCompanyUserTask));
+    }
+
+    /**
+     * 修改销售处理sop任务
+     */
+    @PreAuthorize("@ss.hasPermi('qw:companyUserTask:edit')")
+    @Log(title = "销售处理sop任务", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsSopCompanyUserTask fsSopCompanyUserTask)
+    {
+        return toAjax(fsSopCompanyUserTaskService.updateFsSopCompanyUserTask(fsSopCompanyUserTask));
+    }
+
+    /**
+     * 删除销售处理sop任务
+     */
+    @PreAuthorize("@ss.hasPermi('qw:companyUserTask:remove')")
+    @Log(title = "销售处理sop任务", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsSopCompanyUserTaskService.deleteFsSopCompanyUserTaskByIds(ids));
+    }
+}

+ 38 - 0
fs-company/src/main/java/com/fs/company/controller/qw/FsSopCompanyUserTaskStatsController.java

@@ -0,0 +1,38 @@
+package com.fs.company.controller.qw;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.qw.service.IFsSopCompanyUserTaskService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+
+/**
+ * 销售sop任务统计Controller
+ * 
+ * @author fs
+ * @date 2025-12-23
+ */
+@RestController
+@RequestMapping("/qw/companyUserTaskStats")
+public class FsSopCompanyUserTaskStatsController extends BaseController
+{
+    @Autowired
+    private IFsSopCompanyUserTaskService fsSopCompanyUserTaskService;
+
+    /**
+     * 获取销售/医生sop任务统计
+     */
+    @GetMapping("/stats")
+    @PreAuthorize("@ss.hasPermi('qw:companyUserTaskStats:stats')")
+    public AjaxResult getTaskStatistics() {
+        return AjaxResult.success(fsSopCompanyUserTaskService.getTaskStatistics());
+    }
+
+    @GetMapping("/trend/last7days")
+    @PreAuthorize("@ss.hasPermi('qw:companyUserTaskStats:stats')")
+    public AjaxResult getLast7DaysTrend() {
+        return AjaxResult.success(fsSopCompanyUserTaskService.getTaskTrendLast7Days());
+    }
+}

+ 38 - 0
fs-service/src/main/java/com/fs/qw/domain/FsSopCompanyUserTask.java

@@ -0,0 +1,38 @@
+package com.fs.qw.domain;
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 销售处理sop任务对象 fs_sop_company_user_task
+ *
+ * @author fs
+ * @date 2025-12-23
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FsSopCompanyUserTask extends BaseEntity{
+
+    /** 主键 */
+    private Long id;
+
+    /** 外部联系人主键 */
+    @Excel(name = "外部联系人主键")
+    private Long externalId;
+
+    /** 医生id */
+    @Excel(name = "医生id")
+    private Long doctorId;
+
+    /** 用户id */
+    @Excel(name = "用户id")
+    private Long userId;
+
+    /** 0:待处理,1:已处理 */
+    @Excel(name = "0:待处理,1:已处理")
+    private Long status;
+
+
+}

+ 27 - 0
fs-service/src/main/java/com/fs/qw/dto/SopCompanyUserTaskDto.java

@@ -0,0 +1,27 @@
+package com.fs.qw.dto;
+
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class SopCompanyUserTaskDto extends BaseEntity {
+
+    /** 主键*/
+    private Long id;
+
+    /** 医生id */
+    private String doctorName;
+    private Long doctorId;
+
+    /** 用户id */
+    private String name;
+
+    private Long userId;
+
+    private Long qwUserId;
+
+    /** 0:待处理,1:已处理 */
+    private Long status;
+}

+ 75 - 0
fs-service/src/main/java/com/fs/qw/mapper/FsSopCompanyUserTaskMapper.java

@@ -0,0 +1,75 @@
+package com.fs.qw.mapper;
+
+import java.util.List;
+import java.util.Map;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.qw.domain.FsSopCompanyUserTask;
+import com.fs.qw.dto.SopCompanyUserTaskDto;
+import com.fs.qw.vo.SopCompanyUserTaskVo;
+
+/**
+ * 销售处理sop任务Mapper接口
+ * 
+ * @author fs
+ * @date 2025-12-23
+ */
+public interface FsSopCompanyUserTaskMapper extends BaseMapper<FsSopCompanyUserTask>{
+    /**
+     * 查询销售处理sop任务
+     * 
+     * @param id 销售处理sop任务主键
+     * @return 销售处理sop任务
+     */
+    SopCompanyUserTaskVo selectFsSopCompanyUserTaskVoById(Long id);
+
+    /**
+     * 查询销售处理sop任务列表
+     * 
+     * @param queryDto 销售处理sop任务
+     * @return 销售处理sop任务集合
+     */
+    List<SopCompanyUserTaskVo> selectFsSopCompanyUserTaskVoList(SopCompanyUserTaskDto queryDto);
+
+    /**
+     * 新增销售处理sop任务
+     * 
+     * @param fsSopCompanyUserTask 销售处理sop任务
+     * @return 结果
+     */
+    int insertFsSopCompanyUserTask(FsSopCompanyUserTask fsSopCompanyUserTask);
+
+    /**
+     * 修改销售处理sop任务
+     * 
+     * @param fsSopCompanyUserTask 销售处理sop任务
+     * @return 结果
+     */
+    int updateFsSopCompanyUserTask(FsSopCompanyUserTask fsSopCompanyUserTask);
+
+    /**
+     * 删除销售处理sop任务
+     * 
+     * @param id 销售处理sop任务主键
+     * @return 结果
+     */
+    int deleteFsSopCompanyUserTaskById(Long id);
+
+    /**
+     * 批量删除销售处理sop任务
+     * 
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteFsSopCompanyUserTaskByIds(Long[] ids);
+
+    /**
+     * 按起止时间统计已处理/待处理任务数
+     */
+    Map<String, Object> selectAllTimeRangeStats();
+
+    /**
+     * 查询最近7天每日任务总量(按天分组)
+     */
+    List<Map<String, Object>> getTaskCountLast7Days();
+}

+ 75 - 0
fs-service/src/main/java/com/fs/qw/service/IFsSopCompanyUserTaskService.java

@@ -0,0 +1,75 @@
+package com.fs.qw.service;
+
+import java.util.List;
+import java.util.Map;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.qw.domain.FsSopCompanyUserTask;
+import com.fs.qw.dto.SopCompanyUserTaskDto;
+import com.fs.qw.vo.SopCompanyUserTaskVo;
+
+/**
+ * 销售处理sop任务Service接口
+ * 
+ * @author fs
+ * @date 2025-12-23
+ */
+public interface IFsSopCompanyUserTaskService extends IService<FsSopCompanyUserTask>{
+    /**
+     * 查询销售处理sop任务
+     * 
+     * @param id 销售处理sop任务主键
+     * @return 销售处理sop任务
+     */
+    SopCompanyUserTaskVo selectFsSopCompanyUserTaskVoById(Long id);
+
+    /**
+     * 查询销售处理sop任务列表
+     * 
+     * @param queryDto 销售处理sop任务
+     * @return 销售处理sop任务集合
+     */
+    List<SopCompanyUserTaskVo> selectFsSopCompanyUserTaskVoList(SopCompanyUserTaskDto queryDto);
+
+    /**
+     * 新增销售处理sop任务
+     * 
+     * @param fsSopCompanyUserTask 销售处理sop任务
+     * @return 结果
+     */
+    int insertFsSopCompanyUserTask(FsSopCompanyUserTask fsSopCompanyUserTask);
+
+    /**
+     * 修改销售处理sop任务
+     * 
+     * @param fsSopCompanyUserTask 销售处理sop任务
+     * @return 结果
+     */
+    int updateFsSopCompanyUserTask(FsSopCompanyUserTask fsSopCompanyUserTask);
+
+    /**
+     * 批量删除销售处理sop任务
+     * 
+     * @param ids 需要删除的销售处理sop任务主键集合
+     * @return 结果
+     */
+    int deleteFsSopCompanyUserTaskByIds(Long[] ids);
+
+    /**
+     * 删除销售处理sop任务信息
+     * 
+     * @param id 销售处理sop任务主键
+     * @return 结果
+     */
+    int deleteFsSopCompanyUserTaskById(Long id);
+
+    /**
+     * 获取日/周/月维度统计
+     */
+    Map<String, Object> getTaskStatistics();
+
+    /**
+     * 获取最近7天任务趋势数据
+     */
+    Map<String, Object> getTaskTrendLast7Days();
+}

+ 172 - 0
fs-service/src/main/java/com/fs/qw/service/impl/FsSopCompanyUserTaskServiceImpl.java

@@ -0,0 +1,172 @@
+package com.fs.qw.service.impl;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+
+import com.fs.common.utils.DateUtils;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.qw.dto.SopCompanyUserTaskDto;
+import com.fs.qw.vo.SopCompanyUserTaskVo;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.fs.qw.mapper.FsSopCompanyUserTaskMapper;
+import com.fs.qw.domain.FsSopCompanyUserTask;
+import com.fs.qw.service.IFsSopCompanyUserTaskService;
+
+/**
+ * 销售处理sop任务Service业务层处理
+ * 
+ * @author fs
+ * @date 2025-12-23
+ */
+@Slf4j
+@Service
+public class FsSopCompanyUserTaskServiceImpl extends ServiceImpl<FsSopCompanyUserTaskMapper, FsSopCompanyUserTask> implements IFsSopCompanyUserTaskService {
+
+    @Autowired
+    private FsSopCompanyUserTaskMapper sopCompanyUserTaskMapper;
+
+    private static final DateTimeFormatter DTF = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+    /**
+     * 查询销售处理sop任务
+     * 
+     * @param id 销售处理sop任务主键
+     * @return 销售处理sop任务
+     */
+    @Override
+    public SopCompanyUserTaskVo selectFsSopCompanyUserTaskVoById(Long id)
+    {
+        return sopCompanyUserTaskMapper.selectFsSopCompanyUserTaskVoById(id);
+    }
+
+    /**
+     * 查询销售处理sop任务列表
+     * 
+     * @param queryDto 销售处理sop任务
+     * @return 销售处理sop任务
+     */
+    @Override
+    public List<SopCompanyUserTaskVo> selectFsSopCompanyUserTaskVoList(SopCompanyUserTaskDto queryDto)
+    {
+        return sopCompanyUserTaskMapper.selectFsSopCompanyUserTaskVoList(queryDto);
+    }
+
+    /**
+     * 新增销售处理sop任务
+     * 
+     * @param fsSopCompanyUserTask 销售处理sop任务
+     * @return 结果
+     */
+    @Override
+    public int insertFsSopCompanyUserTask(FsSopCompanyUserTask fsSopCompanyUserTask)
+    {
+        fsSopCompanyUserTask.setCreateTime(DateUtils.getNowDate());
+        return sopCompanyUserTaskMapper.insertFsSopCompanyUserTask(fsSopCompanyUserTask);
+    }
+
+    /**
+     * 修改销售处理sop任务
+     * 
+     * @param fsSopCompanyUserTask 销售处理sop任务
+     * @return 结果
+     */
+    @Override
+    public int updateFsSopCompanyUserTask(FsSopCompanyUserTask fsSopCompanyUserTask)
+    {
+        fsSopCompanyUserTask.setUpdateTime(DateUtils.getNowDate());
+        return sopCompanyUserTaskMapper.updateFsSopCompanyUserTask(fsSopCompanyUserTask);
+    }
+
+    /**
+     * 批量删除销售处理sop任务
+     * 
+     * @param ids 需要删除的销售处理sop任务主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsSopCompanyUserTaskByIds(Long[] ids)
+    {
+        return sopCompanyUserTaskMapper.deleteFsSopCompanyUserTaskByIds(ids);
+    }
+
+    /**
+     * 删除销售处理sop任务信息
+     * 
+     * @param id 销售处理sop任务主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsSopCompanyUserTaskById(Long id)
+    {
+        return sopCompanyUserTaskMapper.deleteFsSopCompanyUserTaskById(id);
+    }
+
+    /**
+     * 统计指定时间段内的任务数量
+     * @return 任务统计结果
+     */
+    @Override
+    public Map<String, Object> getTaskStatistics() {
+        Map<String, Object> raw = sopCompanyUserTaskMapper.selectAllTimeRangeStats();
+        return buildStatsFromSingleQuery(raw);
+    }
+
+    @Override
+    public Map<String, Object> getTaskTrendLast7Days() {
+        List<Map<String, Object>> list = sopCompanyUserTaskMapper.getTaskCountLast7Days();
+
+        // 确保7天连续(补0)
+        List<String> dates = new ArrayList<>();
+        List<Integer> totals = new ArrayList<>();
+
+        LocalDate today = LocalDate.now();
+        for (int i = 6; i >= 0; i--) {
+            LocalDate date = today.minusDays(i);
+            String key = date.format(DateTimeFormatter.ofPattern("MM-dd"));
+            dates.add(key);
+            totals.add(0); // 默认0
+        }
+
+        // 填入真实数据
+        for (Map<String, Object> item : list) {
+            String dbDate = (String) item.get("date");
+            Integer total = ((Number) item.get("total")).intValue();
+            int index = dates.indexOf(dbDate);
+            if (index != -1) {
+                totals.set(index, total);
+            }
+        }
+
+        Map<String, Object> result = new HashMap<>();
+        result.put("dates", dates);
+        result.put("totals", totals);
+        return result;
+    }
+
+    private Map<String, Object> buildStatsFromSingleQuery(Map<String, Object> dbResult) {
+        Map<String, Object> day = new HashMap<>();
+        day.put("processed", safeLong(dbResult.get("day_processed")));
+        day.put("pending", safeLong(dbResult.get("day_pending")));
+
+        Map<String, Object> week = new HashMap<>();
+        week.put("processed", safeLong(dbResult.get("week_processed")));
+        week.put("pending", safeLong(dbResult.get("week_pending")));
+
+        Map<String, Object> month = new HashMap<>();
+        month.put("processed", safeLong(dbResult.get("month_processed")));
+        month.put("pending", safeLong(dbResult.get("month_pending")));
+
+        Map<String, Object> result = new HashMap<>();
+        result.put("day", day);
+        result.put("week", week);
+        result.put("month", month);
+        return result;
+    }
+
+    private long safeLong(Object obj) {
+        return obj == null ? 0L : ((Number) obj).longValue();
+    }
+}

+ 48 - 0
fs-service/src/main/java/com/fs/qw/vo/SopCompanyUserTaskVo.java

@@ -0,0 +1,48 @@
+package com.fs.qw.vo;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+public class SopCompanyUserTaskVo {
+    /** 任务主键*/
+    private Long id;
+
+    /** 医生id */
+    private Long doctorId;
+
+    @Excel(name = "医生姓名")
+    private String doctorName;
+
+    /** 用户id */
+    private Long userId;
+
+    @Excel(name = "用户姓名")
+    private String name;
+
+
+    private String avatar;
+
+    @Excel(name = "用户电话")
+    private String phone;
+
+    /** 0:待处理,1:已处理 */
+    private Long status;
+
+    @Excel(name = "备注信息")
+    private String remark;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @TableField(fill = FieldFill.INSERT)
+    private Date createTime;
+
+    /** 更新时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private Date updateTime;
+}

+ 11 - 0
fs-service/src/main/java/com/fs/qw/vo/TaskStatisticsVo.java

@@ -0,0 +1,11 @@
+package com.fs.qw.vo;
+
+import lombok.Data;
+/**
+ * 销售/医生sop处理任务数据
+ * */
+@Data
+public class TaskStatisticsVo {
+    private Long processed; // 已处理(status=1)
+    private Long pending;   // 待处理(status=0)
+}

+ 190 - 0
fs-service/src/main/resources/mapper/qw/FsSopCompanyUserTaskMapper.xml

@@ -0,0 +1,190 @@
+<?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="com.fs.qw.mapper.FsSopCompanyUserTaskMapper">
+    
+    <resultMap type="FsSopCompanyUserTask" id="FsSopCompanyUserTaskResult">
+        <result property="id"    column="id"    />
+        <result property="externalId"    column="external_id"    />
+        <result property="doctorId"    column="doctor_id"    />
+        <result property="userId"    column="user_id"    />
+        <result property="status"    column="status"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="updateTime"    column="update_time"    />
+        <result property="remark"    column="remark"    />
+    </resultMap>
+
+    <resultMap type="com.fs.qw.vo.SopCompanyUserTaskVo" id="SopCompanyUserTaskVoResult">
+        <!-- 主键可 -->
+        <result property="id"    column="id"    />
+        <!-- fs_sop_company_user_task 表字段 -->
+        <result property="doctorId"     column="doctor_id" />
+        <result property="userId"       column="user_id" />
+        <result property="status"       column="status" />
+        <result property="remark"       column="remark" />
+        <result property="createTime"   column="create_time" />
+        <result property="updateTime"   column="update_time" />
+
+        <!-- 关联表字段 -->
+        <result property="name"     column="name" />          <!-- qwec.name -->
+        <result property="doctorName"   column="doctor_name" />   <!-- fd.doctor_name -->
+        <result property="avatar" column="avatar" />
+        <result property="phone"  column="phone" />
+    </resultMap>
+
+    <sql id="selectFsSopCompanyUserTaskVo">
+        select id, external_id, doctor_id, user_id, status, create_time, update_time, remark from fs_sop_company_user_task
+    </sql>
+
+    <select id="selectFsSopCompanyUserTaskVoList" parameterType="com.fs.qw.dto.SopCompanyUserTaskDto" resultMap="SopCompanyUserTaskVoResult">
+        SELECT
+        sct.id,
+        sct.user_id,
+        qwec.name,
+        qwec.avatar,
+        fu.phone,
+        sct.doctor_id,
+        fd.doctor_name,
+        sct.STATUS,
+        sct.create_time,
+        sct.update_time,
+        sct.external_id,
+        sct.remark
+        FROM
+        fs_sop_company_user_task sct
+        LEFT JOIN fs_doctor fd ON sct.doctor_id = fd.doctor_id
+        LEFT JOIN qw_external_contact qwec ON sct.user_id = qwec.id
+        LEFT JOIN fs_user fu ON qwec.fs_user_id = fu.user_id
+        <where>
+            <if test="id != null "> and sct.id = #{id}</if>
+            <if test="doctorId != null "> and sct.doctor_id = #{doctorId}</if>
+            <if test="userId != null "> and sct.user_id = #{userId}</if>
+            <if test="status != null "> and sct.status = #{status}</if>
+            <if test="qwUserId != null "> and qwec.qw_user_id = #{qwUserId}</if>
+            <if test="name != null "> and qwec.name like concat('%', #{name}, '%')</if>
+            <if test="doctorName != null "> and fd.doctor_name like concat('%', #{doctorName}, '%')</if>
+            <if test="beginTime != null and beginTime != ''">
+                and sct.create_time &gt;= str_to_date(#{beginTime}, '%Y-%m-%d %H:%i:%s')
+            </if>
+            <if test="endTime != null and endTime != ''">
+                and sct.create_time &lt;= str_to_date(#{endTime}, '%Y-%m-%d %H:%i:%s')
+            </if>
+        </where>
+        ORDER BY id DESC
+    </select>
+    
+    <select id="selectFsSopCompanyUserTaskVoById" parameterType="Long" resultMap="SopCompanyUserTaskVoResult">
+        SELECT
+            sct.id,
+            sct.user_id,
+            qwec.name,
+            qwec.avatar,
+            fu.phone,
+            sct.doctor_id,
+            fd.doctor_name,
+            sct.STATUS,
+            sct.create_time,
+            sct.update_time,
+            sct.external_id,
+            sct.remark
+        FROM
+            fs_sop_company_user_task sct
+                LEFT JOIN fs_doctor fd ON sct.doctor_id = fd.doctor_id
+                LEFT JOIN qw_external_contact qwec ON sct.user_id = qwec.id
+                LEFT JOIN fs_user fu ON qwec.fs_user_id = fu.user_id
+        where sct.id = #{id}
+    </select>
+
+    <!-- 统计指定时间段内已处理/待处理数量 -->
+    <select id="selectAllTimeRangeStats" resultType="map">
+        SELECT
+        -- 今日
+        SUM(CASE WHEN DATE(create_time) = CURDATE() AND status = 1 THEN 1 ELSE 0 END) AS day_processed,
+        SUM(CASE WHEN DATE(create_time) = CURDATE() AND status = 0 THEN 1 ELSE 0 END) AS day_pending,
+
+        -- 本周(周一到今天)
+        SUM(CASE
+        WHEN create_time >= DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE()) DAY)
+        AND create_time &lt; CURDATE() + INTERVAL 1 DAY
+        AND status = 1
+        THEN 1 ELSE 0 END) AS week_processed,
+        SUM(CASE
+        WHEN create_time >= DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE()) DAY)
+        AND create_time &lt; CURDATE() + INTERVAL 1 DAY
+        AND status = 0
+        THEN 1 ELSE 0 END) AS week_pending,
+
+        -- 本月
+        SUM(CASE WHEN YEAR(create_time) = YEAR(CURDATE())
+        AND MONTH(create_time) = MONTH(CURDATE())
+        AND status = 1
+        THEN 1 ELSE 0 END) AS month_processed,
+        SUM(CASE WHEN YEAR(create_time) = YEAR(CURDATE())
+        AND MONTH(create_time) = MONTH(CURDATE())
+        AND status = 0
+        THEN 1 ELSE 0 END) AS month_pending
+
+        FROM fs_sop_company_user_task
+        WHERE create_time >= DATE_SUB(CURDATE(), INTERVAL 1 MONTH)  <!-- 只查最近1个月数据,减少扫描量 -->
+    </select>
+
+    <!-- 最近7天每日任务量(含今天) -->
+    <select id="getTaskCountLast7Days" resultType="map">
+        SELECT
+        DATE_FORMAT(create_time, '%m-%d') AS date,
+        COUNT(*) AS total
+        FROM fs_sop_company_user_task
+        WHERE create_time &gt;= DATE_SUB(CURDATE(), INTERVAL 6 DAY)
+        AND create_time &lt; CURDATE() + INTERVAL 1 DAY
+        GROUP BY DATE(create_time)
+        ORDER BY DATE(create_time) ASC
+    </select>
+
+    <insert id="insertFsSopCompanyUserTask" parameterType="FsSopCompanyUserTask" useGeneratedKeys="true" keyProperty="id">
+        insert into fs_sop_company_user_task
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="externalId != null">external_id,</if>
+            <if test="doctorId != null">doctor_id,</if>
+            <if test="userId != null">user_id,</if>
+            <if test="status != null">status,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateTime != null">update_time,</if>
+            <if test="remark != null">remark,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="externalId != null">#{externalId},</if>
+            <if test="doctorId != null">#{doctorId},</if>
+            <if test="userId != null">#{userId},</if>
+            <if test="status != null">#{status},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+            <if test="remark != null">#{remark},</if>
+         </trim>
+    </insert>
+
+    <update id="updateFsSopCompanyUserTask" parameterType="FsSopCompanyUserTask">
+        update fs_sop_company_user_task
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="externalId != null">external_id = #{externalId},</if>
+            <if test="doctorId != null">doctor_id = #{doctorId},</if>
+            <if test="userId != null">user_id = #{userId},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="remark != null">remark = #{remark},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteFsSopCompanyUserTaskById" parameterType="Long">
+        delete from fs_sop_company_user_task where id = #{id}
+    </delete>
+
+    <delete id="deleteFsSopCompanyUserTaskByIds" parameterType="String">
+        delete from fs_sop_company_user_task where id in 
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>