Browse Source

数据驾驶舱

haogh 9 months ago
parent
commit
ec8f2712e1
20 changed files with 2062 additions and 2 deletions
  1. 53 0
      sop-api/src/main/java/com/qmth/sop/server/api/CrmProgressMonitorController.java
  2. 93 0
      sop-api/src/main/java/com/qmth/sop/server/api/ServiceUnitAnalyseController.java
  3. 10 0
      sop-api/src/main/java/com/qmth/sop/server/api/SopWarnMonitorController.java
  4. 128 0
      sop-business/src/main/java/com/qmth/sop/business/bean/result/CrmProgressBaseResult.java
  5. 84 0
      sop-business/src/main/java/com/qmth/sop/business/bean/result/CrmProgressResult.java
  6. 138 0
      sop-business/src/main/java/com/qmth/sop/business/bean/result/CrmProgressSopResult.java
  7. 66 0
      sop-business/src/main/java/com/qmth/sop/business/bean/result/ServiceUnitOverviewResult.java
  8. 80 0
      sop-business/src/main/java/com/qmth/sop/business/bean/result/ServiceUnitProgressResult.java
  9. 124 0
      sop-business/src/main/java/com/qmth/sop/business/bean/result/ServiceUnitProvinceResult.java
  10. 114 0
      sop-business/src/main/java/com/qmth/sop/business/bean/result/SopInfoExportResult.java
  11. 21 0
      sop-business/src/main/java/com/qmth/sop/business/mapper/CrmProgressMonitorMapper.java
  12. 135 0
      sop-business/src/main/java/com/qmth/sop/business/mapper/ServiceUnitAnalyseMapper.java
  13. 19 0
      sop-business/src/main/java/com/qmth/sop/business/service/CrmProgressMonitorService.java
  14. 34 0
      sop-business/src/main/java/com/qmth/sop/business/service/ServiceUnitAnalyseService.java
  15. 195 0
      sop-business/src/main/java/com/qmth/sop/business/service/impl/CrmProgressMonitorServiceImpl.java
  16. 322 0
      sop-business/src/main/java/com/qmth/sop/business/service/impl/ServiceUnitAnalyseServiceImpl.java
  17. 78 0
      sop-business/src/main/resources/mapper/CrmProgressMonitorMapper.xml
  18. 341 0
      sop-business/src/main/resources/mapper/ServiceUnitAnalyseMapper.xml
  19. 2 2
      sop-common/src/main/java/com/qmth/sop/common/contant/SystemConstant.java
  20. 25 0
      sop-common/src/main/java/com/qmth/sop/common/enums/CrmProgressMonitorEnum.java

+ 53 - 0
sop-api/src/main/java/com/qmth/sop/server/api/CrmProgressMonitorController.java

@@ -0,0 +1,53 @@
+package com.qmth.sop.server.api;
+
+import com.qmth.boot.api.constant.ApiConstant;
+import com.qmth.sop.business.bean.result.CrmProgressResult;
+import com.qmth.sop.business.service.CrmProgressMonitorService;
+import com.qmth.sop.common.contant.SystemConstant;
+import com.qmth.sop.common.enums.CrmProgressMonitorEnum;
+import com.qmth.sop.common.util.Result;
+import com.qmth.sop.common.util.ResultUtil;
+import io.swagger.annotations.*;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+
+/**
+ * @Description 项目进度监控
+ * @Author haoguanghui
+ * @date 2024/09/11
+ */
+@Api(tags = "项目进度监控Controller")
+@RestController
+@RequestMapping(ApiConstant.DEFAULT_URI_PREFIX + SystemConstant.PREFIX_URL_CRM_PROCESS_MONITOR)
+public class CrmProgressMonitorController {
+
+    @Resource
+    CrmProgressMonitorService crmProgressMonitorService;
+
+    @ApiOperation(value = "按大区进度统计")
+    @RequestMapping(value = "/region", method = RequestMethod.POST)
+    @ApiResponses({ @ApiResponse(code = 200, message = "返回信息", response = CrmProgressResult.class) })
+    public Result region(@ApiParam(value = "服务单元ID", required = true) @RequestParam(required = true) Long serviceUnitId) {
+        return ResultUtil.ok(crmProgressMonitorService.listSopNum(serviceUnitId, CrmProgressMonitorEnum.BY_LEAD));
+    }
+
+    @ApiOperation(value = "按人力商进度统计")
+    @RequestMapping(value = "/supplier", method = RequestMethod.POST)
+    @ApiResponses({ @ApiResponse(code = 200, message = "返回信息", response = CrmProgressResult.class) })
+    public Result supplier(@ApiParam(value = "服务单元ID", required = true) @RequestParam(required = true) Long serviceUnitId) {
+        return ResultUtil.ok(crmProgressMonitorService.listSopNum(serviceUnitId, CrmProgressMonitorEnum.BY_SUPPLIER));
+    }
+
+    @ApiOperation(value = "按区域协调人进度统计")
+    @RequestMapping(value = "/coordinator", method = RequestMethod.POST)
+    @ApiResponses({ @ApiResponse(code = 200, message = "返回信息", response = CrmProgressResult.class) })
+    public Result coordinator(@ApiParam(value = "服务单元ID", required = true) @RequestParam(required = true) Long serviceUnitId) {
+        return ResultUtil.ok(crmProgressMonitorService.listSopNum(serviceUnitId, CrmProgressMonitorEnum.BY_COORDINATOR));
+    }
+
+
+}

+ 93 - 0
sop-api/src/main/java/com/qmth/sop/server/api/ServiceUnitAnalyseController.java

@@ -0,0 +1,93 @@
+package com.qmth.sop.server.api;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.qmth.boot.api.constant.ApiConstant;
+import com.qmth.sop.business.bean.result.ProjectMonitorResult;
+import com.qmth.sop.business.bean.result.ServiceUnitOverviewResult;
+import com.qmth.sop.business.bean.result.ServiceUnitProgressResult;
+import com.qmth.sop.business.bean.result.ServiceUnitProvinceResult;
+import com.qmth.sop.business.service.ServiceUnitAnalyseService;
+import com.qmth.sop.common.annotation.OperationLog;
+import com.qmth.sop.common.contant.SystemConstant;
+import com.qmth.sop.common.enums.CrmProcessEnum;
+import com.qmth.sop.common.enums.LogTypeEnum;
+import com.qmth.sop.common.util.Result;
+import com.qmth.sop.common.util.ResultUtil;
+import io.swagger.annotations.*;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+
+/**
+ * @Description 服务单元分析
+ * @Author haoguanghui
+ * @date 2024/09/09
+ */
+@Api(tags = "服务单元分析重构 Controller")
+@RestController
+@RequestMapping(ApiConstant.DEFAULT_URI_PREFIX + SystemConstant.PREFIX_URL_SERVICE_ANALYSE)
+public class ServiceUnitAnalyseController {
+
+    @Resource
+    ServiceUnitAnalyseService serviceUnitAnalyseService;
+
+    @ApiOperation(value = "服务单元概况")
+    @RequestMapping(value = "/overview", method = RequestMethod.POST)
+    @ApiResponses({ @ApiResponse(code = 200, message = "返回信息", response = ServiceUnitOverviewResult.class) })
+    public Result overview(@ApiParam(value = "服务单元ID", required = true) @RequestParam(required = true) Long serviceUnitId) {
+        return ResultUtil.ok(serviceUnitAnalyseService.overview(serviceUnitId));
+    }
+
+    @ApiOperation(value = "项目整体进度")
+    @RequestMapping(value = "/progress", method = RequestMethod.POST)
+    @ApiResponses({ @ApiResponse(code = 200, message = "返回信息", response = ServiceUnitProgressResult.class) })
+    public Result progress(@ApiParam(value = "服务单元ID", required = true) @RequestParam(required = true) Long serviceUnitId) {
+        return ResultUtil.ok(serviceUnitAnalyseService.progress(serviceUnitId));
+    }
+
+    @ApiOperation(value = "区域概况")
+    @RequestMapping(value = "/province", method = RequestMethod.POST)
+    @ApiResponses({ @ApiResponse(code = 200, message = "返回信息", response = ServiceUnitProvinceResult.class) })
+    public Result province(@ApiParam(value = "服务单元ID", required = true) @RequestParam(required = true) Long serviceUnitId) {
+        return ResultUtil.ok(serviceUnitAnalyseService.province(serviceUnitId));
+    }
+
+    @ApiOperation(value = "详情分页")
+    @RequestMapping(value = "/detail", method = RequestMethod.POST)
+    @ApiResponses({ @ApiResponse(code = 200, message = "详情分页", response = ProjectMonitorResult.class) })
+    public Result page(
+            @ApiParam(value = "服务单元ID", required = true) @RequestParam(required = true) Long serviceUnitId,
+            @ApiParam(value = "客户经理ID", required = false) @RequestParam(required = false) Long crmUserId,
+            @ApiParam(value = "客户名称", required = false) @RequestParam(required = false) String customName,
+            @ApiParam(value = "当前进度", required = false) @RequestParam(required = false) CrmProcessEnum process,
+            @ApiParam(value = "大区经理", required = false) @RequestParam(required = false) Long leadId,
+            @ApiParam(value = "客户所在省份", required = false) @RequestParam(required = false) String province,
+            @ApiParam(value = "项目经理所在人力商", required = false) @RequestParam(required = false) Long supplierId,
+            @ApiParam(value = "区域协调人ID", required = false) @RequestParam(required = false) Long coordinatorId,
+            @ApiParam(value = "分页页码", required = true) @RequestParam @Min(SystemConstant.PAGE_NUMBER_MIN) Integer pageNumber,
+            @ApiParam(value = "分页数", required = true) @RequestParam @Min(SystemConstant.PAGE_SIZE_MIN) @Max(SystemConstant.PAGE_SIZE_MAX) Integer pageSize) {
+        return ResultUtil.ok(serviceUnitAnalyseService.pageSop(new Page<>(pageNumber, pageSize), serviceUnitId, crmUserId, customName, process,
+                leadId, province, supplierId, coordinatorId));
+    }
+
+    @ApiOperation(value = "详情导出")
+    @RequestMapping(value = "/export", method = RequestMethod.POST)
+    @ApiResponses({ @ApiResponse(code = 200, message = "返回信息", response = Object.class) })
+    @OperationLog(logType = LogTypeEnum.EXPORT)
+    public void export(@ApiParam(value = "服务单元ID", required = true) @RequestParam(required = true) Long serviceUnitId,
+            @ApiParam(value = "客户经理ID", required = false) @RequestParam(required = false) Long crmUserId,
+            @ApiParam(value = "客户名称", required = false) @RequestParam(required = false) String customName,
+            @ApiParam(value = "当前进度", required = false) @RequestParam(required = false) CrmProcessEnum process,
+            @ApiParam(value = "大区经理", required = false) @RequestParam(required = false) Long leadId,
+            @ApiParam(value = "客户所在省份", required = false) @RequestParam(required = false) String province,
+            @ApiParam(value = "项目经理所在人力商", required = false) @RequestParam(required = false) Long supplierId,
+            @ApiParam(value = "区域协调人ID", required = false) @RequestParam(required = false) Long coordinatorId) {
+        serviceUnitAnalyseService.export(serviceUnitId, crmUserId, customName, process, leadId, province, supplierId, coordinatorId);
+    }
+
+}

+ 10 - 0
sop-api/src/main/java/com/qmth/sop/server/api/SopWarnMonitorController.java

@@ -0,0 +1,10 @@
+package com.qmth.sop.server.api;
+
+/**
+ * @Description sop预警监控
+ * @Author haoguanghui
+ * @date 2024/09/12
+ */
+public class SopWarnMonitorController {
+
+}

+ 128 - 0
sop-business/src/main/java/com/qmth/sop/business/bean/result/CrmProgressBaseResult.java

@@ -0,0 +1,128 @@
+package com.qmth.sop.business.bean.result;
+
+import io.swagger.annotations.ApiModelProperty;
+
+
+public class CrmProgressBaseResult {
+
+    @ApiModelProperty(value = "总数")
+    private Integer total;
+
+    @ApiModelProperty(value = "准备阶段sop数量")
+    private Integer prepareSopNum;
+
+    @ApiModelProperty(value = "准备阶段sop数量占比")
+    private String prepareSopRatio;
+
+    @ApiModelProperty(value = "扫描阶段sop数量")
+    private Integer scanSopNum;
+
+    @ApiModelProperty(value = "扫描阶段sop数量占比")
+    private String scanSopRatio;
+
+    @ApiModelProperty(value = "评卷阶段sop数量")
+    private Integer markSopNum;
+
+    @ApiModelProperty(value = "评卷阶段sop数量占比")
+    private String markSopRatio;
+
+    @ApiModelProperty(value = "收尾阶段sop数量")
+    private Integer finalSopNum;
+
+    @ApiModelProperty(value = "收尾阶段sop数量占比")
+    private String finalSopRatio;
+
+    @ApiModelProperty(value = "已完成sop数量")
+    private Integer finishSopNum;
+
+    @ApiModelProperty(value = "已完成sop数量占比")
+    private String finishSopRatio;
+
+    public Integer getPrepareSopNum() {
+        return prepareSopNum;
+    }
+
+    public void setPrepareSopNum(Integer prepareSopNum) {
+        this.prepareSopNum = prepareSopNum;
+    }
+
+    public String getPrepareSopRatio() {
+        return prepareSopRatio;
+    }
+
+    public void setPrepareSopRatio(String prepareSopRatio) {
+        this.prepareSopRatio = prepareSopRatio;
+    }
+
+    public Integer getScanSopNum() {
+        return scanSopNum;
+    }
+
+    public void setScanSopNum(Integer scanSopNum) {
+        this.scanSopNum = scanSopNum;
+    }
+
+    public String getScanSopRatio() {
+        return scanSopRatio;
+    }
+
+    public void setScanSopRatio(String scanSopRatio) {
+        this.scanSopRatio = scanSopRatio;
+    }
+
+    public Integer getMarkSopNum() {
+        return markSopNum;
+    }
+
+    public void setMarkSopNum(Integer markSopNum) {
+        this.markSopNum = markSopNum;
+    }
+
+    public String getMarkSopRatio() {
+        return markSopRatio;
+    }
+
+    public void setMarkSopRatio(String markSopRatio) {
+        this.markSopRatio = markSopRatio;
+    }
+
+    public Integer getFinalSopNum() {
+        return finalSopNum;
+    }
+
+    public void setFinalSopNum(Integer finalSopNum) {
+        this.finalSopNum = finalSopNum;
+    }
+
+    public String getFinalSopRatio() {
+        return finalSopRatio;
+    }
+
+    public void setFinalSopRatio(String finalSopRatio) {
+        this.finalSopRatio = finalSopRatio;
+    }
+
+    public Integer getFinishSopNum() {
+        return finishSopNum;
+    }
+
+    public void setFinishSopNum(Integer finishSopNum) {
+        this.finishSopNum = finishSopNum;
+    }
+
+    public String getFinishSopRatio() {
+        return finishSopRatio;
+    }
+
+    public void setFinishSopRatio(String finishSopRatio) {
+        this.finishSopRatio = finishSopRatio;
+    }
+
+    public Integer getTotal() {
+        return total;
+    }
+
+    public void setTotal(Integer total) {
+        this.total = total;
+    }
+}

+ 84 - 0
sop-business/src/main/java/com/qmth/sop/business/bean/result/CrmProgressResult.java

@@ -0,0 +1,84 @@
+package com.qmth.sop.business.bean.result;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * @Description 项目进度监控-大区经理、供应商、区域协调人统计
+ * @Author haoguanghui
+ * @date 2024/09/11
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class CrmProgressResult extends CrmProgressBaseResult {
+
+    @ApiModelProperty(value = "大区经理ID")
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long leadId;
+
+    @ApiModelProperty(value = "大区经理")
+    private String leadName;
+
+    @ApiModelProperty(value = "区域协调人ID")
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long coordinatorId;
+
+    @ApiModelProperty(value = "区域协调人名称")
+    private String coordinatorName;
+
+    @ApiModelProperty(value = "供应商ID")
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long supplierId;
+
+    @ApiModelProperty(value = "供应商名称")
+    private String supplierName;
+
+    public Long getLeadId() {
+        return leadId;
+    }
+
+    public void setLeadId(Long leadId) {
+        this.leadId = leadId;
+    }
+
+    public String getLeadName() {
+        return leadName;
+    }
+
+    public void setLeadName(String leadName) {
+        this.leadName = leadName;
+    }
+
+    public Long getCoordinatorId() {
+        return coordinatorId;
+    }
+
+    public void setCoordinatorId(Long coordinatorId) {
+        this.coordinatorId = coordinatorId;
+    }
+
+    public String getCoordinatorName() {
+        return coordinatorName;
+    }
+
+    public void setCoordinatorName(String coordinatorName) {
+        this.coordinatorName = coordinatorName;
+    }
+
+    public Long getSupplierId() {
+        return supplierId;
+    }
+
+    public void setSupplierId(Long supplierId) {
+        this.supplierId = supplierId;
+    }
+
+    public String getSupplierName() {
+        return supplierName;
+    }
+
+    public void setSupplierName(String supplierName) {
+        this.supplierName = supplierName;
+    }
+}

+ 138 - 0
sop-business/src/main/java/com/qmth/sop/business/bean/result/CrmProgressSopResult.java

@@ -0,0 +1,138 @@
+package com.qmth.sop.business.bean.result;
+
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * @Description  项目进度监控-sop详情
+ * @Author haoguanghui
+ * @date 2024/09/11
+ */
+public class CrmProgressSopResult {
+
+    @ApiModelProperty(value = "客户名称")
+    private String customName;
+
+    @ApiModelProperty(value = "项目名称")
+    private String crmName;
+
+    @ApiModelProperty(value = "科目名称")
+    private String courseName;
+
+    @ApiModelProperty(value = "审核所处阶段")
+    private String taskName;
+
+    @ApiModelProperty(value = "大区经理ID")
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long leadId;
+
+    @ApiModelProperty(value = "大区经理")
+    private String leadName;
+
+    @ApiModelProperty(value = "派单详情ID")
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long crmDetailId;
+
+    @ApiModelProperty(value = "供应商名称")
+    private String supplierName;
+
+    @ApiModelProperty(value = "供应商ID")
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long supplierId;
+
+    @ApiModelProperty(value = "负责人名称")
+    private String realName;
+
+    @ApiModelProperty(value = "负责人ID")
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long userId;
+
+    public String getCustomName() {
+        return customName;
+    }
+
+    public void setCustomName(String customName) {
+        this.customName = customName;
+    }
+
+    public String getCrmName() {
+        return crmName;
+    }
+
+    public void setCrmName(String crmName) {
+        this.crmName = crmName;
+    }
+
+    public String getCourseName() {
+        return courseName;
+    }
+
+    public void setCourseName(String courseName) {
+        this.courseName = courseName;
+    }
+
+    public String getTaskName() {
+        return taskName;
+    }
+
+    public void setTaskName(String taskName) {
+        this.taskName = taskName;
+    }
+
+    public Long getLeadId() {
+        return leadId;
+    }
+
+    public void setLeadId(Long leadId) {
+        this.leadId = leadId;
+    }
+
+    public String getLeadName() {
+        return leadName;
+    }
+
+    public void setLeadName(String leadName) {
+        this.leadName = leadName;
+    }
+
+    public Long getCrmDetailId() {
+        return crmDetailId;
+    }
+
+    public void setCrmDetailId(Long crmDetailId) {
+        this.crmDetailId = crmDetailId;
+    }
+
+    public String getSupplierName() {
+        return supplierName;
+    }
+
+    public void setSupplierName(String supplierName) {
+        this.supplierName = supplierName;
+    }
+
+    public Long getSupplierId() {
+        return supplierId;
+    }
+
+    public void setSupplierId(Long supplierId) {
+        this.supplierId = supplierId;
+    }
+
+    public String getRealName() {
+        return realName;
+    }
+
+    public void setRealName(String realName) {
+        this.realName = realName;
+    }
+
+    public Long getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Long userId) {
+        this.userId = userId;
+    }
+}

+ 66 - 0
sop-business/src/main/java/com/qmth/sop/business/bean/result/ServiceUnitOverviewResult.java

@@ -0,0 +1,66 @@
+package com.qmth.sop.business.bean.result;
+
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * @Description 服务单元概况 返回结果
+ * @Author haoguanghui
+ * @date 2024/09/09
+ */
+public class ServiceUnitOverviewResult {
+
+    @ApiModelProperty(value = "累计派单数量")
+    private Integer crmNum;
+
+    @ApiModelProperty(value = "sop数量")
+    private Integer sopNum;
+
+    @ApiModelProperty(value = "大区经理人数")
+    private Integer leadNum;
+
+    @ApiModelProperty(value = "总投入人数")
+    private Integer totalPerson;
+
+    @ApiModelProperty(value = "投入扫描仪数量")
+    private Integer scannerNum;
+
+    public Integer getCrmNum() {
+        return crmNum;
+    }
+
+    public void setCrmNum(Integer crmNum) {
+        this.crmNum = crmNum;
+    }
+
+    public Integer getSopNum() {
+        return sopNum;
+    }
+
+    public void setSopNum(Integer sopNum) {
+        this.sopNum = sopNum;
+    }
+
+    public Integer getLeadNum() {
+        return leadNum;
+    }
+
+    public void setLeadNum(Integer leadNum) {
+        this.leadNum = leadNum;
+    }
+
+    public Integer getTotalPerson() {
+        return totalPerson;
+    }
+
+    public void setTotalPerson(Integer totalPerson) {
+        this.totalPerson = totalPerson;
+    }
+
+    public Integer getScannerNum() {
+        return scannerNum;
+    }
+
+    public void setScannerNum(Integer scannerNum) {
+        this.scannerNum = scannerNum;
+    }
+}

+ 80 - 0
sop-business/src/main/java/com/qmth/sop/business/bean/result/ServiceUnitProgressResult.java

@@ -0,0 +1,80 @@
+package com.qmth.sop.business.bean.result;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.qmth.sop.common.enums.CrmProcessEnum;
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * @Description 服务单元分析-项目整体进度
+ * @Author haoguanghui
+ * @date 2024/09/09
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class ServiceUnitProgressResult {
+
+    @ApiModelProperty(value = "所处阶段")
+    private CrmProcessEnum process;
+
+    @ApiModelProperty(value = "sop数量")
+    private Integer sopNum;
+
+    @ApiModelProperty(value = "占比")
+    private String ratio;
+
+    @ApiModelProperty(value = "所处阶段")
+    private String taskName;
+
+    @ApiModelProperty(value = "sop编号")
+    private String sopNo;
+
+    @ApiModelProperty(value = "客户所在省份")
+    private String provinceName;
+
+    public CrmProcessEnum getProcess() {
+        return process;
+    }
+
+    public void setProcess(CrmProcessEnum process) {
+        this.process = process;
+    }
+
+    public Integer getSopNum() {
+        return sopNum;
+    }
+
+    public void setSopNum(Integer sopNum) {
+        this.sopNum = sopNum;
+    }
+
+    public String getRatio() {
+        return ratio;
+    }
+
+    public void setRatio(String ratio) {
+        this.ratio = ratio;
+    }
+
+    public String getTaskName() {
+        return taskName;
+    }
+
+    public void setTaskName(String taskName) {
+        this.taskName = taskName;
+    }
+
+    public String getSopNo() {
+        return sopNo;
+    }
+
+    public void setSopNo(String sopNo) {
+        this.sopNo = sopNo;
+    }
+
+    public String getProvinceName() {
+        return provinceName;
+    }
+
+    public void setProvinceName(String provinceName) {
+        this.provinceName = provinceName;
+    }
+}

+ 124 - 0
sop-business/src/main/java/com/qmth/sop/business/bean/result/ServiceUnitProvinceResult.java

@@ -0,0 +1,124 @@
+package com.qmth.sop.business.bean.result;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * @Description 区域概况
+ * @Author haoguanghui
+ * @date 2024/09/10
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class ServiceUnitProvinceResult {
+
+    @ApiModelProperty(value = "省份")
+    private String province;
+
+    @ApiModelProperty(value = "派单数")
+    private Integer crmNum;
+
+    @ApiModelProperty(value = "sop数量")
+    private Integer sopNum;
+
+    @ApiModelProperty(value = "准备阶段sop数量")
+    private Integer prepareSopNum;
+
+    @ApiModelProperty(value = "扫描阶段sop数量")
+    private Integer scanSopNum;
+
+    @ApiModelProperty(value = "评卷阶段sop数量")
+    private Integer markSopNum;
+
+    @ApiModelProperty(value = "收尾阶段sop数量")
+    private Integer finalSopNum;
+
+    @ApiModelProperty(value = "已完成sop数量")
+    private Integer finishSopNum;
+
+    @ApiModelProperty(value = "按照省份统计派单的数量")
+    private Integer provinceCrmNum;
+
+    @ApiModelProperty(value = "扫描、评卷、收尾、结束sop数量之和")
+    private Integer sopSum;
+
+
+    public String getProvince() {
+        return province;
+    }
+
+    public void setProvince(String province) {
+        this.province = province;
+    }
+
+    public Integer getCrmNum() {
+        return crmNum;
+    }
+
+    public void setCrmNum(Integer crmNum) {
+        this.crmNum = crmNum;
+    }
+
+    public Integer getSopNum() {
+        return sopNum;
+    }
+
+    public void setSopNum(Integer sopNum) {
+        this.sopNum = sopNum;
+    }
+
+    public Integer getPrepareSopNum() {
+        return prepareSopNum;
+    }
+
+    public void setPrepareSopNum(Integer prepareSopNum) {
+        this.prepareSopNum = prepareSopNum;
+    }
+
+    public Integer getScanSopNum() {
+        return scanSopNum;
+    }
+
+    public void setScanSopNum(Integer scanSopNum) {
+        this.scanSopNum = scanSopNum;
+    }
+
+    public Integer getMarkSopNum() {
+        return markSopNum;
+    }
+
+    public void setMarkSopNum(Integer markSopNum) {
+        this.markSopNum = markSopNum;
+    }
+
+    public Integer getFinalSopNum() {
+        return finalSopNum;
+    }
+
+    public void setFinalSopNum(Integer finalSopNum) {
+        this.finalSopNum = finalSopNum;
+    }
+
+    public Integer getFinishSopNum() {
+        return finishSopNum;
+    }
+
+    public void setFinishSopNum(Integer finishSopNum) {
+        this.finishSopNum = finishSopNum;
+    }
+
+    public Integer getProvinceCrmNum() {
+        return provinceCrmNum;
+    }
+
+    public void setProvinceCrmNum(Integer provinceCrmNum) {
+        this.provinceCrmNum = provinceCrmNum;
+    }
+
+    public Integer getSopSum() {
+        return sopSum;
+    }
+
+    public void setSopSum(Integer sopSum) {
+        this.sopSum = sopSum;
+    }
+}

+ 114 - 0
sop-business/src/main/java/com/qmth/sop/business/bean/result/SopInfoExportResult.java

@@ -0,0 +1,114 @@
+package com.qmth.sop.business.bean.result;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.alibaba.excel.annotation.write.style.ColumnWidth;
+import com.alibaba.excel.annotation.write.style.HeadFontStyle;
+import com.alibaba.excel.annotation.write.style.HeadStyle;
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * @Description sop导出结果
+ * @Author haoguanghui
+ * @date 2024/09/10
+ */
+@ColumnWidth(value = 30)
+@HeadStyle(fillForegroundColor = 12)
+@HeadFontStyle(color = 1)
+public class SopInfoExportResult {
+
+    @ApiModelProperty(value = "客户名称")
+    @ExcelProperty(value = "客户名称")
+    private String customName;
+
+    @ApiModelProperty(value = "项目名称")
+    @ExcelProperty(value = "项目名称")
+    private String crmName;
+
+    @ApiModelProperty(value = "科目名称")
+    @ExcelProperty(value = "科目名称")
+    private String courseName;
+
+    @ApiModelProperty(value = "当前进度")
+    @ExcelProperty(value = "当前进度")
+    private String process;
+
+    @ApiModelProperty(value = "大区经理")
+    @ExcelProperty(value = "大区经理")
+    private String leadName;
+
+    @ApiModelProperty(value = "区域协调人")
+    @ExcelProperty(value = "区域协调人")
+    private String regionCoordinator;
+
+    @ApiModelProperty(value = "项目经理")
+    @ExcelProperty(value = "项目经理")
+    private String projectManager;
+
+    @ApiModelProperty(value = "工程师")
+    @ExcelProperty(value = "工程师")
+    private String engineer;
+
+    public String getCustomName() {
+        return customName;
+    }
+
+    public void setCustomName(String customName) {
+        this.customName = customName;
+    }
+
+    public String getCrmName() {
+        return crmName;
+    }
+
+    public void setCrmName(String crmName) {
+        this.crmName = crmName;
+    }
+
+    public String getCourseName() {
+        return courseName;
+    }
+
+    public void setCourseName(String courseName) {
+        this.courseName = courseName;
+    }
+
+    public String getProcess() {
+        return process;
+    }
+
+    public void setProcess(String process) {
+        this.process = process;
+    }
+
+    public String getLeadName() {
+        return leadName;
+    }
+
+    public void setLeadName(String leadName) {
+        this.leadName = leadName;
+    }
+
+    public String getRegionCoordinator() {
+        return regionCoordinator;
+    }
+
+    public void setRegionCoordinator(String regionCoordinator) {
+        this.regionCoordinator = regionCoordinator;
+    }
+
+    public String getProjectManager() {
+        return projectManager;
+    }
+
+    public void setProjectManager(String projectManager) {
+        this.projectManager = projectManager;
+    }
+
+    public String getEngineer() {
+        return engineer;
+    }
+
+    public void setEngineer(String engineer) {
+        this.engineer = engineer;
+    }
+}

+ 21 - 0
sop-business/src/main/java/com/qmth/sop/business/mapper/CrmProgressMonitorMapper.java

@@ -0,0 +1,21 @@
+package com.qmth.sop.business.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.qmth.sop.business.bean.result.CrmProgressSopResult;
+import com.qmth.sop.business.entity.TBSopInfo;
+import com.qmth.sop.common.enums.SopRoleTypeEnum;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * @Description 项目进度监控
+ * @Author haoguanghui
+ * @date 2024/09/11
+ */
+public interface CrmProgressMonitorMapper extends BaseMapper<TBSopInfo> {
+
+    List<CrmProgressSopResult> listSop(@Param(value = "serviceUnitId") Long serviceUnitId);
+
+    List<CrmProgressSopResult> listSopByRoleType(@Param(value = "serviceUnitId") Long serviceUnitId, @Param(value = "sopRoleType") SopRoleTypeEnum sopRoleType);
+}

+ 135 - 0
sop-business/src/main/java/com/qmth/sop/business/mapper/ServiceUnitAnalyseMapper.java

@@ -0,0 +1,135 @@
+package com.qmth.sop.business.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.qmth.sop.business.bean.result.ProjectMonitorResult;
+import com.qmth.sop.business.bean.result.ServiceUnitProgressResult;
+import com.qmth.sop.business.bean.result.ServiceUnitProvinceResult;
+import com.qmth.sop.business.entity.TBService;
+import com.qmth.sop.common.enums.CrmProcessEnum;
+import com.qmth.sop.common.enums.DeviceDeliveryStatusEnum;
+import com.qmth.sop.common.enums.InOutTypeEnum;
+import com.qmth.sop.common.enums.SopRoleTypeEnum;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+public interface ServiceUnitAnalyseMapper extends BaseMapper<TBService> {
+
+    /**
+     * 统计服务单元下大区经理的人数
+     *
+     * @param serviceUnitId 服务单元ID
+     * @return 大区经理的人数
+     */
+    int countLead(@Param(value = "serviceUnitId") Long serviceUnitId);
+
+    /**
+     * 统计服务单元下所有的参与人数
+     *
+     * @param serviceUnitId 服务单元ID
+     * @return 参与人数
+     */
+    int countTotalPerson(@Param(value = "serviceUnitId") Long serviceUnitId);
+
+    /**
+     * 统计服务单元所有已签收的设备
+     *
+     * @param serviceUnitId      服务单元ID
+     * @param deliveryStatusList 设备使用状态List
+     * @param out                设备出入库
+     * @return 服务单元下所有使用的设备
+     */
+    int countScanner(@Param(value = "serviceUnitId") Long serviceUnitId, @Param(value = "deliveryStatusList") List<DeviceDeliveryStatusEnum> deliveryStatusList,
+            @Param(value = "out") InOutTypeEnum out);
+
+    /**
+     * 统计服务单元所有阶段的sop
+     *
+     * @param serviceUnitId 服务单元ID
+     * @return sop列表
+     */
+    List<ServiceUnitProgressResult> listSopProgress(@Param(value = "serviceUnitId") Long serviceUnitId);
+
+    /**
+     * 统计服务单元所有省份的派单数量
+     *
+     * @param serviceUnitId 服务单元ID
+     * @return 各省份派单数量Map
+     */
+    List<ServiceUnitProvinceResult> listProvinceCrmNum(@Param(value = "serviceUnitId") Long serviceUnitId);
+
+    /**
+     * 通过服务单元等条件分页查询sop
+     *
+     * @param page          分页参数
+     * @param serviceUnitId 服务单元ID
+     * @param crmUserId     客户经理
+     * @param customName    客户名称
+     * @param process       当前进度
+     * @param leadId        大区经理
+     * @param province      客户所在省份
+     * @param processList   进度List
+     * @return 分页结果
+     */
+    IPage<ProjectMonitorResult> pageSop(Page<ProjectMonitorResult> page, @Param(value = "serviceUnitId") Long serviceUnitId,
+            @Param(value = "crmUserId") Long crmUserId, @Param(value = "customName") String customName, @Param(value = "process") CrmProcessEnum process,
+            @Param(value = "leadId") Long leadId, @Param(value = "province") String province, @Param(value = "prList") List<String> processList);
+
+    /**
+     * 过服务单元等条件查询sop列表
+     *
+     * @param serviceUnitId 服务单元ID
+     * @param crmUserId     客户经理
+     * @param customName    客户名称
+     * @param process       当前进度
+     * @param leadId        大区经理
+     * @param province      客户所在省份
+     * @param processList   进度List
+     * @return 列表
+     */
+    List<ProjectMonitorResult> listSop(@Param(value = "serviceUnitId") Long serviceUnitId,
+            @Param(value = "crmUserId") Long crmUserId, @Param(value = "customName") String customName, @Param(value = "process") CrmProcessEnum process,
+            @Param(value = "leadId") Long leadId, @Param(value = "province") String province, @Param(value = "prList") List<String> processList);
+
+    /**
+     * 通过服务单元等条件分页查询sop
+     *
+     * @param page          分页参数
+     * @param serviceUnitId 服务单元ID
+     * @param crmUserId     客户经理
+     * @param customName    客户名称
+     * @param process       当前进度
+     * @param leadId        大区经理
+     * @param province      客户所在省份
+     * @param processList   进度List
+     * @param supplierId    人力商ID
+     * @param coordinatorId 区域协调人ID
+     * @param roleType      sop角色
+     * @return 分页结果
+     */
+    IPage<ProjectMonitorResult> pageSopByUserAllocation(Page<ProjectMonitorResult> page, @Param(value = "serviceUnitId") Long serviceUnitId,
+            @Param(value = "crmUserId") Long crmUserId, @Param(value = "customName") String customName, @Param(value = "process") CrmProcessEnum process,
+            @Param(value = "leadId") Long leadId, @Param(value = "province") String province, @Param(value = "prList") List<String> processList,
+            @Param(value = "supplierId") Long supplierId, @Param(value = "coordinatorId") Long coordinatorId,
+            @Param(value = "roleType") SopRoleTypeEnum roleType);
+
+    /**
+     * 过服务单元等条件查询sop列表
+     *
+     * @param serviceUnitId 服务单元ID
+     * @param crmUserId     客户经理
+     * @param customName    客户名称
+     * @param process       当前进度
+     * @param leadId        大区经理
+     * @param province      客户所在省份
+     * @param processList   进度List
+     * @return 列表
+     */
+    List<ProjectMonitorResult> listSopByUserAllocation(@Param(value = "serviceUnitId") Long serviceUnitId,
+            @Param(value = "crmUserId") Long crmUserId, @Param(value = "customName") String customName, @Param(value = "process") CrmProcessEnum process,
+            @Param(value = "leadId") Long leadId, @Param(value = "province") String province, @Param(value = "prList") List<String> processList,
+            @Param(value = "supplierId") Long supplierId, @Param(value = "coordinatorId") Long coordinatorId,
+            @Param(value = "roleType") SopRoleTypeEnum roleType);
+}

+ 19 - 0
sop-business/src/main/java/com/qmth/sop/business/service/CrmProgressMonitorService.java

@@ -0,0 +1,19 @@
+package com.qmth.sop.business.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.qmth.sop.business.bean.result.CrmProgressResult;
+import com.qmth.sop.business.entity.TBSopInfo;
+import com.qmth.sop.common.enums.CrmProgressMonitorEnum;
+
+import java.util.List;
+
+/**
+ * @Description 项目进度监控
+ * @Author haoguanghui
+ * @date 2024/09/11
+ */
+public interface CrmProgressMonitorService extends IService<TBSopInfo> {
+
+    List<CrmProgressResult> listSopNum(Long serviceUnitId, CrmProgressMonitorEnum monitorEnum);
+
+}

+ 34 - 0
sop-business/src/main/java/com/qmth/sop/business/service/ServiceUnitAnalyseService.java

@@ -0,0 +1,34 @@
+package com.qmth.sop.business.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.qmth.sop.business.bean.result.ProjectMonitorResult;
+import com.qmth.sop.business.bean.result.ServiceUnitOverviewResult;
+import com.qmth.sop.business.bean.result.ServiceUnitProgressResult;
+import com.qmth.sop.business.bean.result.ServiceUnitProvinceResult;
+import com.qmth.sop.business.entity.TBService;
+import com.qmth.sop.common.enums.CrmProcessEnum;
+
+import java.util.List;
+
+/**
+ * @Description 服务单元分析
+ * @Author haoguanghui
+ * @date 2024/09/09
+ */
+public interface ServiceUnitAnalyseService extends IService<TBService> {
+
+    ServiceUnitOverviewResult overview(Long serviceUnitId);
+
+    List<ServiceUnitProgressResult> progress(Long serviceUnitId);
+
+    List<ServiceUnitProvinceResult> province(Long serviceUnitId);
+
+    IPage<ProjectMonitorResult> pageSop(Page<ProjectMonitorResult> page, Long serviceUnitId, Long crmUserId, String customName, CrmProcessEnum process,
+            Long leadId, String province, Long supplierId, Long coordinatorId);
+
+    void export(Long serviceUnitId, Long crmUserId, String customName, CrmProcessEnum process, Long leadId, String province, Long supplierId,
+            Long coordinatorId);
+
+}

+ 195 - 0
sop-business/src/main/java/com/qmth/sop/business/service/impl/CrmProgressMonitorServiceImpl.java

@@ -0,0 +1,195 @@
+package com.qmth.sop.business.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.qmth.sop.business.bean.result.CrmProgressResult;
+import com.qmth.sop.business.bean.result.CrmProgressSopResult;
+import com.qmth.sop.business.entity.TBSopInfo;
+import com.qmth.sop.business.mapper.CrmProgressMonitorMapper;
+import com.qmth.sop.business.service.CrmProgressMonitorService;
+import com.qmth.sop.common.enums.CrmProcessEnum;
+import com.qmth.sop.common.enums.CrmProgressMonitorEnum;
+import com.qmth.sop.common.enums.SopRoleTypeEnum;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.text.NumberFormat;
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+@Service
+public class CrmProgressMonitorServiceImpl extends ServiceImpl<CrmProgressMonitorMapper, TBSopInfo> implements CrmProgressMonitorService {
+
+
+    @Override
+    public List<CrmProgressResult> listSopNum(Long serviceUnitId, CrmProgressMonitorEnum monitorEnum) {
+        List<CrmProgressResult> resultList = new ArrayList<>();
+        List<CrmProgressSopResult> sopList;
+        Function<CrmProgressSopResult, List<Object>> compositeKey;
+
+        //sop结果
+        if (monitorEnum.equals(CrmProgressMonitorEnum.BY_LEAD)) {
+            sopList = baseMapper.listSop(serviceUnitId);
+            compositeKey = sop -> Arrays.asList(sop.getLeadId(), sop.getLeadName());
+        } else if (monitorEnum.equals(CrmProgressMonitorEnum.BY_SUPPLIER)) {
+            sopList = baseMapper.listSopByRoleType(serviceUnitId, SopRoleTypeEnum.PROJECT_MANAGER);
+            compositeKey = sop -> Arrays.asList(sop.getSupplierId(), sop.getSupplierName());
+        } else {
+            sopList = baseMapper.listSopByRoleType(serviceUnitId, SopRoleTypeEnum.REGION_COORDINATOR);
+            compositeKey = sop -> Arrays.asList(sop.getUserId(), sop.getRealName());
+        }
+
+        Map<List<Object>, List<CrmProgressSopResult>> sopMap = sopList.stream().collect(Collectors.groupingBy(compositeKey, Collectors.toList()));
+        Set<Map.Entry<List<Object>, List<CrmProgressSopResult>>> entries = sopMap.entrySet();
+        for (Map.Entry<List<Object>, List<CrmProgressSopResult>> entry : entries) {
+            CrmProgressResult result = new CrmProgressResult();
+            List<Object> list = entry.getKey();
+            List<CrmProgressSopResult> crmProgressSopList = entry.getValue();
+
+            //按照不同的类别设置大区经理、供应商、区域协调人
+            if (monitorEnum.equals(CrmProgressMonitorEnum.BY_LEAD)) {
+                result.setLeadId(Long.valueOf(String.valueOf(list.get(0))));
+                result.setLeadName(String.valueOf(list.get(1)));
+            } else if (monitorEnum.equals(CrmProgressMonitorEnum.BY_SUPPLIER)) {
+                result.setSupplierId(Long.valueOf(list.get(0).toString()));
+                result.setSupplierName(list.get(1).toString());
+            } else {
+                result.setCoordinatorId(Long.valueOf(list.get(0).toString()));
+                result.setCoordinatorName(list.get(1).toString());
+            }
+
+            result.setTotal(crmProgressSopList.size());
+            resultList.add(result);
+        }
+        //大区经理的sop数量从高到低排序
+        List<CrmProgressResult> sopOrderList = resultList.stream()
+                .sorted(Comparator.comparing(CrmProgressResult::getTotal).reversed())
+                .collect(Collectors.toList());
+
+        //填充各阶段sop的数量
+        for (CrmProgressResult result : sopOrderList) {
+
+            List<CrmProgressSopResult> filterSopList;
+            if (monitorEnum.equals(CrmProgressMonitorEnum.BY_LEAD)) {
+                filterSopList = sopList.stream()
+                        .filter(item -> item.getLeadId().equals(result.getLeadId()))
+                        .collect(Collectors.toList());
+            } else if (monitorEnum.equals(CrmProgressMonitorEnum.BY_SUPPLIER)) {
+                filterSopList = sopList.stream()
+                        .filter(item -> item.getSupplierId().equals(result.getSupplierId()))
+                        .collect(Collectors.toList());
+            } else {
+                filterSopList = sopList.stream()
+                        .filter(item -> item.getUserId().equals(result.getCoordinatorId()))
+                        .collect(Collectors.toList());
+            }
+
+            //设置界面展示的阶段
+            setDisplayProcess(result, filterSopList);
+        }
+
+        //合计
+        getSum(resultList);
+
+        return resultList;
+    }
+
+    private void getSum(List<CrmProgressResult> resultList) {
+        if (resultList != null && !resultList.isEmpty()) {
+            CrmProgressResult sumResult = new CrmProgressResult();
+            //计算总量
+            int sum = resultList.stream().mapToInt(CrmProgressResult::getTotal).sum();
+            //准备阶段的总量
+            int prepareTotal = resultList.stream().mapToInt(CrmProgressResult::getPrepareSopNum).sum();
+            //扫描阶段的总量
+            int scanTotal = resultList.stream().mapToInt(CrmProgressResult::getScanSopNum).sum();
+            //试卷阶段的总量
+            int markTotal = resultList.stream().mapToInt(CrmProgressResult::getMarkSopNum).sum();
+            //收尾阶段的总量
+            int finalTotal = resultList.stream().mapToInt(CrmProgressResult::getFinalSopNum).sum();
+            //已完成阶段的总量
+            int finishTotal = resultList.stream().mapToInt(CrmProgressResult::getFinishSopNum).sum();
+
+            sumResult.setTotal(sum);
+            sumResult.setPrepareSopNum(prepareTotal);
+            sumResult.setPrepareSopRatio(getRatio(BigDecimal.valueOf(sum), BigDecimal.valueOf(prepareTotal)));
+            sumResult.setScanSopNum(scanTotal);
+            sumResult.setScanSopRatio(getRatio(BigDecimal.valueOf(sum), BigDecimal.valueOf(scanTotal)));
+            sumResult.setMarkSopNum(markTotal);
+            sumResult.setMarkSopRatio(getRatio(BigDecimal.valueOf(sum), BigDecimal.valueOf(markTotal)));
+            sumResult.setFinalSopNum(finalTotal);
+            sumResult.setFinalSopRatio(getRatio(BigDecimal.valueOf(sum), BigDecimal.valueOf(finalTotal)));
+            sumResult.setFinishSopNum(finishTotal);
+            sumResult.setFinishSopRatio(getRatio(BigDecimal.valueOf(sum), BigDecimal.valueOf(finishTotal)));
+            resultList.add(sumResult);
+        }
+    }
+
+    private String getRatio(BigDecimal total, BigDecimal sopNum) {
+        String ratio = "";
+        //计算占比
+        if (total.intValue() != 0) {
+            sopNum = sopNum.divide(total, 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100));
+            NumberFormat formatter = NumberFormat.getPercentInstance();
+            formatter.setMinimumFractionDigits(2);
+            ratio = formatter.format(sopNum.doubleValue() / 100);
+        }
+        return ratio;
+    }
+
+    private void setDisplayProcess(CrmProgressResult result, List<CrmProgressSopResult> filterSopList) {
+        Map<String, List<CrmProgressSopResult>> map = filterSopList.stream().collect(Collectors.groupingBy(CrmProgressSopResult::getTaskName));
+        Map<CrmProcessEnum, Integer> summaryMap = getCrmProcessNum(map);
+        for (CrmProcessEnum process : CrmProcessEnum.values()) {
+            Integer sopNum = 0;
+            String ratio = "";
+            //设置包含了某阶段的sop数量和占比
+            if (summaryMap.containsKey(process)) {
+                sopNum = summaryMap.get(process);
+                //计算占比
+                BigDecimal processNum = BigDecimal.valueOf(sopNum);
+                BigDecimal totalNum = BigDecimal.valueOf(result.getTotal());
+                ratio = getRatio(totalNum, processNum);
+            }
+            //设置各阶段的sop数量和占比
+            switch (process) {
+            case PREPARE:
+                result.setPrepareSopNum(sopNum);
+                result.setPrepareSopRatio(ratio);
+                break;
+            case SCAN:
+                result.setScanSopNum(sopNum);
+                result.setScanSopRatio(ratio);
+                break;
+            case MARK:
+                result.setMarkSopNum(sopNum);
+                result.setMarkSopRatio(ratio);
+                break;
+            case FINAL:
+                result.setFinalSopNum(sopNum);
+                result.setFinalSopRatio(ratio);
+                break;
+            case FINISH:
+                result.setFinishSopNum(sopNum);
+                result.setFinishSopRatio(ratio);
+                break;
+            }
+        }
+    }
+
+    private Map<CrmProcessEnum, Integer> getCrmProcessNum(Map<String, List<CrmProgressSopResult>> processMap) {
+        Map<CrmProcessEnum, Integer> summaryMap = new HashMap<>();
+        processMap.forEach((k, v) -> {
+            CrmProcessEnum crmProcess = CrmProcessEnum.findCrmProcessByDesc(k);
+            if (crmProcess == null)
+                return;
+            if (!summaryMap.containsKey(crmProcess)) {
+                summaryMap.put(crmProcess, v.size());
+            } else {
+                summaryMap.put(crmProcess, summaryMap.get(crmProcess) + v.size());
+            }
+        });
+        return summaryMap;
+    }
+}

+ 322 - 0
sop-business/src/main/java/com/qmth/sop/business/service/impl/ServiceUnitAnalyseServiceImpl.java

@@ -0,0 +1,322 @@
+package com.qmth.sop.business.service.impl;
+
+import com.alibaba.excel.EasyExcel;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.qmth.sop.business.bean.dto.UserArchivesAllocationDto;
+import com.qmth.sop.business.bean.result.*;
+import com.qmth.sop.business.entity.TBCrm;
+import com.qmth.sop.business.entity.TBService;
+import com.qmth.sop.business.entity.TBSopInfo;
+import com.qmth.sop.business.mapper.ServiceUnitAnalyseMapper;
+import com.qmth.sop.business.service.ServiceUnitAnalyseService;
+import com.qmth.sop.business.service.TBCrmService;
+import com.qmth.sop.business.service.TBSopInfoService;
+import com.qmth.sop.business.service.TBUserArchivesAllocationService;
+import com.qmth.sop.common.contant.SystemConstant;
+import com.qmth.sop.common.enums.*;
+import com.qmth.sop.common.util.FileUtil;
+import com.qmth.sop.common.util.ServletUtil;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import java.io.File;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.text.NumberFormat;
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Service
+public class ServiceUnitAnalyseServiceImpl extends ServiceImpl<ServiceUnitAnalyseMapper, TBService> implements ServiceUnitAnalyseService {
+
+    @Resource
+    private TBCrmService tbCrmService;
+
+    @Resource
+    private TBSopInfoService tbSopInfoService;
+
+    @Resource
+    private TBUserArchivesAllocationService tbUserArchivesAllocationService;
+
+    @Override
+    public ServiceUnitOverviewResult overview(Long serviceUnitId) {
+        ServiceUnitOverviewResult result = new ServiceUnitOverviewResult();
+        //派单数量
+        LambdaQueryWrapper<TBCrm> crmWrapper = new LambdaQueryWrapper<>();
+        crmWrapper.eq(TBCrm::getServiceId, serviceUnitId);
+        int crmNum = tbCrmService.count(crmWrapper);
+        result.setCrmNum(crmNum);
+
+        //sop数量
+        LambdaQueryWrapper<TBSopInfo> sopWrapper = new LambdaQueryWrapper<>();
+        sopWrapper.eq(TBSopInfo::getServiceId, serviceUnitId);
+        int sopNum = tbSopInfoService.count(sopWrapper);
+        result.setSopNum(sopNum);
+
+        //大区经理人数
+        int leadNum = baseMapper.countLead(serviceUnitId);
+        result.setLeadNum(leadNum);
+
+        //总投入人数
+        int totalPerson = baseMapper.countTotalPerson(serviceUnitId);
+        result.setTotalPerson(totalPerson);
+
+        //扫描仪数量
+        List<DeviceDeliveryStatusEnum> deliveryStatusList = Arrays.asList(DeviceDeliveryStatusEnum.USING, DeviceDeliveryStatusEnum.IN, DeviceDeliveryStatusEnum.TRANSFER,
+                DeviceDeliveryStatusEnum.TRANSFER_SIGN);
+        int scannerNum = baseMapper.countScanner(serviceUnitId, deliveryStatusList, InOutTypeEnum.OUT);
+        result.setScannerNum(scannerNum);
+        return result;
+    }
+
+    @Override
+    public List<ServiceUnitProgressResult> progress(Long serviceUnitId) {
+        List<ServiceUnitProgressResult> progressResultList = new ArrayList<>();
+        List<ServiceUnitProgressResult> sopList = baseMapper.listSopProgress(serviceUnitId);
+        if (sopList != null && !sopList.isEmpty()) {
+            //系统中任务状态汇总,并根据业务状态计算数量
+            Map<String, List<ServiceUnitProgressResult>> map = sopList.stream().collect(Collectors.groupingBy(ServiceUnitProgressResult::getTaskName));
+            Map<CrmProcessEnum, Integer> summaryMap = getCrmProcessNum(map);
+
+            //sop的总量
+            int total = summaryMap.values().stream().mapToInt(Integer::intValue).sum();
+            BigDecimal totalNum = BigDecimal.valueOf(total);
+            NumberFormat formatter = NumberFormat.getPercentInstance();
+            formatter.setMinimumFractionDigits(2);
+
+            //界面定义的阶段组装
+            ServiceUnitProgressResult progressResult;
+            CrmProcessEnum[] processValues = CrmProcessEnum.values();
+            for (CrmProcessEnum process : processValues) {
+                progressResult = new ServiceUnitProgressResult();
+                //补充sop数量为0的阶段
+                if (!summaryMap.containsKey(process)) {
+                    progressResult.setProcess(process);
+                    progressResult.setSopNum(0);
+                    progressResultList.add(progressResult);
+                } else {
+                    Integer sopNum = summaryMap.get(process);
+                    progressResult.setProcess(process);
+                    progressResult.setSopNum(sopNum);
+                    //计算占比
+                    BigDecimal processNum = BigDecimal.valueOf(sopNum);
+                    processNum = processNum.divide(totalNum, 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100));
+                    progressResult.setRatio(formatter.format(processNum.doubleValue() / 100));
+                    progressResultList.add(progressResult);
+                }
+            }
+        }
+        return progressResultList;
+    }
+
+    @Override
+    public List<ServiceUnitProvinceResult> province(Long serviceUnitId) {
+        List<ServiceUnitProvinceResult> provinceResultList = new ArrayList<>();
+        List<ServiceUnitProgressResult> sopList = baseMapper.listSopProgress(serviceUnitId);
+        List<ServiceUnitProvinceResult> provinceCrmNumList = baseMapper.listProvinceCrmNum(serviceUnitId);
+
+        // 各省份的派单数量
+        provinceCrmNumList.forEach(province -> {
+            ServiceUnitProvinceResult result = new ServiceUnitProvinceResult();
+            result.setProvince(province.getProvince());
+            result.setCrmNum(province.getProvinceCrmNum());
+
+            //某个省份下所有sop数量,并按照所处阶段分组
+            Map<String, List<ServiceUnitProgressResult>> processMap = sopList.stream()
+                    .filter(item -> StringUtils.isNotEmpty(item.getProvinceName()) && item.getProvinceName().equals(province.getProvince()))
+                    .collect(Collectors.groupingBy(ServiceUnitProgressResult::getTaskName));
+            //统计界面定义的阶段,sop的数量
+            Map<CrmProcessEnum, Integer> summaryMap = getCrmProcessNum(processMap);
+
+            Integer prepareSopNum = summaryMap.get(CrmProcessEnum.PREPARE);
+            Integer scanSopNum = summaryMap.get(CrmProcessEnum.SCAN);
+            Integer markSopNum = summaryMap.get(CrmProcessEnum.MARK);
+            Integer finalSopNum = summaryMap.get(CrmProcessEnum.FINAL);
+            Integer finishSopNum = summaryMap.get(CrmProcessEnum.FINISH);
+
+            result.setPrepareSopNum(prepareSopNum == null ? 0 : prepareSopNum);
+            result.setScanSopNum(scanSopNum == null ? 0 : scanSopNum);
+            result.setMarkSopNum(markSopNum == null ? 0 : markSopNum);
+            result.setFinalSopNum(finalSopNum == null ? 0 : finalSopNum);
+            result.setFinishSopNum(finishSopNum == null ? 0 : finishSopNum);
+            Integer sopNum = result.getPrepareSopNum() + result.getScanSopNum() + result.getMarkSopNum() + result.getFinalSopNum() + result.getFinishSopNum();
+            result.setSopNum(sopNum);
+            Integer sopSum =  result.getScanSopNum() + result.getMarkSopNum() + result.getFinalSopNum() + result.getFinishSopNum();
+            result.setSopSum(sopSum);
+
+            provinceResultList.add(result);
+        });
+
+        return provinceResultList;
+    }
+
+    @Override
+    public IPage<ProjectMonitorResult> pageSop(Page<ProjectMonitorResult> page, Long serviceUnitId, Long crmUserId, String customName, CrmProcessEnum process,
+            Long leadId, String province, Long supplierId, Long coordinatorId) {
+        List<String> processList = new ArrayList<>();
+        if (process != null) {
+            processList = getProcessList(process);
+        }
+        SopRoleTypeEnum roleType = null;
+        if (supplierId != null) {
+            roleType = SopRoleTypeEnum.PROJECT_MANAGER;
+        }
+        if (coordinatorId != null) {
+            roleType = SopRoleTypeEnum.REGION_COORDINATOR;
+        }
+
+        IPage<ProjectMonitorResult> pageSop;
+        // 人力供应商、区域协调人
+        if (supplierId == null && coordinatorId == null) {
+            pageSop = baseMapper.pageSop(new Page<>(page.getCurrent(), page.getSize()), serviceUnitId, crmUserId,
+                    customName, process, leadId, province, processList);
+        } else { // 其他
+            pageSop = baseMapper.pageSopByUserAllocation(new Page<>(page.getCurrent(), page.getSize()), serviceUnitId, crmUserId,
+                    customName, process, leadId, province, processList, supplierId, coordinatorId, roleType);
+        }
+
+        List<ProjectMonitorResult> monitorResultList = pageSop.getRecords();
+        for (ProjectMonitorResult monitorResult : monitorResultList) {
+            monitorResult.setProcess(CrmProcessEnum.findCrmProcessByDesc(monitorResult.getTaskName()));
+            //设置区域协调人、大区经理、工程师
+            List<UserArchivesAllocationDto> allocationDtoList = tbUserArchivesAllocationService.listUserArchivesAllocation(
+                    monitorResult.getCrmDetailId());
+            List<ProjectMonitorUserResult> coordinatorList = filterUserArchivesAllocation(allocationDtoList, SopRoleTypeEnum.REGION_COORDINATOR);
+            monitorResult.setRegionCoordinator(!coordinatorList.isEmpty() ? coordinatorList.get(0) : null);
+            List<ProjectMonitorUserResult> managerList = filterUserArchivesAllocation(allocationDtoList, SopRoleTypeEnum.PROJECT_MANAGER);
+            monitorResult.setProjectManager(!managerList.isEmpty() ? managerList.get(0) : null);
+            monitorResult.setEngineerList(filterUserArchivesAllocation(allocationDtoList, SopRoleTypeEnum.ENGINEER));
+        }
+        return pageSop;
+    }
+
+    @Override
+    public void export(Long serviceUnitId, Long crmUserId, String customName, CrmProcessEnum process, Long leadId, String province, Long supplierId, Long coordinatorId) {
+        List<String> processList = new ArrayList<>();
+        if (process != null) {
+            processList = getProcessList(process);
+        }
+        SopRoleTypeEnum roleType = null;
+        if (supplierId != null) {
+            roleType = SopRoleTypeEnum.PROJECT_MANAGER;
+        }
+        if (coordinatorId != null) {
+            roleType = SopRoleTypeEnum.REGION_COORDINATOR;
+        }
+
+        List<ProjectMonitorResult> resultList;
+        // 人力供应商、区域协调人
+        if (supplierId == null && coordinatorId == null) {
+            resultList = baseMapper.listSop(serviceUnitId, crmUserId, customName, process, leadId, province, processList);
+        } else {
+            resultList = baseMapper.listSopByUserAllocation(serviceUnitId, crmUserId, customName, process, leadId, province, processList, supplierId,
+                    coordinatorId, roleType);
+        }
+
+        //封装导出结果
+        List<SopInfoExportResult> exportResultList = new ArrayList<>();
+        for(ProjectMonitorResult result : resultList) {
+            result.setProcess(CrmProcessEnum.findCrmProcessByDesc(result.getTaskName()));
+            SopInfoExportResult exportResult = getSopInfoExportResult(result);
+            exportResultList.add(exportResult);
+        }
+
+        File fileTemp = null;
+        try {
+            fileTemp = SystemConstant.getFileTempVar(SystemConstant.XLSX_PREFIX);
+            EasyExcel.write(fileTemp, SopInfoExportResult.class).sheet("sop信息").doWrite(exportResultList);
+            HttpServletResponse response = ServletUtil.getResponse();
+            FileUtil.outputFile(response, fileTemp, "sop信息.xlsx");
+        } catch (IOException e) {
+            throw ExceptionResultEnum.ERROR.exception(e.getMessage());
+        } finally {
+            if (Objects.nonNull(fileTemp)) {
+                fileTemp.delete();
+            }
+        }
+    }
+
+
+    private SopInfoExportResult getSopInfoExportResult(ProjectMonitorResult result) {
+        SopInfoExportResult exportResult = new SopInfoExportResult();
+        exportResult.setCustomName(result.getCustomName());
+        exportResult.setCrmName(result.getCrmName());
+        exportResult.setCourseName(result.getCourseName());
+        exportResult.setProcess(result.getProcess().getTitle());
+        exportResult.setLeadName(result.getLeadName()+"-"+ result.getLeadMobile());
+        List<UserArchivesAllocationDto> allocationDtoList = tbUserArchivesAllocationService.listUserArchivesAllocation(result.getCrmDetailId());
+        //设置区域协调人
+        List<ProjectMonitorUserResult> coordinatorList = filterUserArchivesAllocation(allocationDtoList, SopRoleTypeEnum.REGION_COORDINATOR);
+        ProjectMonitorUserResult coordinator;
+        if(!coordinatorList.isEmpty()) {
+            coordinator = coordinatorList.get(0);
+            exportResult.setRegionCoordinator(String.join("-", coordinator.getSupplierName(), coordinator.getUserName(), coordinator.getMobileNumber()));
+        }
+
+        //设置项目经理
+        List<ProjectMonitorUserResult> managerList = filterUserArchivesAllocation(allocationDtoList, SopRoleTypeEnum.PROJECT_MANAGER);
+        if(!managerList.isEmpty()) {
+            ProjectMonitorUserResult projectManager = managerList.get(0);
+            exportResult.setProjectManager(String.join("-", projectManager.getSupplierName(), projectManager.getUserName(), projectManager.getMobileNumber()));
+        }
+
+        //设置工程师
+        List<ProjectMonitorUserResult> engineerList = filterUserArchivesAllocation(allocationDtoList, SopRoleTypeEnum.ENGINEER);
+        if(!engineerList.isEmpty()) {
+            int index = 0;
+            StringBuilder engineer = new StringBuilder();
+            for(ProjectMonitorUserResult engineerUser : engineerList) {
+                engineer.append(String.join("-", engineerUser.getSupplierName(), engineerUser.getUserName(), engineerUser.getMobileNumber()));
+                if(index < engineerList.size() - 1) {
+                    engineer.append(",");
+                }
+                index++;
+            }
+            exportResult.setEngineer(engineer.toString());
+        }
+        return exportResult;
+    }
+
+    private List<ProjectMonitorUserResult> filterUserArchivesAllocation(List<UserArchivesAllocationDto> allocationDtoList, SopRoleTypeEnum roleType) {
+        List<ProjectMonitorUserResult> monitorResultList = new ArrayList<>();
+        List<UserArchivesAllocationDto> filterUserList = allocationDtoList.stream().filter(user -> user.getSopRoleType().equals(roleType)).collect(
+                Collectors.toList());
+        for (UserArchivesAllocationDto dto : filterUserList) {
+            ProjectMonitorUserResult userResult = new ProjectMonitorUserResult();
+            BeanUtils.copyProperties(dto, userResult);
+            monitorResultList.add(userResult);
+        }
+        return monitorResultList;
+    }
+
+    private List<String> getProcessList(CrmProcessEnum process) {
+        List<String> processList = new ArrayList<>();
+        if (process == CrmProcessEnum.FINISH) {
+            return processList;
+        }
+        String[] descArr = process.getDesc().split(",");
+        return Arrays.asList(descArr);
+    }
+
+    private Map<CrmProcessEnum, Integer> getCrmProcessNum(Map<String, List<ServiceUnitProgressResult>> processMap) {
+        Map<CrmProcessEnum, Integer> summaryMap = new HashMap<>();
+        processMap.forEach((k, v) -> {
+            CrmProcessEnum crmProcess = CrmProcessEnum.findCrmProcessByDesc(k);
+            if (crmProcess == null)
+                return;
+            if (!summaryMap.containsKey(crmProcess)) {
+                summaryMap.put(crmProcess, v.size());
+            } else {
+                summaryMap.put(crmProcess, summaryMap.get(crmProcess) + v.size());
+            }
+        });
+        return summaryMap;
+    }
+}

+ 78 - 0
sop-business/src/main/resources/mapper/CrmProgressMonitorMapper.xml

@@ -0,0 +1,78 @@
+<?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.qmth.sop.business.mapper.CrmProgressMonitorMapper">
+
+    <select id="listSop" resultType="com.qmth.sop.business.bean.result.CrmProgressSopResult">
+        SELECT DISTINCT
+        sc.NAME AS customName,
+        tbc.NAME AS crmName,
+        tbcd.course_name AS courseName,
+        IFNULL( art.NAME_, '已完成' ) AS taskName,
+        tbc.lead_id leadId,
+        su3.real_name AS leadName,
+        su3.mobile_number leadMobile,
+        tbsi.sop_no AS sopNo,
+        tbcd.id AS crmDetailId
+        FROM
+        t_b_sop_info tbsi
+        LEFT JOIN t_b_crm tbc ON tbc.crm_no = tbsi.crm_no
+        LEFT JOIN t_b_service tbs ON tbs.id = tbsi.service_id
+        LEFT JOIN sys_custom sc ON sc.id = tbsi.custom_id
+        LEFT JOIN sys_user su1 ON su1.id = tbc.crm_user_id
+        LEFT JOIN t_f_custom_flow_entity tfcfe ON tfcfe.CODE = tbsi.sop_no
+        LEFT JOIN t_f_flow_approve tffa ON tffa.flow_id = tfcfe.flow_id
+        LEFT JOIN ACT_RU_TASK art ON art.PROC_INST_ID_ = tffa.flow_id
+        LEFT JOIN t_f_custom_flow tfcf ON tfcf.id = tfcfe.t_f_custom_flow_id
+        LEFT JOIN t_b_crm_detail tbcd ON tbcd.sop_no = tbsi.sop_no
+        LEFT JOIN sys_user su3 ON su3.id = tbc.lead_id
+
+        <where>
+            <if test="serviceUnitId != null">
+                AND tbsi.service_id = #{serviceUnitId}
+            </if>
+            and IF(tbs.`status` = 'PUBLISH', tffa.status in ('START', 'DRAFT', 'AUDITING', 'REJECT', 'CANCEL', 'FINISH'), 1 <![CDATA[ <> ]]> 1)
+        </where>
+    </select>
+
+
+    <select id="listSopByRoleType" resultType="com.qmth.sop.business.bean.result.CrmProgressSopResult">
+        SELECT DISTINCT sc.NAME AS customName,
+        tbc.NAME AS crmName,
+        tbcd.course_name AS courseName,
+        IFNULL(art.NAME_, '已完成') AS taskName,
+        tbc.lead_id leadId,
+        su3.real_name AS leadName,
+        su3.mobile_number leadMobile,
+        tbsi.sop_no AS sopNo,
+        tbcd.id AS crmDetailId,
+        ss.name supplierName,
+        ss.id supplierId,
+        tbusaa.user_id userId,
+        syu.real_name realName
+        FROM t_b_sop_info tbsi
+        LEFT JOIN t_b_crm tbc ON tbc.crm_no = tbsi.crm_no
+        LEFT JOIN t_b_service tbs ON tbs.id = tbsi.service_id
+        LEFT JOIN sys_custom sc ON sc.id = tbsi.custom_id
+        LEFT JOIN sys_user su1 ON su1.id = tbc.crm_user_id
+        LEFT JOIN t_f_custom_flow_entity tfcfe ON tfcfe.CODE = tbsi.sop_no
+        LEFT JOIN t_f_flow_approve tffa ON tffa.flow_id = tfcfe.flow_id
+        LEFT JOIN ACT_RU_TASK art ON art.PROC_INST_ID_ = tffa.flow_id
+        LEFT JOIN t_f_custom_flow tfcf ON tfcf.id = tfcfe.t_f_custom_flow_id
+        LEFT JOIN t_b_crm_detail tbcd ON tbcd.sop_no = tbsi.sop_no
+        LEFT JOIN sys_user su3 ON su3.id = tbc.lead_id
+        LEFT JOIN t_b_user_archives_allocation tbusaa on tbusaa.crm_detail_id = tbcd.id
+        LEFT JOIN t_b_user_archives_supplier tbusas on tbusas.user_archives_id = tbusaa.archives_id
+        LEFT JOIN sys_supplier ss on ss.id = tbusas.supplier_id
+        LEFT JOIN sys_user syu on syu.id = tbusaa.user_id
+
+        <where>
+            <if test="serviceUnitId != null">
+                AND tbsi.service_id = #{serviceUnitId}
+            </if>
+            <if test="sopRoleType != null">
+                AND tbusaa.sop_role_type = #{sopRoleType}
+            </if>
+            and IF(tbs.`status` = 'PUBLISH', tffa.status in ('START', 'DRAFT', 'AUDITING', 'REJECT', 'CANCEL', 'FINISH'), 1 <![CDATA[ <> ]]> 1)
+        </where>
+    </select>
+</mapper>

+ 341 - 0
sop-business/src/main/resources/mapper/ServiceUnitAnalyseMapper.xml

@@ -0,0 +1,341 @@
+<?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.qmth.sop.business.mapper.ServiceUnitAnalyseMapper">
+
+    <select id="countLead" resultType="java.lang.Integer" parameterType="java.lang.Long">
+        select count(distinct lead_id)
+        from t_b_crm
+        where service_id = #{serviceUnitId}
+    </select>
+
+    <select id="countTotalPerson" resultType="java.lang.Integer" parameterType="java.lang.Long">
+        select count(distinct user_id)
+        from t_b_user_archives_allocation
+        where service_id = #{serviceUnitId}
+    </select>
+
+    <select id="countScanner" resultType="java.lang.Integer">
+        SELECT
+        count( 1 )
+        FROM
+        t_b_device_delivery tdd,
+        t_b_crm tbc
+        WHERE
+        tdd.crm_no = tbc.crm_no
+        AND tbc.service_id = #{serviceUnitId}
+        AND tdd.delivery_type = #{out}
+        <if test="deliveryStatusList != null and deliveryStatusList.size() > 0">
+            AND tdd.STATUS IN
+            <foreach collection="deliveryStatusList" item="item" open="(" close=")" index="index" separator=",">
+                #{item}
+            </foreach>
+        </if>
+    </select>
+
+    <select id="listSopProgress" resultType="com.qmth.sop.business.bean.result.ServiceUnitProgressResult" parameterType="java.lang.Long">
+        SELECT DISTINCT IFNULL(art.NAME_, '已完成') AS taskName,
+                        tbsi.sop_no                 AS sopNo,
+                        sc.province                 AS provinceName
+        FROM t_b_sop_info tbsi
+                 INNER JOIN t_b_crm tbc ON tbc.crm_no = tbsi.crm_no
+                 INNER JOIN t_b_service tbs ON tbs.id = tbsi.service_id
+                 INNER JOIN t_f_custom_flow_entity tfcfe ON tfcfe.CODE = tbsi.sop_no
+                 INNER JOIN t_f_flow_approve tffa ON tffa.flow_id = tfcfe.flow_id
+                 LEFT JOIN ACT_RU_TASK art ON art.PROC_INST_ID_ = tffa.flow_id
+                 INNER JOIN t_f_custom_flow tfcf ON tfcf.id = tfcfe.t_f_custom_flow_id
+                 INNER JOIN t_b_crm_detail tbcd ON tbcd.sop_no = tbsi.sop_no
+                 INNER JOIN sys_custom sc on sc.id = tbc.custom_id
+        WHERE tbsi.service_id = #{serviceUnitId}
+    </select>
+
+    <select id="listProvinceCrmNum" resultType="com.qmth.sop.business.bean.result.ServiceUnitProvinceResult" parameterType="java.lang.Long">
+        SELECT sc.province,
+               count(1) AS provinceCrmNum
+        FROM t_b_crm tbc
+                 INNER JOIN sys_custom sc ON tbc.custom_id = sc.id
+        WHERE tbc.service_id = #{serviceUnitId}
+        GROUP BY sc.province
+    </select>
+
+    <select id="pageSop" resultType="com.qmth.sop.business.bean.result.ProjectMonitorResult">
+        SELECT DISTINCT
+        sc.NAME AS customName,
+        tbc.NAME AS crmName,
+        tbcd.course_name AS courseName,
+        IFNULL( art.NAME_, '已完成' ) AS taskName,
+        tbc.lead_id AS leadId,
+        su3.real_name AS leadName,
+        su3.mobile_number leadMobile,
+        tbsi.sop_no AS sopNo,
+        tbcd.id AS crmDetailId,
+        IF ( sc.type = 'OFFICE', '教务处', '研究生' ) AS customManagerTypeStr,
+        cast( tfcfe.flow_id AS CHAR ) AS flowId,
+        tbsi.update_time AS updateTime
+        FROM
+        t_b_sop_info tbsi
+        LEFT JOIN t_b_crm tbc ON tbc.crm_no = tbsi.crm_no
+        LEFT JOIN t_b_service tbs ON tbs.id = tbsi.service_id
+        LEFT JOIN sys_custom sc ON sc.id = tbsi.custom_id
+        LEFT JOIN sys_user su1 ON su1.id = tbc.crm_user_id
+        LEFT JOIN t_f_custom_flow_entity tfcfe ON tfcfe.CODE = tbsi.sop_no
+        LEFT JOIN t_f_flow_approve tffa ON tffa.flow_id = tfcfe.flow_id
+        LEFT JOIN ACT_RU_TASK art ON art.PROC_INST_ID_ = tffa.flow_id
+        LEFT JOIN t_f_custom_flow tfcf ON tfcf.id = tfcfe.t_f_custom_flow_id
+        LEFT JOIN t_b_crm_detail tbcd ON tbcd.sop_no = tbsi.sop_no
+        LEFT JOIN sys_user su3 ON su3.id = tbc.lead_id
+        <where>
+            <if test="serviceUnitId != null">
+                AND tbsi.service_id=#{serviceUnitId}
+            </if>
+            <if test="crmUserId != null">
+                AND tbc.crm_user_id=#{crmUserId}
+            </if>
+            <if test="customName != null and customName != ''">
+                AND sc.name=#{customName}
+            </if>
+            <if test="leadId != null">
+                AND tbc.lead_id=#{leadId}
+            </if>
+            <if test="province != null and province != '' ">
+                AND sc.province=#{province}
+            </if>
+
+            <if test="process != null">
+                <choose>
+                    <!--非已完成状态-->
+                    <when test="prList != null and prList.size > 0">
+                        AND art.NAME_ in
+                        <foreach collection="prList" item="item" index="index" open="(" separator="," close=")">
+                            #{item}
+                        </foreach>
+                    </when>
+                    <!-- 已完成状态-->
+                    <otherwise>
+                        AND art.NAME_ is null
+                    </otherwise>
+                </choose>
+            </if>
+            and IF(tbs.`status` = 'PUBLISH', tffa.status in ('START', 'DRAFT', 'AUDITING', 'REJECT', 'CANCEL', 'FINISH'), 1 <![CDATA[ <> ]]> 1)
+        </where>
+        ORDER BY tbsi.update_time DESC,tbsi.sop_no DESC
+    </select>
+
+    <select id="pageSopByUserAllocation" resultType="com.qmth.sop.business.bean.result.ProjectMonitorResult">
+        SELECT DISTINCT
+        sc.NAME AS customName,
+        tbc.NAME AS crmName,
+        tbcd.course_name AS courseName,
+        IFNULL( art.NAME_, '已完成' ) AS taskName,
+        tbc.lead_id AS leadId,
+        su3.real_name AS leadName,
+        su3.mobile_number leadMobile,
+        tbsi.sop_no AS sopNo,
+        tbcd.id AS crmDetailId,
+        IF ( sc.type = 'OFFICE', '教务处', '研究生' ) AS customManagerTypeStr,
+        cast( tfcfe.flow_id AS CHAR ) AS flowId,
+        tbsi.update_time AS updateTime
+        FROM
+        t_b_sop_info tbsi
+        LEFT JOIN t_b_crm tbc ON tbc.crm_no = tbsi.crm_no
+        LEFT JOIN t_b_service tbs ON tbs.id = tbsi.service_id
+        LEFT JOIN sys_custom sc ON sc.id = tbsi.custom_id
+        LEFT JOIN sys_user su1 ON su1.id = tbc.crm_user_id
+        LEFT JOIN t_f_custom_flow_entity tfcfe ON tfcfe.CODE = tbsi.sop_no
+        LEFT JOIN t_f_flow_approve tffa ON tffa.flow_id = tfcfe.flow_id
+        LEFT JOIN ACT_RU_TASK art ON art.PROC_INST_ID_ = tffa.flow_id
+        LEFT JOIN t_f_custom_flow tfcf ON tfcf.id = tfcfe.t_f_custom_flow_id
+        LEFT JOIN t_b_crm_detail tbcd ON tbcd.sop_no = tbsi.sop_no
+        LEFT JOIN sys_user su3 ON su3.id = tbc.lead_id
+        LEFT JOIN t_b_user_archives_allocation tbusaa ON tbusaa.crm_detail_id = tbcd.id
+        LEFT JOIN t_b_user_archives_supplier tbusas ON tbusas.user_archives_id = tbusaa.archives_id
+        LEFT JOIN sys_supplier ss ON ss.id = tbusas.supplier_id
+        LEFT JOIN sys_user syu ON syu.id = tbusaa.user_id
+        <where>
+            <if test="serviceUnitId != null">
+                AND tbsi.service_id=#{serviceUnitId}
+            </if>
+            <if test="crmUserId != null">
+                AND tbc.crm_user_id=#{crmUserId}
+            </if>
+            <if test="customName != null and customName != ''">
+                AND sc.name=#{customName}
+            </if>
+            <if test="leadId != null">
+                AND tbc.lead_id=#{leadId}
+            </if>
+            <if test="province != null and province != '' ">
+                AND sc.province=#{province}
+            </if>
+            <if test="supplierId != null">
+                AND ss.id = #{supplierId}
+            </if>
+            <if test="coordinatorId != null">
+                AND tbusaa.user_id = #{coordinatorId}
+            </if>
+            <if test="roleType != null">
+                AND tbusaa.sop_role_type = #{roleType}
+            </if>
+
+            <if test="process != null">
+                <choose>
+                    <!--非已完成状态-->
+                    <when test="prList != null and prList.size > 0">
+                        AND art.NAME_ in
+                        <foreach collection="prList" item="item" index="index" open="(" separator="," close=")">
+                            #{item}
+                        </foreach>
+                    </when>
+                    <!-- 已完成状态-->
+                    <otherwise>
+                        AND art.NAME_ is null
+                    </otherwise>
+                </choose>
+            </if>
+            and IF(tbs.`status` = 'PUBLISH', tffa.status in ('START', 'DRAFT', 'AUDITING', 'REJECT', 'CANCEL', 'FINISH'), 1 <![CDATA[ <> ]]> 1)
+        </where>
+        ORDER BY tbsi.update_time DESC,tbsi.sop_no DESC
+    </select>
+
+
+
+    <select id="listSop" resultType="com.qmth.sop.business.bean.result.ProjectMonitorResult">
+        SELECT DISTINCT
+        sc.NAME AS customName,
+        tbc.NAME AS crmName,
+        tbcd.course_name AS courseName,
+        IFNULL( art.NAME_, '已完成' ) AS taskName,
+        tbc.lead_id leadId,
+        su3.real_name AS leadName,
+        su3.mobile_number leadMobile,
+        tbsi.sop_no AS sopNo,
+        tbcd.id AS crmDetailId,
+        IF ( sc.type = 'OFFICE', '教务处', '研究生' ) AS customManagerTypeStr,
+        cast( tfcfe.flow_id AS CHAR ) AS flowId,
+        tbsi.update_time AS updateTime
+        FROM
+        t_b_sop_info tbsi
+        LEFT JOIN t_b_crm tbc ON tbc.crm_no = tbsi.crm_no
+        LEFT JOIN t_b_service tbs ON tbs.id = tbsi.service_id
+        LEFT JOIN sys_custom sc ON sc.id = tbsi.custom_id
+        LEFT JOIN sys_user su1 ON su1.id = tbc.crm_user_id
+        LEFT JOIN t_f_custom_flow_entity tfcfe ON tfcfe.CODE = tbsi.sop_no
+        LEFT JOIN t_f_flow_approve tffa ON tffa.flow_id = tfcfe.flow_id
+        LEFT JOIN ACT_RU_TASK art ON art.PROC_INST_ID_ = tffa.flow_id
+        LEFT JOIN t_f_custom_flow tfcf ON tfcf.id = tfcfe.t_f_custom_flow_id
+        LEFT JOIN t_b_crm_detail tbcd ON tbcd.sop_no = tbsi.sop_no
+        LEFT JOIN sys_user su3 ON su3.id = tbc.lead_id
+        <where>
+            <if test="serviceUnitId != null">
+                AND tbsi.service_id=#{serviceUnitId}
+            </if>
+            <if test="crmUserId != null">
+                AND tbc.crm_user_id=#{crmUserId}
+            </if>
+            <if test="customName != null and customName != ''">
+                AND sc.name=#{customName}
+            </if>
+            <if test="leadId != null">
+                AND tbc.lead_id=#{leadId}
+            </if>
+            <if test="province != null and province != '' ">
+                AND sc.province=#{province}
+            </if>
+
+            <if test="process != null">
+                <choose>
+                    <!--非已完成状态-->
+                    <when test="prList != null and prList.size > 0">
+                        AND art.NAME_ in
+                        <foreach collection="prList" item="item" index="index" open="(" separator="," close=")">
+                            #{item}
+                        </foreach>
+                    </when>
+                    <!-- 已完成状态-->
+                    <otherwise>
+                        AND art.NAME_ is null
+                    </otherwise>
+                </choose>
+            </if>
+            and IF(tbs.`status` = 'PUBLISH', tffa.status in ('START', 'DRAFT', 'AUDITING', 'REJECT', 'CANCEL', 'FINISH'), 1 <![CDATA[ <> ]]> 1)
+        </where>
+        ORDER BY tbsi.update_time DESC,tbsi.sop_no DESC
+    </select>
+
+    <select id="listSopByUserAllocation" resultType="com.qmth.sop.business.bean.result.ProjectMonitorResult">
+        SELECT DISTINCT
+        sc.NAME AS customName,
+        tbc.NAME AS crmName,
+        tbcd.course_name AS courseName,
+        IFNULL( art.NAME_, '已完成' ) AS taskName,
+        tbc.lead_id AS leadId,
+        su3.real_name AS leadName,
+        su3.mobile_number leadMobile,
+        tbsi.sop_no AS sopNo,
+        tbcd.id AS crmDetailId,
+        IF ( sc.type = 'OFFICE', '教务处', '研究生' ) AS customManagerTypeStr,
+        cast( tfcfe.flow_id AS CHAR ) AS flowId,
+        tbsi.update_time AS updateTime
+        FROM
+        t_b_sop_info tbsi
+        LEFT JOIN t_b_crm tbc ON tbc.crm_no = tbsi.crm_no
+        LEFT JOIN t_b_service tbs ON tbs.id = tbsi.service_id
+        LEFT JOIN sys_custom sc ON sc.id = tbsi.custom_id
+        LEFT JOIN sys_user su1 ON su1.id = tbc.crm_user_id
+        LEFT JOIN t_f_custom_flow_entity tfcfe ON tfcfe.CODE = tbsi.sop_no
+        LEFT JOIN t_f_flow_approve tffa ON tffa.flow_id = tfcfe.flow_id
+        LEFT JOIN ACT_RU_TASK art ON art.PROC_INST_ID_ = tffa.flow_id
+        LEFT JOIN t_f_custom_flow tfcf ON tfcf.id = tfcfe.t_f_custom_flow_id
+        LEFT JOIN t_b_crm_detail tbcd ON tbcd.sop_no = tbsi.sop_no
+        LEFT JOIN sys_user su3 ON su3.id = tbc.lead_id
+        LEFT JOIN t_b_user_archives_allocation tbusaa ON tbusaa.crm_detail_id = tbcd.id
+        LEFT JOIN t_b_user_archives_supplier tbusas ON tbusas.user_archives_id = tbusaa.archives_id
+        LEFT JOIN sys_supplier ss ON ss.id = tbusas.supplier_id
+        LEFT JOIN sys_user syu ON syu.id = tbusaa.user_id
+        <where>
+            <if test="serviceUnitId != null">
+                AND tbsi.service_id=#{serviceUnitId}
+            </if>
+            <if test="crmUserId != null">
+                AND tbc.crm_user_id=#{crmUserId}
+            </if>
+            <if test="customName != null and customName != ''">
+                AND sc.name=#{customName}
+            </if>
+            <if test="leadId != null">
+                AND tbc.lead_id=#{leadId}
+            </if>
+            <if test="province != null and province != '' ">
+                AND sc.province=#{province}
+            </if>
+            <if test="supplierId != null">
+                AND ss.id = #{supplierId}
+            </if>
+            <if test="coordinatorId != null">
+                AND tbusaa.user_id = #{coordinatorId}
+            </if>
+            <if test="roleType != null">
+                AND tbusaa.sop_role_type = #{roleType}
+            </if>
+
+            <if test="process != null">
+                <choose>
+                    <!--非已完成状态-->
+                    <when test="prList != null and prList.size > 0">
+                        AND art.NAME_ in
+                        <foreach collection="prList" item="item" index="index" open="(" separator="," close=")">
+                            #{item}
+                        </foreach>
+                    </when>
+                    <!-- 已完成状态-->
+                    <otherwise>
+                        AND art.NAME_ is null
+                    </otherwise>
+                </choose>
+            </if>
+            and IF(tbs.`status` = 'PUBLISH', tffa.status in ('START', 'DRAFT', 'AUDITING', 'REJECT', 'CANCEL', 'FINISH'), 1 <![CDATA[ <> ]]> 1)
+        </where>
+        ORDER BY tbsi.update_time DESC,tbsi.sop_no DESC
+    </select>
+
+
+</mapper>

+ 2 - 2
sop-common/src/main/java/com/qmth/sop/common/contant/SystemConstant.java

@@ -441,9 +441,9 @@ public class SystemConstant {
 
     public static final String PREFIX_URL_PROJECT_MONITOR = "/admin/project/monitor";
 
-//    public static final String PREFIX_URL_SERVICE_ANALYSE = "/admin/service/analyse";
+    public static final String PREFIX_URL_SERVICE_ANALYSE = "/admin/service/analyse";
 
-//    public static final String PREFIX_URL_CRM_PROCESS_MONITOR = "/admin/crm/process/monitor";
+    public static final String PREFIX_URL_CRM_PROCESS_MONITOR = "/admin/crm/process/monitor";
 
     /**
      * 缓存配置

+ 25 - 0
sop-common/src/main/java/com/qmth/sop/common/enums/CrmProgressMonitorEnum.java

@@ -0,0 +1,25 @@
+package com.qmth.sop.common.enums;
+
+/**
+ * @Description 项目进度监控统计
+ * @Author haoguanghui
+ * @date 2024/09/12
+ */
+public enum CrmProgressMonitorEnum {
+
+    BY_LEAD("按大区经理"),
+
+    BY_COORDINATOR("按区域协调人"),
+
+    BY_SUPPLIER("按项目经理所属供应商");
+
+    private String title;
+
+    CrmProgressMonitorEnum(String title) {
+        this.title = title;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+}