Commit 7f53d60ed30251cd44cff383529c13858d374a7f

Authored by YunaiV
1 parent 971c5a65

fix: 修复首批关系型数据库通用 SQL 兼容问题

Showing 38 changed files with 657 additions and 203 deletions
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/jdbc/JdbcExceptionUtils.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.common.util.jdbc;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +
  5 +import java.sql.SQLException;
  6 +import java.util.*;
  7 +
  8 +/**
  9 + * JDBC 异常工具类
  10 + *
  11 + * @author 芋道源码
  12 + */
  13 +public class JdbcExceptionUtils {
  14 +
  15 + /**
  16 + * 表示“表不存在”的 SQLState
  17 + *
  18 + * 1. 42S02:ANSI / ODBC 标准,MySQL(1146)、MariaDB、H2、SQL Server(jTDS)等使用
  19 + * 2. S0002:X/Open 旧标准,SQL Server(208,mssql-jdbc)使用
  20 + * 3. 42P01:PostgreSQL 的 undefined_table,openGauss、KingbaseES 等 PostgreSQL 兼容数据库同样使用
  21 + */
  22 + private static final Set<String> TABLE_NOT_EXISTS_SQL_STATES = new HashSet<>(Arrays.asList("42S02", "S0002", "42P01"));
  23 +
  24 + /**
  25 + * Oracle 的“表或视图不存在”错误码:ORA-00942,对应 SQLState 为通用的 42000,需要同时匹配错误码
  26 + */
  27 + private static final int ORACLE_TABLE_NOT_EXISTS_ERROR_CODE = 942;
  28 + private static final String ORACLE_TABLE_NOT_EXISTS_SQL_STATE = "42000";
  29 +
  30 + /**
  31 + * 兜底的“表不存在”异常文案,兼容未携带 SQLState 的场景(MySQL 的 Table 'xxx' doesn't exist)
  32 + */
  33 + private static final String TABLE_NOT_EXISTS_MESSAGE = "doesn't exist";
  34 +
  35 + /**
  36 + * 判断异常是否为“表不存在”
  37 + *
  38 + * 沿 cause 链、{@link SQLException#getNextException()} 链查找,优先识别 SQLState、错误码。
  39 + * 仅在整条链都没有有效 SQLState 时兜底匹配异常文案,避免外层包装信息覆盖明确的数据库错误。
  40 + * 列不存在、权限不足、语法错误等其它异常,都返回 false,由调用方原样抛出。
  41 + *
  42 + * @param throwable 异常
  43 + * @return 是否表不存在
  44 + */
  45 + @SuppressWarnings("PatternVariableCanBeUsed") // 保持 JDK8 兼容性
  46 + public static boolean isTableNotExists(Throwable throwable) {
  47 + Set<Throwable> visited = Collections.newSetFromMap(new IdentityHashMap<>());
  48 + Deque<Throwable> queue = new ArrayDeque<>();
  49 + if (throwable != null) {
  50 + queue.add(throwable);
  51 + }
  52 + boolean hasSqlState = false;
  53 + boolean hasLegacyMessage = false;
  54 + while (!queue.isEmpty()) {
  55 + Throwable current = queue.poll();
  56 + if (!visited.add(current)) { // 避免循环引用
  57 + continue;
  58 + }
  59 + if (current instanceof SQLException) {
  60 + SQLException sqlException = (SQLException) current;
  61 + if (isTableNotExists(sqlException)) {
  62 + return true;
  63 + }
  64 + hasSqlState |= StrUtil.isNotBlank(sqlException.getSQLState());
  65 + addIfNotNull(queue, sqlException.getNextException());
  66 + }
  67 + hasLegacyMessage |= StrUtil.contains(current.getMessage(), TABLE_NOT_EXISTS_MESSAGE);
  68 + addIfNotNull(queue, current.getCause());
  69 + }
  70 + return !hasSqlState && hasLegacyMessage;
  71 + }
  72 +
  73 + private static boolean isTableNotExists(SQLException sqlException) {
  74 + String sqlState = sqlException.getSQLState();
  75 + if (TABLE_NOT_EXISTS_SQL_STATES.contains(sqlState)) {
  76 + return true;
  77 + }
  78 + return sqlException.getErrorCode() == ORACLE_TABLE_NOT_EXISTS_ERROR_CODE
  79 + && ORACLE_TABLE_NOT_EXISTS_SQL_STATE.equals(sqlState);
  80 + }
  81 +
  82 + private static void addIfNotNull(Deque<Throwable> queue, Throwable throwable) {
  83 + if (throwable != null) {
  84 + queue.add(throwable);
  85 + }
  86 + }
  87 +
  88 +}
yudao-framework/yudao-common/src/test/java/cn/iocoder/yudao/framework/common/util/jdbc/JdbcExceptionUtilsTest.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.common.util.jdbc;
  2 +
  3 +import org.junit.jupiter.api.Test;
  4 +
  5 +import java.sql.SQLException;
  6 +
  7 +import static org.junit.jupiter.api.Assertions.assertFalse;
  8 +import static org.junit.jupiter.api.Assertions.assertTrue;
  9 +
  10 +/**
  11 + * {@link JdbcExceptionUtils} 的单元测试
  12 + *
  13 + * @author 芋道源码
  14 + */
  15 +public class JdbcExceptionUtilsTest {
  16 +
  17 + // ========== 表不存在:SQLState、错误码 ==========
  18 +
  19 + @Test
  20 + public void testIsTableNotExists_mysql() {
  21 + assertTrue(JdbcExceptionUtils.isTableNotExists(
  22 + new SQLException("Table 'ruoyi-vue-pro.bpm_category' doesn't exist", "42S02", 1146)));
  23 + // SQLState 识别,不依赖文案
  24 + assertTrue(JdbcExceptionUtils.isTableNotExists(new SQLException("unknown", "42S02", 1146)));
  25 + }
  26 +
  27 + @Test
  28 + public void testIsTableNotExists_postgresql() {
  29 + assertTrue(JdbcExceptionUtils.isTableNotExists(
  30 + new SQLException("ERROR: relation \"bpm_category\" does not exist", "42P01")));
  31 + }
  32 +
  33 + @Test
  34 + public void testIsTableNotExists_oracle() {
  35 + assertTrue(JdbcExceptionUtils.isTableNotExists(
  36 + new SQLException("ORA-00942: table or view does not exist\n", "42000", 942)));
  37 + }
  38 +
  39 + @Test
  40 + public void testIsTableNotExists_sqlServer() {
  41 + // mssql-jdbc
  42 + assertTrue(JdbcExceptionUtils.isTableNotExists(
  43 + new SQLException("Invalid object name 'bpm_category'.", "S0002", 208)));
  44 + // jTDS
  45 + assertTrue(JdbcExceptionUtils.isTableNotExists(
  46 + new SQLException("Invalid object name 'bpm_category'.", "42S02", 208)));
  47 + }
  48 +
  49 + @Test
  50 + public void testIsTableNotExists_messageFallback() {
  51 + // 未携带 SQLState 时,兜底匹配 MySQL 文案
  52 + assertTrue(JdbcExceptionUtils.isTableNotExists(
  53 + new SQLException("Table 'ruoyi-vue-pro.mp_account' doesn't exist")));
  54 + assertTrue(JdbcExceptionUtils.isTableNotExists(
  55 + new RuntimeException("### Cause: Table 'ruoyi-vue-pro.mp_account' doesn't exist")));
  56 + }
  57 +
  58 + // ========== 表不存在:异常链 ==========
  59 +
  60 + @Test
  61 + public void testIsTableNotExists_causeChain() {
  62 + SQLException sqlException = new SQLException("ERROR: relation \"mp_account\" does not exist", "42P01");
  63 + // 外层包装的文案不包含“表不存在”,需要沿 cause 链查找
  64 + assertTrue(JdbcExceptionUtils.isTableNotExists(
  65 + new RuntimeException("nested exception", new IllegalStateException(sqlException))));
  66 + }
  67 +
  68 + @Test
  69 + public void testIsTableNotExists_nextExceptionChain() {
  70 + SQLException batchException = new SQLException("Batch entry 0 was aborted", "25P02");
  71 + batchException.setNextException(new SQLException("ERROR: relation \"mp_account\" does not exist", "42P01"));
  72 + assertTrue(JdbcExceptionUtils.isTableNotExists(new RuntimeException(batchException)));
  73 + }
  74 +
  75 + // ========== 非表不存在:原样返回 false ==========
  76 +
  77 + @Test
  78 + public void testIsTableNotExists_columnNotExists() {
  79 + // MySQL
  80 + assertFalse(JdbcExceptionUtils.isTableNotExists(
  81 + new SQLException("Unknown column 'xxx' in 'field list'", "42S22", 1054)));
  82 + // PostgreSQL
  83 + assertFalse(JdbcExceptionUtils.isTableNotExists(
  84 + new SQLException("ERROR: column \"xxx\" does not exist", "42703")));
  85 + // Oracle
  86 + assertFalse(JdbcExceptionUtils.isTableNotExists(
  87 + new SQLException("ORA-00904: \"XXX\": invalid identifier\n", "42000", 904)));
  88 + // SQL Server
  89 + assertFalse(JdbcExceptionUtils.isTableNotExists(
  90 + new SQLException("Invalid column name 'xxx'.", "S0001", 207)));
  91 + }
  92 +
  93 + @Test
  94 + public void testIsTableNotExists_permissionDenied() {
  95 + // MySQL
  96 + assertFalse(JdbcExceptionUtils.isTableNotExists(
  97 + new SQLException("SELECT command denied to user 'u'@'localhost' for table 'bpm_category'", "42000", 1142)));
  98 + // PostgreSQL
  99 + assertFalse(JdbcExceptionUtils.isTableNotExists(
  100 + new SQLException("ERROR: permission denied for table bpm_category", "42501")));
  101 + // Oracle
  102 + assertFalse(JdbcExceptionUtils.isTableNotExists(
  103 + new SQLException("ORA-01031: insufficient privileges\n", "42000", 1031)));
  104 + }
  105 +
  106 + @Test
  107 + public void testIsTableNotExists_sqlStateTakesPrecedenceOverLegacyMessage() {
  108 + // 用户名或外层包装信息可能包含旧文案,明确的权限/列错误仍应保留。
  109 + assertFalse(JdbcExceptionUtils.isTableNotExists(new SQLException(
  110 + "SELECT command denied to user 'doesn't exist'@'localhost' for table 'erp_product'",
  111 + "42000", 1142)));
  112 + assertFalse(JdbcExceptionUtils.isTableNotExists(new RuntimeException(
  113 + "Previous lookup: Table 'erp_product' doesn't exist",
  114 + new SQLException("column missing", "42703"))));
  115 + }
  116 +
  117 + @Test
  118 + public void testIsTableNotExists_otherErrors() {
  119 + // 语法错误:与 Oracle ORA-00942 同为 42000,但错误码不同
  120 + assertFalse(JdbcExceptionUtils.isTableNotExists(
  121 + new SQLException("You have an error in your SQL syntax", "42000", 1064)));
  122 + assertFalse(JdbcExceptionUtils.isTableNotExists(
  123 + new SQLException("ERROR: syntax error at or near \"FROM\"", "42601")));
  124 + // 错误码相同,但 SQLState 不是“表不存在”:Oracle ORA-01146、SQL Server 942
  125 + assertFalse(JdbcExceptionUtils.isTableNotExists(
  126 + new SQLException("ORA-01146: cannot start online backup", "72000", 1146)));
  127 + assertFalse(JdbcExceptionUtils.isTableNotExists(
  128 + new SQLException("Database 'xxx' cannot be opened because it is offline.", "S0001", 942)));
  129 + // 唯一键冲突、连接异常
  130 + assertFalse(JdbcExceptionUtils.isTableNotExists(
  131 + new SQLException("Duplicate entry '1' for key 'PRIMARY'", "23000", 1062)));
  132 + assertFalse(JdbcExceptionUtils.isTableNotExists(new SQLException("Connection refused", "08001")));
  133 + // 非 MySQL 文案的“does not exist”,且无 SQLState
  134 + assertFalse(JdbcExceptionUtils.isTableNotExists(new RuntimeException("Table does not exist")));
  135 + }
  136 +
  137 + @Test
  138 + public void testIsTableNotExists_nullAndEmpty() {
  139 + assertFalse(JdbcExceptionUtils.isTableNotExists(null));
  140 + assertFalse(JdbcExceptionUtils.isTableNotExists(new RuntimeException((String) null)));
  141 + assertFalse(JdbcExceptionUtils.isTableNotExists(new SQLException((String) null)));
  142 + assertFalse(JdbcExceptionUtils.isTableNotExists(
  143 + new RuntimeException(null, new SQLException(null, null, 0))));
  144 + }
  145 +
  146 + @Test
  147 + public void testIsTableNotExists_cycle() {
  148 + // cause 链循环
  149 + RuntimeException a = new RuntimeException("a");
  150 + RuntimeException b = new RuntimeException("b", a);
  151 + a.initCause(b);
  152 + assertFalse(JdbcExceptionUtils.isTableNotExists(a));
  153 +
  154 + // nextException 链循环
  155 + SQLException s1 = new SQLException("s1", "HY000", 1);
  156 + SQLException s2 = new SQLException("s2", "HY000", 2);
  157 + s1.setNextException(s2);
  158 + s2.setNextException(s1);
  159 + assertFalse(JdbcExceptionUtils.isTableNotExists(s1));
  160 +
  161 + // 循环链中存在“表不存在”时,仍可识别
  162 + SQLException s3 = new SQLException("s3", "HY000", 3);
  163 + SQLException s4 = new SQLException("ERROR: relation \"mp_account\" does not exist", "42P01");
  164 + s3.setNextException(s4);
  165 + s4.setNextException(s3);
  166 + assertTrue(JdbcExceptionUtils.isTableNotExists(s4));
  167 + assertTrue(JdbcExceptionUtils.isTableNotExists(s3));
  168 + }
  169 +
  170 +}
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java
@@ -11,6 +11,7 @@ import cn.iocoder.yudao.framework.common.exception.ServiceException; @@ -11,6 +11,7 @@ import cn.iocoder.yudao.framework.common.exception.ServiceException;
11 import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil; 11 import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
12 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 12 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
13 import cn.iocoder.yudao.framework.common.util.collection.SetUtils; 13 import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
  14 +import cn.iocoder.yudao.framework.common.util.jdbc.JdbcExceptionUtils;
14 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 15 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
15 import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils; 16 import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
16 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; 17 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
@@ -396,10 +397,10 @@ public class GlobalExceptionHandler { @@ -396,10 +397,10 @@ public class GlobalExceptionHandler {
396 * @return 如果是 Table 不存在的异常,则返回对应的 CommonResult 397 * @return 如果是 Table 不存在的异常,则返回对应的 CommonResult
397 */ 398 */
398 private CommonResult<?> handleTableNotExists(Throwable ex) { 399 private CommonResult<?> handleTableNotExists(Throwable ex) {
399 - String message = ExceptionUtil.getRootCauseMessage(ex);  
400 - if (!message.contains("doesn't exist")) { 400 + if (!JdbcExceptionUtils.isTableNotExists(ex)) {
401 return null; 401 return null;
402 } 402 }
  403 + String message = StrUtil.nullToEmpty(ExceptionUtil.getRootCauseMessage(ex));
403 // 1. 工作流 404 // 1. 工作流
404 if (message.contains("bpm_")) { 405 if (message.contains("bpm_")) {
405 log.error("[工作流模块 yudao-module-bpm - 表结构未导入][参考 https://cloud.iocoder.cn/bpm/ 开启]"); 406 log.error("[工作流模块 yudao-module-bpm - 表结构未导入][参考 https://cloud.iocoder.cn/bpm/ 开启]");
yudao-framework/yudao-spring-boot-starter-web/src/test/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandlerTest.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.web.core.handler;
  2 +
  3 +import cn.iocoder.yudao.framework.common.biz.infra.logger.ApiErrorLogCommonApi;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import jakarta.servlet.http.HttpServletRequest;
  6 +import org.junit.jupiter.api.BeforeEach;
  7 +import org.junit.jupiter.api.Test;
  8 +
  9 +import java.sql.SQLException;
  10 +
  11 +import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
  12 +import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.NOT_IMPLEMENTED;
  13 +import static org.junit.jupiter.api.Assertions.assertEquals;
  14 +import static org.junit.jupiter.api.Assertions.assertTrue;
  15 +import static org.mockito.Mockito.mock;
  16 +
  17 +/**
  18 + * {@link GlobalExceptionHandler} 的单元测试
  19 + *
  20 + * 重点验证“表不存在”的识别:按 SQLState / 错误码识别多种数据库,其它 SQL 异常不会被当作表不存在
  21 + *
  22 + * @author 芋道源码
  23 + */
  24 +public class GlobalExceptionHandlerTest {
  25 +
  26 + private GlobalExceptionHandler globalExceptionHandler;
  27 +
  28 + private HttpServletRequest request;
  29 +
  30 + @BeforeEach
  31 + public void setUp() {
  32 + globalExceptionHandler = new GlobalExceptionHandler("test", mock(ApiErrorLogCommonApi.class));
  33 + request = mock(HttpServletRequest.class);
  34 + }
  35 +
  36 + @Test
  37 + public void testDefaultExceptionHandler_tableNotExists_mysql() {
  38 + // 准备参数
  39 + Exception ex = new RuntimeException("nested exception",
  40 + new SQLException("Table 'ruoyi-vue-pro.erp_product' doesn't exist", "42S02", 1146));
  41 +
  42 + // 调用
  43 + CommonResult<?> result = globalExceptionHandler.defaultExceptionHandler(request, ex);
  44 + // 断言
  45 + assertEquals(NOT_IMPLEMENTED.getCode(), result.getCode());
  46 + assertTrue(result.getMsg().contains("yudao-module-erp"));
  47 + }
  48 +
  49 + @Test
  50 + public void testDefaultExceptionHandler_tableNotExists_postgresql() {
  51 + // 准备参数:PostgreSQL 的文案不包含 doesn't exist,通过 SQLState 42P01 识别
  52 + Exception ex = new RuntimeException("nested exception",
  53 + new SQLException("ERROR: relation \"bpm_category\" does not exist", "42P01"));
  54 +
  55 + // 调用
  56 + CommonResult<?> result = globalExceptionHandler.defaultExceptionHandler(request, ex);
  57 + // 断言
  58 + assertEquals(NOT_IMPLEMENTED.getCode(), result.getCode());
  59 + assertTrue(result.getMsg().contains("yudao-module-bpm"));
  60 + }
  61 +
  62 + @Test
  63 + public void testDefaultExceptionHandler_tableNotExists_noPrefixMatched() {
  64 + // 准备参数:Oracle 的文案不包含表名,无法匹配模块前缀,按系统异常处理
  65 + Exception ex = new RuntimeException("nested exception",
  66 + new SQLException("ORA-00942: table or view does not exist\n", "42000", 942));
  67 +
  68 + // 调用
  69 + CommonResult<?> result = globalExceptionHandler.defaultExceptionHandler(request, ex);
  70 + // 断言
  71 + assertEquals(INTERNAL_SERVER_ERROR.getCode(), result.getCode());
  72 + }
  73 +
  74 + @Test
  75 + public void testDefaultExceptionHandler_columnNotExists() {
  76 + // 准备参数:列不存在,即使文案包含模块前缀,也不是表不存在
  77 + Exception ex = new RuntimeException("nested exception",
  78 + new SQLException("ERROR: column \"xxx\" of relation \"bpm_category\" does not exist", "42703"));
  79 +
  80 + // 调用
  81 + CommonResult<?> result = globalExceptionHandler.defaultExceptionHandler(request, ex);
  82 + // 断言
  83 + assertEquals(INTERNAL_SERVER_ERROR.getCode(), result.getCode());
  84 + }
  85 +
  86 + @Test
  87 + public void testDefaultExceptionHandler_permissionDenied() {
  88 + // 准备参数:权限不足,不是表不存在
  89 + Exception ex = new RuntimeException("nested exception",
  90 + new SQLException("SELECT command denied to user 'u'@'localhost' for table 'erp_product'", "42000", 1142));
  91 +
  92 + // 调用
  93 + CommonResult<?> result = globalExceptionHandler.defaultExceptionHandler(request, ex);
  94 + // 断言
  95 + assertEquals(INTERNAL_SERVER_ERROR.getCode(), result.getCode());
  96 + }
  97 +
  98 + @Test
  99 + public void testDefaultExceptionHandler_permissionDeniedWithLegacyPhrase() {
  100 + Exception ex = new RuntimeException("nested exception", new SQLException(
  101 + "SELECT command denied to user 'doesn't exist'@'localhost' for table 'erp_product'",
  102 + "42000", 1142));
  103 +
  104 + CommonResult<?> result = globalExceptionHandler.defaultExceptionHandler(request, ex);
  105 +
  106 + assertEquals(INTERNAL_SERVER_ERROR.getCode(), result.getCode());
  107 + }
  108 +
  109 + @Test
  110 + public void testDefaultExceptionHandler_nullMessage() {
  111 + // 调用
  112 + CommonResult<?> result = globalExceptionHandler.defaultExceptionHandler(request,
  113 + new RuntimeException((String) null));
  114 + // 断言
  115 + assertEquals(INTERNAL_SERVER_ERROR.getCode(), result.getCode());
  116 + }
  117 +
  118 +}
yudao-module-ai1-springai/src/main/java/cn/iocoder/yudao/module/ai1/dal/mysql/home/Ai1HomeMapper.java
@@ -5,6 +5,8 @@ import cn.iocoder.yudao.module.ai1.controller.admin.home.vo.Ai1HomeMessageSummar @@ -5,6 +5,8 @@ import cn.iocoder.yudao.module.ai1.controller.admin.home.vo.Ai1HomeMessageSummar
5 import cn.iocoder.yudao.module.ai1.controller.admin.home.vo.Ai1HomeSummaryRespVO; 5 import cn.iocoder.yudao.module.ai1.controller.admin.home.vo.Ai1HomeSummaryRespVO;
6 import org.apache.ibatis.annotations.Mapper; 6 import org.apache.ibatis.annotations.Mapper;
7 import org.apache.ibatis.annotations.Param; 7 import org.apache.ibatis.annotations.Param;
  8 +import org.apache.ibatis.annotations.Result;
  9 +import org.apache.ibatis.annotations.Results;
8 import org.apache.ibatis.annotations.Select; 10 import org.apache.ibatis.annotations.Select;
9 11
10 import java.time.LocalDateTime; 12 import java.time.LocalDateTime;
@@ -18,32 +20,36 @@ import java.util.List; @@ -18,32 +20,36 @@ import java.util.List;
18 @Mapper 20 @Mapper
19 public interface Ai1HomeMapper { 21 public interface Ai1HomeMapper {
20 22
21 - @Select("SELECT (SELECT COUNT(1) FROM ai1_agent WHERE deleted = FALSE) AS agentCount, " +  
22 - "(SELECT COUNT(1) FROM ai1_skill WHERE deleted = FALSE) AS skillCount, " +  
23 - "(SELECT COUNT(1) FROM ai1_mcp WHERE deleted = FALSE) AS mcpCount, " +  
24 - "(SELECT COUNT(1) FROM ai1_model WHERE deleted = FALSE) AS modelCount") 23 + @Select("SELECT (SELECT COUNT(1) FROM ai1_agent WHERE deleted = 0) AS agentCount, " +
  24 + "(SELECT COUNT(1) FROM ai1_skill WHERE deleted = 0) AS skillCount, " +
  25 + "(SELECT COUNT(1) FROM ai1_mcp WHERE deleted = 0) AS mcpCount, " +
  26 + "(SELECT COUNT(1) FROM ai1_model WHERE deleted = 0) AS modelCount")
25 Ai1HomeSummaryRespVO selectSummary(); 27 Ai1HomeSummaryRespVO selectSummary();
26 28
27 /** 29 /**
28 * 按天统计消息数量,仅返回有消息的日期;使用 CAST AS DATE 兼容 MySQL 与 H2 30 * 按天统计消息数量,仅返回有消息的日期;使用 CAST AS DATE 兼容 MySQL 与 H2
29 */ 31 */
30 - @Select("SELECT CAST(create_time AS DATE) AS `date`, COUNT(1) AS `count` " + 32 + @Select("SELECT CAST(create_time AS DATE) AS statDate, COUNT(1) AS count " +
31 "FROM ai1_session_message " + 33 "FROM ai1_session_message " +
32 - "WHERE create_time BETWEEN #{beginTime} AND #{endTime} AND deleted = FALSE " + 34 + "WHERE create_time BETWEEN #{beginTime} AND #{endTime} AND deleted = 0 " +
33 "GROUP BY CAST(create_time AS DATE) " + 35 "GROUP BY CAST(create_time AS DATE) " +
34 - "ORDER BY `date`") 36 + "ORDER BY statDate")
  37 + @Results({
  38 + @Result(column = "statDate", property = "date"),
  39 + @Result(column = "count", property = "count")
  40 + })
35 List<Ai1HomeMessageSummaryByDateRespVO> selectMessageSummaryListByCreateTimeBetweenGroupByDate( 41 List<Ai1HomeMessageSummaryByDateRespVO> selectMessageSummaryListByCreateTimeBetweenGroupByDate(
36 @Param("beginTime") LocalDateTime beginTime, @Param("endTime") LocalDateTime endTime); 42 @Param("beginTime") LocalDateTime beginTime, @Param("endTime") LocalDateTime endTime);
37 43
38 /** 44 /**
39 * 按 Agent 统计消息数量,按数量倒序,数量相同按 Agent 编号升序;已删除会话下的消息不计入 45 * 按 Agent 统计消息数量,按数量倒序,数量相同按 Agent 编号升序;已删除会话下的消息不计入
40 */ 46 */
41 - @Select("SELECT c.agent_id AS agentId, COUNT(1) AS `count` " + 47 + @Select("SELECT c.agent_id AS agentId, COUNT(1) AS count " +
42 "FROM ai1_session_message m " + 48 "FROM ai1_session_message m " +
43 - "INNER JOIN ai1_session c ON m.session_id = c.id AND c.deleted = FALSE " +  
44 - "WHERE m.create_time BETWEEN #{beginTime} AND #{endTime} AND m.deleted = FALSE " + 49 + "INNER JOIN ai1_session c ON m.session_id = c.id AND c.deleted = 0 " +
  50 + "WHERE m.create_time BETWEEN #{beginTime} AND #{endTime} AND m.deleted = 0 " +
45 "GROUP BY c.agent_id " + 51 "GROUP BY c.agent_id " +
46 - "ORDER BY `count` DESC, c.agent_id") 52 + "ORDER BY count DESC, c.agent_id")
47 List<Ai1HomeMessageSummaryByAgentRespVO> selectMessageSummaryListByCreateTimeBetweenGroupByAgentId( 53 List<Ai1HomeMessageSummaryByAgentRespVO> selectMessageSummaryListByCreateTimeBetweenGroupByAgentId(
48 @Param("beginTime") LocalDateTime beginTime, @Param("endTime") LocalDateTime endTime); 54 @Param("beginTime") LocalDateTime beginTime, @Param("endTime") LocalDateTime endTime);
49 55
yudao-module-crm/src/main/resources/mapper/permission/CrmOwnerRecordMapper.xml
@@ -16,7 +16,7 @@ @@ -16,7 +16,7 @@
16 </foreach> 16 </foreach>
17 AND post_owner_user_id IS NULL 17 AND post_owner_user_id IS NULL
18 AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime} 18 AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
19 - GROUP BY time 19 + GROUP BY DATE_FORMAT(create_time, '%Y-%m-%d')
20 </select> 20 </select>
21 21
22 <select id="selectPoolCustomerTakeCountByDate" 22 <select id="selectPoolCustomerTakeCountByDate"
@@ -32,7 +32,7 @@ @@ -32,7 +32,7 @@
32 #{userId} 32 #{userId}
33 </foreach> 33 </foreach>
34 AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime} 34 AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
35 - GROUP BY time 35 + GROUP BY DATE_FORMAT(create_time, '%Y-%m-%d')
36 </select> 36 </select>
37 37
38 <select id="selectPoolCustomerPutCountByUser" 38 <select id="selectPoolCustomerPutCountByUser"
yudao-module-crm/src/main/resources/mapper/statistics/CrmStatisticsCustomerMapper.xml
@@ -13,15 +13,15 @@ @@ -13,15 +13,15 @@
13 #{userId} 13 #{userId}
14 </foreach> 14 </foreach>
15 AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime} 15 AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
16 - GROUP BY time 16 + GROUP BY DATE_FORMAT(create_time, '%Y-%m-%d')
17 </select> 17 </select>
18 18
19 <select id="selectCustomerDealCountGroupByDate" 19 <select id="selectCustomerDealCountGroupByDate"
20 resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerSummaryByDateRespVO"> 20 resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerSummaryByDateRespVO">
21 SELECT DATE_FORMAT(customer.create_time, '%Y-%m-%d') AS time, 21 SELECT DATE_FORMAT(customer.create_time, '%Y-%m-%d') AS time,
22 COUNT(DISTINCT customer.id) AS customer_deal_count 22 COUNT(DISTINCT customer.id) AS customer_deal_count
23 - FROM crm_customer AS customer  
24 - LEFT JOIN crm_contract AS contract ON contract.customer_id = customer.id 23 + FROM crm_customer customer
  24 + LEFT JOIN crm_contract contract ON contract.customer_id = customer.id
25 WHERE customer.deleted = 0 25 WHERE customer.deleted = 0
26 AND contract.deleted = 0 26 AND contract.deleted = 0
27 AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status} 27 AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
@@ -30,7 +30,7 @@ @@ -30,7 +30,7 @@
30 #{userId} 30 #{userId}
31 </foreach> 31 </foreach>
32 AND customer.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime} 32 AND customer.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
33 - GROUP BY time 33 + GROUP BY DATE_FORMAT(customer.create_time, '%Y-%m-%d')
34 </select> 34 </select>
35 35
36 <select id="selectCustomerCreateCountGroupByUser" 36 <select id="selectCustomerCreateCountGroupByUser"
@@ -51,8 +51,8 @@ @@ -51,8 +51,8 @@
51 resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerSummaryByUserRespVO"> 51 resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerSummaryByUserRespVO">
52 SELECT customer.owner_user_id, 52 SELECT customer.owner_user_id,
53 COUNT(DISTINCT customer.id) AS customer_deal_count 53 COUNT(DISTINCT customer.id) AS customer_deal_count
54 - FROM crm_customer AS customer  
55 - LEFT JOIN crm_contract AS contract ON contract.customer_id = customer.id 54 + FROM crm_customer customer
  55 + LEFT JOIN crm_contract contract ON contract.customer_id = customer.id
56 WHERE customer.deleted = 0 56 WHERE customer.deleted = 0
57 AND contract.deleted = 0 57 AND contract.deleted = 0
58 AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status} 58 AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
@@ -67,7 +67,7 @@ @@ -67,7 +67,7 @@
67 <select id="selectContractPriceGroupByUser" 67 <select id="selectContractPriceGroupByUser"
68 resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerSummaryByUserRespVO"> 68 resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerSummaryByUserRespVO">
69 SELECT owner_user_id, 69 SELECT owner_user_id,
70 - IFNULL(SUM(total_price), 0) AS contract_price 70 + COALESCE(SUM(total_price), 0) AS contract_price
71 FROM crm_contract 71 FROM crm_contract
72 WHERE deleted = 0 72 WHERE deleted = 0
73 AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status} 73 AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
@@ -82,7 +82,7 @@ @@ -82,7 +82,7 @@
82 <select id="selectReceivablePriceGroupByUser" 82 <select id="selectReceivablePriceGroupByUser"
83 resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerSummaryByUserRespVO"> 83 resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerSummaryByUserRespVO">
84 SELECT owner_user_id, 84 SELECT owner_user_id,
85 - IFNULL(SUM(price), 0) AS receivable_price 85 + COALESCE(SUM(price), 0) AS receivable_price
86 FROM crm_receivable 86 FROM crm_receivable
87 WHERE deleted = 0 87 WHERE deleted = 0
88 AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status} 88 AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
@@ -105,7 +105,7 @@ @@ -105,7 +105,7 @@
105 </foreach> 105 </foreach>
106 AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime} 106 AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
107 AND biz_type = ${@cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum@CRM_CUSTOMER.type} 107 AND biz_type = ${@cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum@CRM_CUSTOMER.type}
108 - GROUP BY time 108 + GROUP BY DATE_FORMAT(create_time, '%Y-%m-%d')
109 </select> 109 </select>
110 110
111 <select id="selectFollowUpCustomerCountGroupByDate" 111 <select id="selectFollowUpCustomerCountGroupByDate"
@@ -119,7 +119,7 @@ @@ -119,7 +119,7 @@
119 </foreach> 119 </foreach>
120 AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime} 120 AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
121 AND biz_type = ${@cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum@CRM_CUSTOMER.type} 121 AND biz_type = ${@cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum@CRM_CUSTOMER.type}
122 - GROUP BY time 122 + GROUP BY DATE_FORMAT(create_time, '%Y-%m-%d')
123 </select> 123 </select>
124 124
125 <select id="selectFollowUpRecordCountGroupByUser" 125 <select id="selectFollowUpRecordCountGroupByUser"
@@ -162,7 +162,7 @@ @@ -162,7 +162,7 @@
162 </foreach> 162 </foreach>
163 AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime} 163 AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
164 AND biz_type = ${@cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum@CRM_CUSTOMER.type} 164 AND biz_type = ${@cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum@CRM_CUSTOMER.type}
165 - GROUP BY follow_up_type 165 + GROUP BY type
166 </select> 166 </select>
167 167
168 <select id="selectContractSummary" 168 <select id="selectContractSummary"
@@ -176,10 +176,10 @@ @@ -176,10 +176,10 @@
176 contract.name AS contract_name, 176 contract.name AS contract_name,
177 contract.total_price, 177 contract.total_price,
178 contract.order_date, 178 contract.order_date,
179 - IFNULL(receivable.price, 0) AS receivable_price  
180 - FROM crm_customer AS customer  
181 - INNER JOIN crm_contract AS contract ON customer.id = contract.customer_id  
182 - LEFT JOIN crm_receivable AS receivable ON contract.id = receivable.contract_id 179 + COALESCE(receivable.price, 0) AS receivable_price
  180 + FROM crm_customer customer
  181 + INNER JOIN crm_contract contract ON customer.id = contract.customer_id
  182 + LEFT JOIN crm_receivable receivable ON contract.id = receivable.contract_id
183 WHERE customer.deleted = 0 AND contract.deleted = 0 AND receivable.deleted = 0 183 WHERE customer.deleted = 0 AND contract.deleted = 0 AND receivable.deleted = 0
184 AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status} 184 AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
185 AND customer.owner_user_id IN 185 AND customer.owner_user_id IN
@@ -193,9 +193,9 @@ @@ -193,9 +193,9 @@
193 <select id="selectCustomerDealCycleGroupByDate" 193 <select id="selectCustomerDealCycleGroupByDate"
194 resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerDealCycleByDateRespVO"> 194 resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerDealCycleByDateRespVO">
195 SELECT DATE_FORMAT(contract.order_date, '%Y-%m-%d') AS time, 195 SELECT DATE_FORMAT(contract.order_date, '%Y-%m-%d') AS time,
196 - IFNULL(TRUNCATE(AVG(TIMESTAMPDIFF(DAY, customer.create_time, contract.order_date)), 1), 0) AS customer_deal_cycle  
197 - FROM crm_customer AS customer  
198 - LEFT JOIN crm_contract AS contract ON contract.customer_id = customer.id 196 + COALESCE(TRUNCATE(AVG(TIMESTAMPDIFF(DAY, customer.create_time, contract.order_date)), 1), 0) AS customer_deal_cycle
  197 + FROM crm_customer customer
  198 + LEFT JOIN crm_contract contract ON contract.customer_id = customer.id
199 WHERE customer.deleted = 0 AND contract.deleted = 0 199 WHERE customer.deleted = 0 AND contract.deleted = 0
200 AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status} 200 AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
201 AND customer.owner_user_id IN 201 AND customer.owner_user_id IN
@@ -203,15 +203,15 @@ @@ -203,15 +203,15 @@
203 #{userId} 203 #{userId}
204 </foreach> 204 </foreach>
205 AND customer.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime} 205 AND customer.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
206 - GROUP BY time 206 + GROUP BY DATE_FORMAT(contract.order_date, '%Y-%m-%d')
207 </select> 207 </select>
208 208
209 <select id="selectCustomerDealCycleGroupByUser" 209 <select id="selectCustomerDealCycleGroupByUser"
210 resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerDealCycleByUserRespVO"> 210 resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerDealCycleByUserRespVO">
211 SELECT customer.owner_user_id, 211 SELECT customer.owner_user_id,
212 - IFNULL(TRUNCATE(AVG(TIMESTAMPDIFF(DAY, customer.create_time, contract.order_date)), 1), 0) AS customer_deal_cycle  
213 - FROM crm_customer AS customer  
214 - LEFT JOIN crm_contract AS contract ON contract.customer_id = customer.id 212 + COALESCE(TRUNCATE(AVG(TIMESTAMPDIFF(DAY, customer.create_time, contract.order_date)), 1), 0) AS customer_deal_cycle
  213 + FROM crm_customer customer
  214 + LEFT JOIN crm_contract contract ON contract.customer_id = customer.id
215 WHERE customer.deleted = 0 AND contract.deleted = 0 215 WHERE customer.deleted = 0 AND contract.deleted = 0
216 AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status} 216 AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
217 AND customer.owner_user_id IN 217 AND customer.owner_user_id IN
@@ -225,10 +225,10 @@ @@ -225,10 +225,10 @@
225 <select id="selectCustomerDealCycleGroupByAreaId" 225 <select id="selectCustomerDealCycleGroupByAreaId"
226 resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerDealCycleByAreaRespVO"> 226 resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerDealCycleByAreaRespVO">
227 SELECT customer.area_id AS area_id, 227 SELECT customer.area_id AS area_id,
228 - IFNULL(TRUNCATE(AVG(TIMESTAMPDIFF(DAY, customer.create_time, contract.order_date)), 1), 0) AS customer_deal_cycle, 228 + COALESCE(TRUNCATE(AVG(TIMESTAMPDIFF(DAY, customer.create_time, contract.order_date)), 1), 0) AS customer_deal_cycle,
229 COUNT(DISTINCT customer.id) AS customer_deal_count 229 COUNT(DISTINCT customer.id) AS customer_deal_count
230 - FROM crm_customer AS customer  
231 - LEFT JOIN crm_contract AS contract ON customer.id = contract.customer_id 230 + FROM crm_customer customer
  231 + LEFT JOIN crm_contract contract ON customer.id = contract.customer_id
232 WHERE customer.deleted = 0 232 WHERE customer.deleted = 0
233 AND contract.deleted = 0 233 AND contract.deleted = 0
234 AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status} 234 AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
@@ -244,12 +244,12 @@ @@ -244,12 +244,12 @@
244 <select id="selectCustomerDealCycleGroupByProductId" 244 <select id="selectCustomerDealCycleGroupByProductId"
245 resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerDealCycleByProductRespVO"> 245 resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerDealCycleByProductRespVO">
246 SELECT product.name AS product_name, 246 SELECT product.name AS product_name,
247 - IFNULL(TRUNCATE(AVG(TIMESTAMPDIFF(DAY, customer.create_time, contract.order_date)), 1), 0) AS customer_deal_cycle, 247 + COALESCE(TRUNCATE(AVG(TIMESTAMPDIFF(DAY, customer.create_time, contract.order_date)), 1), 0) AS customer_deal_cycle,
248 COUNT(DISTINCT customer.id) AS customer_deal_count 248 COUNT(DISTINCT customer.id) AS customer_deal_count
249 - FROM crm_customer AS customer  
250 - LEFT JOIN crm_contract AS contract ON customer.id = contract.customer_id  
251 - LEFT JOIN crm_contract_product AS contract_product ON contract_product.contract_id = contract.id  
252 - LEFT JOIN crm_product AS product ON contract_product.product_id = product.id 249 + FROM crm_customer customer
  250 + LEFT JOIN crm_contract contract ON customer.id = contract.customer_id
  251 + LEFT JOIN crm_contract_product contract_product ON contract_product.contract_id = contract.id
  252 + LEFT JOIN crm_product product ON contract_product.product_id = product.id
253 WHERE customer.deleted = 0 253 WHERE customer.deleted = 0
254 AND contract.deleted = 0 254 AND contract.deleted = 0
255 AND contract_product.deleted = 0 255 AND contract_product.deleted = 0
@@ -260,7 +260,7 @@ @@ -260,7 +260,7 @@
260 #{userId} 260 #{userId}
261 </foreach> 261 </foreach>
262 AND customer.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime} 262 AND customer.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
263 - GROUP BY product.id 263 + GROUP BY product.id, product.name
264 </select> 264 </select>
265 265
266 </mapper> 266 </mapper>
yudao-module-crm/src/main/resources/mapper/statistics/CrmStatisticsFunnelMapper.xml
@@ -41,7 +41,7 @@ @@ -41,7 +41,7 @@
41 SELECT 41 SELECT
42 end_status AS endStatus, 42 end_status AS endStatus,
43 COUNT(*) AS businessCount, 43 COUNT(*) AS businessCount,
44 - IFNULL(SUM(total_price), 0) AS totalPrice 44 + COALESCE(SUM(total_price), 0) AS totalPrice
45 FROM crm_business 45 FROM crm_business
46 WHERE deleted = 0 AND end_status IS NOT NULL 46 WHERE deleted = 0 AND end_status IS NOT NULL
47 <if test="statusTypeId != null"> 47 <if test="statusTypeId != null">
@@ -64,7 +64,7 @@ @@ -64,7 +64,7 @@
64 status.percent AS statusPercent, 64 status.percent AS statusPercent,
65 status.sort AS sort, 65 status.sort AS sort,
66 COUNT(business.id) AS businessCount, 66 COUNT(business.id) AS businessCount,
67 - IFNULL(SUM(business.total_price), 0) AS totalPrice 67 + COALESCE(SUM(business.total_price), 0) AS totalPrice
68 FROM crm_business_status status 68 FROM crm_business_status status
69 LEFT JOIN crm_business business ON business.status_id = status.id 69 LEFT JOIN crm_business business ON business.status_id = status.id
70 AND business.deleted = 0 70 AND business.deleted = 0
@@ -88,7 +88,7 @@ @@ -88,7 +88,7 @@
88 SELECT 88 SELECT
89 DATE_FORMAT(create_time, '%Y-%m-%d') AS time, 89 DATE_FORMAT(create_time, '%Y-%m-%d') AS time,
90 COUNT(*) AS businessCreateCount, 90 COUNT(*) AS businessCreateCount,
91 - IFNULL(SUM(total_price), 0) AS totalPrice 91 + COALESCE(SUM(total_price), 0) AS totalPrice
92 FROM crm_business 92 FROM crm_business
93 WHERE deleted = 0 93 WHERE deleted = 0
94 <if test="statusTypeId != null"> 94 <if test="statusTypeId != null">
@@ -100,7 +100,7 @@ @@ -100,7 +100,7 @@
100 </foreach> 100 </foreach>
101 AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND 101 AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND
102 #{times[1],javaType=java.time.LocalDateTime} 102 #{times[1],javaType=java.time.LocalDateTime}
103 - GROUP BY time 103 + GROUP BY DATE_FORMAT(create_time, '%Y-%m-%d')
104 </select> 104 </select>
105 105
106 <select id="selectBusinessInversionRateSummaryByDate" 106 <select id="selectBusinessInversionRateSummaryByDate"
@@ -108,7 +108,7 @@ @@ -108,7 +108,7 @@
108 SELECT 108 SELECT
109 DATE_FORMAT(create_time, '%Y-%m-%d') AS time, 109 DATE_FORMAT(create_time, '%Y-%m-%d') AS time,
110 COUNT(*) AS businessCount, 110 COUNT(*) AS businessCount,
111 - SUM(IF(end_status = 1, 1, 0)) AS businessWinCount 111 + SUM(CASE WHEN end_status = 1 THEN 1 ELSE 0 END) AS businessWinCount
112 FROM crm_business 112 FROM crm_business
113 WHERE deleted = 0 113 WHERE deleted = 0
114 <if test="statusTypeId != null"> 114 <if test="statusTypeId != null">
@@ -120,7 +120,7 @@ @@ -120,7 +120,7 @@
120 </foreach> 120 </foreach>
121 AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND 121 AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND
122 #{times[1],javaType=java.time.LocalDateTime} 122 #{times[1],javaType=java.time.LocalDateTime}
123 - GROUP BY time 123 + GROUP BY DATE_FORMAT(create_time, '%Y-%m-%d')
124 </select> 124 </select>
125 125
126 </mapper> 126 </mapper>
yudao-module-crm/src/main/resources/mapper/statistics/CrmStatisticsPerformanceMapper.xml
@@ -16,14 +16,14 @@ @@ -16,14 +16,14 @@
16 </foreach> 16 </foreach>
17 AND order_date between #{times[0],javaType=java.time.LocalDateTime} and 17 AND order_date between #{times[0],javaType=java.time.LocalDateTime} and
18 #{times[1],javaType=java.time.LocalDateTime} 18 #{times[1],javaType=java.time.LocalDateTime}
19 - GROUP BY time 19 + GROUP BY DATE_FORMAT(order_date, '%Y%m')
20 </select> 20 </select>
21 21
22 <select id="selectContractPricePerformance" 22 <select id="selectContractPricePerformance"
23 resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO"> 23 resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO">
24 SELECT 24 SELECT
25 DATE_FORMAT(order_date, '%Y%m') AS time, 25 DATE_FORMAT(order_date, '%Y%m') AS time,
26 - IFNULL(SUM(total_price), 0) AS currentMonthCount 26 + COALESCE(SUM(total_price), 0) AS currentMonthCount
27 FROM crm_contract 27 FROM crm_contract
28 WHERE deleted = 0 28 WHERE deleted = 0
29 AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status} 29 AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
@@ -33,14 +33,14 @@ @@ -33,14 +33,14 @@
33 </foreach> 33 </foreach>
34 AND order_date between #{times[0],javaType=java.time.LocalDateTime} and 34 AND order_date between #{times[0],javaType=java.time.LocalDateTime} and
35 #{times[1],javaType=java.time.LocalDateTime} 35 #{times[1],javaType=java.time.LocalDateTime}
36 - GROUP BY time 36 + GROUP BY DATE_FORMAT(order_date, '%Y%m')
37 </select> 37 </select>
38 38
39 <select id="selectReceivablePricePerformance" 39 <select id="selectReceivablePricePerformance"
40 resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO"> 40 resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO">
41 SELECT 41 SELECT
42 DATE_FORMAT(return_time, '%Y%m') AS time, 42 DATE_FORMAT(return_time, '%Y%m') AS time,
43 - IFNULL(SUM(price), 0) AS currentMonthCount 43 + COALESCE(SUM(price), 0) AS currentMonthCount
44 FROM crm_receivable 44 FROM crm_receivable
45 WHERE deleted = 0 45 WHERE deleted = 0
46 AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status} 46 AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
@@ -50,25 +50,25 @@ @@ -50,25 +50,25 @@
50 </foreach> 50 </foreach>
51 AND return_time between #{times[0],javaType=java.time.LocalDateTime} and 51 AND return_time between #{times[0],javaType=java.time.LocalDateTime} and
52 #{times[1],javaType=java.time.LocalDateTime} 52 #{times[1],javaType=java.time.LocalDateTime}
53 - GROUP BY time 53 + GROUP BY DATE_FORMAT(return_time, '%Y%m')
54 </select> 54 </select>
55 55
56 <select id="selectContractSummary" 56 <select id="selectContractSummary"
57 resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceSummaryRespVO"> 57 resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceSummaryRespVO">
58 SELECT DATE_FORMAT(contract.order_date, '%Y%m') AS time, 58 SELECT DATE_FORMAT(contract.order_date, '%Y%m') AS time,
59 COUNT(DISTINCT contract.id) AS contract_count, 59 COUNT(DISTINCT contract.id) AS contract_count,
60 - IFNULL(SUM(contract.total_price), 0) AS contract_price,  
61 - IFNULL(SUM(receivable.receivable_price), 0) AS receivable_price,  
62 - IFNULL(SUM(contract.total_price), 0) - IFNULL(SUM(receivable.receivable_price), 0) AS unreceived_price  
63 - FROM crm_contract AS contract 60 + COALESCE(SUM(contract.total_price), 0) AS contract_price,
  61 + COALESCE(SUM(receivable.receivable_price), 0) AS receivable_price,
  62 + COALESCE(SUM(contract.total_price), 0) - COALESCE(SUM(receivable.receivable_price), 0) AS unreceived_price
  63 + FROM crm_contract contract
64 LEFT JOIN ( 64 LEFT JOIN (
65 SELECT contract_id, 65 SELECT contract_id,
66 - IFNULL(SUM(price), 0) AS receivable_price 66 + COALESCE(SUM(price), 0) AS receivable_price
67 FROM crm_receivable 67 FROM crm_receivable
68 WHERE deleted = 0 68 WHERE deleted = 0
69 AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status} 69 AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
70 GROUP BY contract_id 70 GROUP BY contract_id
71 - ) AS receivable ON contract.id = receivable.contract_id 71 + ) receivable ON contract.id = receivable.contract_id
72 WHERE contract.deleted = 0 72 WHERE contract.deleted = 0
73 AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status} 73 AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
74 AND contract.owner_user_id in 74 AND contract.owner_user_id in
@@ -77,7 +77,7 @@ @@ -77,7 +77,7 @@
77 </foreach> 77 </foreach>
78 AND contract.order_date between #{times[0],javaType=java.time.LocalDateTime} and 78 AND contract.order_date between #{times[0],javaType=java.time.LocalDateTime} and
79 #{times[1],javaType=java.time.LocalDateTime} 79 #{times[1],javaType=java.time.LocalDateTime}
80 - GROUP BY time 80 + GROUP BY DATE_FORMAT(contract.order_date, '%Y%m')
81 </select> 81 </select>
82 82
83 </mapper> 83 </mapper>
yudao-module-crm/src/main/resources/mapper/statistics/CrmStatisticsPerformanceTargetMapper.xml
@@ -11,7 +11,7 @@ @@ -11,7 +11,7 @@
11 <select id="selectContractActualByMonth" 11 <select id="selectContractActualByMonth"
12 resultMap="CrmStatisticsPerformanceTargetActualResultMap"> 12 resultMap="CrmStatisticsPerformanceTargetActualResultMap">
13 SELECT MONTH(order_date) AS month, 13 SELECT MONTH(order_date) AS month,
14 - IFNULL(SUM(total_price), 0) AS current_price 14 + COALESCE(SUM(total_price), 0) AS current_price
15 FROM crm_contract 15 FROM crm_contract
16 WHERE deleted = 0 16 WHERE deleted = 0
17 AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status} 17 AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
@@ -26,7 +26,7 @@ @@ -26,7 +26,7 @@
26 <select id="selectReceivableActualByMonth" 26 <select id="selectReceivableActualByMonth"
27 resultMap="CrmStatisticsPerformanceTargetActualResultMap"> 27 resultMap="CrmStatisticsPerformanceTargetActualResultMap">
28 SELECT MONTH(return_time) AS month, 28 SELECT MONTH(return_time) AS month,
29 - IFNULL(SUM(price), 0) AS current_price 29 + COALESCE(SUM(price), 0) AS current_price
30 FROM crm_receivable 30 FROM crm_receivable
31 WHERE deleted = 0 31 WHERE deleted = 0
32 AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status} 32 AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
yudao-module-crm/src/main/resources/mapper/statistics/CrmStatisticsProductMapper.xml
@@ -26,7 +26,7 @@ @@ -26,7 +26,7 @@
26 <select id="selectProductSalesList" 26 <select id="selectProductSalesList"
27 resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.product.CrmStatisticsProductSalesRespVO"> 27 resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.product.CrmStatisticsProductSalesRespVO">
28 SELECT category.id AS category_id, 28 SELECT category.id AS category_id,
29 - IFNULL(category.name, '未分类') AS category_name, 29 + COALESCE(category.name, '未分类') AS category_name,
30 product.id AS product_id, 30 product.id AS product_id,
31 product.name AS product_name, 31 product.name AS product_name,
32 contract.id AS contract_id, 32 contract.id AS contract_id,
@@ -39,12 +39,12 @@ @@ -39,12 +39,12 @@
39 contract_product.product_price, 39 contract_product.product_price,
40 contract_product.count AS product_count, 40 contract_product.count AS product_count,
41 contract_product.total_price AS product_total_price 41 contract_product.total_price AS product_total_price
42 - FROM crm_contract_product AS contract_product  
43 - INNER JOIN crm_contract AS contract ON contract_product.contract_id = contract.id  
44 - INNER JOIN crm_product AS product ON contract_product.product_id = product.id  
45 - LEFT JOIN crm_product_category AS category ON product.category_id = category.id AND category.deleted = 0  
46 - LEFT JOIN crm_customer AS customer ON contract.customer_id = customer.id AND customer.deleted = 0  
47 - LEFT JOIN system_users AS owner ON contract.owner_user_id = owner.id AND owner.deleted = 0 42 + FROM crm_contract_product contract_product
  43 + INNER JOIN crm_contract contract ON contract_product.contract_id = contract.id
  44 + INNER JOIN crm_product product ON contract_product.product_id = product.id
  45 + LEFT JOIN crm_product_category category ON product.category_id = category.id AND category.deleted = 0
  46 + LEFT JOIN crm_customer customer ON contract.customer_id = customer.id AND customer.deleted = 0
  47 + LEFT JOIN system_users owner ON contract.owner_user_id = owner.id AND owner.deleted = 0
48 WHERE <include refid="ProductStatisticsWhere"/> 48 WHERE <include refid="ProductStatisticsWhere"/>
49 ORDER BY category.id ASC, product.id ASC, contract.order_date DESC 49 ORDER BY category.id ASC, product.id ASC, contract.order_date DESC
50 </select> 50 </select>
@@ -52,14 +52,14 @@ @@ -52,14 +52,14 @@
52 <select id="selectProductCategorySummary" 52 <select id="selectProductCategorySummary"
53 resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.product.CrmStatisticsProductCategoryRespVO"> 53 resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.product.CrmStatisticsProductCategoryRespVO">
54 SELECT category.id AS category_id, 54 SELECT category.id AS category_id,
55 - IFNULL(category.name, '未分类') AS category_name, 55 + COALESCE(category.name, '未分类') AS category_name,
56 COUNT(DISTINCT contract.id) AS contract_count, 56 COUNT(DISTINCT contract.id) AS contract_count,
57 - IFNULL(SUM(contract_product.count), 0) AS product_count,  
58 - IFNULL(SUM(contract_product.total_price), 0) AS product_total_price  
59 - FROM crm_contract_product AS contract_product  
60 - INNER JOIN crm_contract AS contract ON contract_product.contract_id = contract.id  
61 - INNER JOIN crm_product AS product ON contract_product.product_id = product.id  
62 - LEFT JOIN crm_product_category AS category ON product.category_id = category.id AND category.deleted = 0 57 + COALESCE(SUM(contract_product.count), 0) AS product_count,
  58 + COALESCE(SUM(contract_product.total_price), 0) AS product_total_price
  59 + FROM crm_contract_product contract_product
  60 + INNER JOIN crm_contract contract ON contract_product.contract_id = contract.id
  61 + INNER JOIN crm_product product ON contract_product.product_id = product.id
  62 + LEFT JOIN crm_product_category category ON product.category_id = category.id AND category.deleted = 0
63 WHERE <include refid="ProductStatisticsWhere"/> 63 WHERE <include refid="ProductStatisticsWhere"/>
64 GROUP BY category.id, category.name 64 GROUP BY category.id, category.name
65 ORDER BY product_count DESC, product_total_price DESC 65 ORDER BY product_count DESC, product_total_price DESC
yudao-module-crm/src/main/resources/mapper/statistics/CrmStatisticsRankMapper.xml
@@ -4,7 +4,7 @@ @@ -4,7 +4,7 @@
4 4
5 <select id="selectContractPriceRank" 5 <select id="selectContractPriceRank"
6 resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankRespVO"> 6 resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankRespVO">
7 - SELECT IFNULL(SUM(total_price), 0) AS count, owner_user_id 7 + SELECT COALESCE(SUM(total_price), 0) AS count, owner_user_id
8 FROM crm_contract 8 FROM crm_contract
9 WHERE deleted = 0 9 WHERE deleted = 0
10 AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status} 10 AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
@@ -18,7 +18,7 @@ @@ -18,7 +18,7 @@
18 18
19 <select id="selectReceivablePriceRank" 19 <select id="selectReceivablePriceRank"
20 resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankRespVO"> 20 resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankRespVO">
21 - SELECT IFNULL(SUM(price), 0) AS count, owner_user_id 21 + SELECT COALESCE(SUM(price), 0) AS count, owner_user_id
22 FROM crm_receivable 22 FROM crm_receivable
23 WHERE deleted = 0 23 WHERE deleted = 0
24 AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status} 24 AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
@@ -88,8 +88,8 @@ @@ -88,8 +88,8 @@
88 <select id="selectFollowCountRank" 88 <select id="selectFollowCountRank"
89 resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankRespVO"> 89 resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankRespVO">
90 SELECT COUNT(1) AS count, cc.owner_user_id 90 SELECT COUNT(1) AS count, cc.owner_user_id
91 - FROM crm_follow_up_record AS cfur  
92 - LEFT JOIN crm_contact AS cc ON FIND_IN_SET(cc.id, cfur.contact_ids) 91 + FROM crm_follow_up_record cfur
  92 + LEFT JOIN crm_contact cc ON FIND_IN_SET(cc.id, cfur.contact_ids)
93 WHERE cfur.deleted = 0 93 WHERE cfur.deleted = 0
94 AND cc.deleted = 0 94 AND cc.deleted = 0
95 AND cc.owner_user_id in 95 AND cc.owner_user_id in
@@ -103,8 +103,8 @@ @@ -103,8 +103,8 @@
103 <select id="selectFollowCustomerCountRank" 103 <select id="selectFollowCustomerCountRank"
104 resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankRespVO"> 104 resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.rank.CrmStatisticsRankRespVO">
105 SELECT COUNT(DISTINCT cc.id) AS count, cc.owner_user_id 105 SELECT COUNT(DISTINCT cc.id) AS count, cc.owner_user_id
106 - FROM crm_follow_up_record AS cfur  
107 - LEFT JOIN crm_contact AS cc ON FIND_IN_SET(cc.id, cfur.contact_ids) 106 + FROM crm_follow_up_record cfur
  107 + LEFT JOIN crm_contact cc ON FIND_IN_SET(cc.id, cfur.contact_ids)
108 WHERE cfur.deleted = 0 108 WHERE cfur.deleted = 0
109 AND cc.deleted = 0 109 AND cc.deleted = 0
110 AND cc.owner_user_id in 110 AND cc.owner_user_id in
yudao-module-erp/src/main/resources/mapper/statistics/ErpPurchaseStatisticsMapper.xml
@@ -4,7 +4,7 @@ @@ -4,7 +4,7 @@
4 4
5 <select id="getPurchasePrice" resultType="java.math.BigDecimal"> 5 <select id="getPurchasePrice" resultType="java.math.BigDecimal">
6 SELECT 6 SELECT
7 - (SELECT IFNULL(SUM(total_price), 0) 7 + (SELECT COALESCE(SUM(total_price), 0)
8 FROM erp_purchase_in 8 FROM erp_purchase_in
9 WHERE in_time >= #{beginTime} 9 WHERE in_time >= #{beginTime}
10 <if test="endTime != null"> 10 <if test="endTime != null">
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 </if> 15 </if>
16 AND deleted = 0 16 AND deleted = 0
17 AND status = 20) - 17 AND status = 20) -
18 - (SELECT IFNULL(SUM(total_price), 0) 18 + (SELECT COALESCE(SUM(total_price), 0)
19 FROM erp_purchase_return 19 FROM erp_purchase_return
20 WHERE return_time >= #{beginTime} 20 WHERE return_time >= #{beginTime}
21 <if test="endTime != null"> 21 <if test="endTime != null">
yudao-module-erp/src/main/resources/mapper/statistics/ErpSaleStatisticsMapper.xml
@@ -4,7 +4,7 @@ @@ -4,7 +4,7 @@
4 4
5 <select id="getSalePrice" resultType="java.math.BigDecimal"> 5 <select id="getSalePrice" resultType="java.math.BigDecimal">
6 SELECT 6 SELECT
7 - (SELECT IFNULL(SUM(total_price), 0) 7 + (SELECT COALESCE(SUM(total_price), 0)
8 FROM erp_sale_out 8 FROM erp_sale_out
9 WHERE out_time >= #{beginTime} 9 WHERE out_time >= #{beginTime}
10 <if test="endTime != null"> 10 <if test="endTime != null">
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 </if> 15 </if>
16 AND deleted = 0 16 AND deleted = 0
17 AND status = 20) - 17 AND status = 20) -
18 - (SELECT IFNULL(SUM(total_price), 0) 18 + (SELECT COALESCE(SUM(total_price), 0)
19 FROM erp_sale_return 19 FROM erp_sale_return
20 WHERE return_time >= #{beginTime} 20 WHERE return_time >= #{beginTime}
21 <if test="endTime != null"> 21 <if test="endTime != null">
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/statistics/ImStatisticsManagerMapper.java
@@ -56,9 +56,9 @@ public interface ImStatisticsManagerMapper { @@ -56,9 +56,9 @@ public interface ImStatisticsManagerMapper {
56 /** 56 /**
57 * 区间内每日新增用户数(按天分组) 57 * 区间内每日新增用户数(按天分组)
58 * 58 *
59 - * @return [{date: "yyyy-MM-dd", count: 123}, ...] 59 + * @return [{statDate: "yyyy-MM-dd", count: 123}, ...]
60 */ 60 */
61 - @Select("SELECT DATE(create_time) AS date, COUNT(*) AS count FROM system_users " + 61 + @Select("SELECT DATE(create_time) AS statDate, COUNT(*) AS count FROM system_users " +
62 "WHERE deleted = 0 AND create_time >= #{beginTime} AND create_time < #{endTime} " + 62 "WHERE deleted = 0 AND create_time >= #{beginTime} AND create_time < #{endTime} " +
63 "GROUP BY DATE(create_time)") 63 "GROUP BY DATE(create_time)")
64 @ResultMap(CaseInsensitiveResultMap.RESULT_MAP_ID) 64 @ResultMap(CaseInsensitiveResultMap.RESULT_MAP_ID)
@@ -68,7 +68,7 @@ public interface ImStatisticsManagerMapper { @@ -68,7 +68,7 @@ public interface ImStatisticsManagerMapper {
68 /** 68 /**
69 * 区间内每日活跃用户数(按天分组、跨私聊+群聊去重) 69 * 区间内每日活跃用户数(按天分组、跨私聊+群聊去重)
70 */ 70 */
71 - @Select("SELECT day AS date, COUNT(DISTINCT user_id) AS count FROM (" + 71 + @Select("SELECT day AS statDate, COUNT(DISTINCT user_id) AS count FROM (" +
72 " SELECT DATE(send_time) AS day, sender_id AS user_id FROM im_private_message " + 72 " SELECT DATE(send_time) AS day, sender_id AS user_id FROM im_private_message " +
73 " WHERE deleted = 0 AND " + NORMAL_MESSAGE_CONDITION + 73 " WHERE deleted = 0 AND " + NORMAL_MESSAGE_CONDITION +
74 " AND send_time >= #{beginTime} AND send_time < #{endTime}" + 74 " AND send_time >= #{beginTime} AND send_time < #{endTime}" +
@@ -98,23 +98,25 @@ public interface ImStatisticsManagerMapper { @@ -98,23 +98,25 @@ public interface ImStatisticsManagerMapper {
98 /** 98 /**
99 * 群规模分布(按群成员数分桶) 99 * 群规模分布(按群成员数分桶)
100 * 100 *
101 - * @return [{range: "1-9 人", count: 123}, ...] 101 + * @return [{sizeRange: "1-9 人", count: 123}, ...]
102 */ 102 */
103 - @Select("SELECT " +  
104 - " CASE " +  
105 - " WHEN cnt < 10 THEN '1-9 人' " +  
106 - " WHEN cnt < 50 THEN '10-49 人' " +  
107 - " WHEN cnt < 200 THEN '50-199 人' " +  
108 - " ELSE '200+ 人' " +  
109 - " END AS `range`, COUNT(*) AS count " +  
110 - "FROM (" +  
111 - " SELECT g.id, COUNT(m.id) AS cnt " +  
112 - " FROM im_group g " +  
113 - " LEFT JOIN im_group_member m ON m.group_id = g.id AND m.status = 0 AND m.deleted = 0 " +  
114 - " WHERE g.deleted = 0 AND g.status = 0 " +  
115 - " GROUP BY g.id" +  
116 - ") t " +  
117 - "GROUP BY `range`") 103 + @Select("SELECT sizeRange, COUNT(*) AS count FROM (" +
  104 + " SELECT " +
  105 + " CASE " +
  106 + " WHEN cnt < 10 THEN '1-9 人' " +
  107 + " WHEN cnt < 50 THEN '10-49 人' " +
  108 + " WHEN cnt < 200 THEN '50-199 人' " +
  109 + " ELSE '200+ 人' " +
  110 + " END AS sizeRange " +
  111 + " FROM (" +
  112 + " SELECT g.id, COUNT(m.id) AS cnt " +
  113 + " FROM im_group g " +
  114 + " LEFT JOIN im_group_member m ON m.group_id = g.id AND m.status = 0 AND m.deleted = 0 " +
  115 + " WHERE g.deleted = 0 AND g.status = 0 " +
  116 + " GROUP BY g.id" +
  117 + " ) t0" +
  118 + ") t1 " +
  119 + "GROUP BY sizeRange")
118 @ResultMap(CaseInsensitiveResultMap.RESULT_MAP_ID) 120 @ResultMap(CaseInsensitiveResultMap.RESULT_MAP_ID)
119 List<Map<String, Object>> selectGroupSizeDistribution(); 121 List<Map<String, Object>> selectGroupSizeDistribution();
120 122
@@ -141,7 +143,7 @@ public interface ImStatisticsManagerMapper { @@ -141,7 +143,7 @@ public interface ImStatisticsManagerMapper {
141 /** 143 /**
142 * 区间内每日私聊消息数(按天分组) 144 * 区间内每日私聊消息数(按天分组)
143 */ 145 */
144 - @Select("SELECT DATE(send_time) AS date, COUNT(*) AS count FROM im_private_message " + 146 + @Select("SELECT DATE(send_time) AS statDate, COUNT(*) AS count FROM im_private_message " +
145 "WHERE deleted = 0 AND " + NORMAL_MESSAGE_CONDITION + 147 "WHERE deleted = 0 AND " + NORMAL_MESSAGE_CONDITION +
146 " AND send_time >= #{beginTime} AND send_time < #{endTime} " + 148 " AND send_time >= #{beginTime} AND send_time < #{endTime} " +
147 "GROUP BY DATE(send_time)") 149 "GROUP BY DATE(send_time)")
@@ -152,7 +154,7 @@ public interface ImStatisticsManagerMapper { @@ -152,7 +154,7 @@ public interface ImStatisticsManagerMapper {
152 /** 154 /**
153 * 区间内每日群聊消息数(按天分组) 155 * 区间内每日群聊消息数(按天分组)
154 */ 156 */
155 - @Select("SELECT DATE(send_time) AS date, COUNT(*) AS count FROM im_group_message " + 157 + @Select("SELECT DATE(send_time) AS statDate, COUNT(*) AS count FROM im_group_message " +
156 "WHERE deleted = 0 AND " + NORMAL_MESSAGE_CONDITION + 158 "WHERE deleted = 0 AND " + NORMAL_MESSAGE_CONDITION +
157 " AND send_time >= #{beginTime} AND send_time < #{endTime} " + 159 " AND send_time >= #{beginTime} AND send_time < #{endTime} " +
158 "GROUP BY DATE(send_time)") 160 "GROUP BY DATE(send_time)")
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/statistics/ImStatisticsManagerServiceImpl.java
@@ -70,7 +70,7 @@ public class ImStatisticsManagerServiceImpl implements ImStatisticsManagerServic @@ -70,7 +70,7 @@ public class ImStatisticsManagerServiceImpl implements ImStatisticsManagerServic
70 public Map<String, Long> getGroupSizeCountMap() { 70 public Map<String, Long> getGroupSizeCountMap() {
71 List<Map<String, Object>> rows = statisticsMapper.selectGroupSizeDistribution(); 71 List<Map<String, Object>> rows = statisticsMapper.selectGroupSizeDistribution();
72 return convertMap(rows, 72 return convertMap(rows,
73 - row -> (String) row.get("range"), 73 + row -> (String) row.get("sizeRange"),
74 row -> Convert.toLong(row.get("count"))); 74 row -> Convert.toLong(row.get("count")));
75 } 75 }
76 76
@@ -115,11 +115,11 @@ public class ImStatisticsManagerServiceImpl implements ImStatisticsManagerServic @@ -115,11 +115,11 @@ public class ImStatisticsManagerServiceImpl implements ImStatisticsManagerServic
115 } 115 }
116 116
117 /** 117 /**
118 - * 把 [{date, count}] 行映射为 {LocalDateTime -> Long}; 118 + * 把 [{statDate, count}] 行映射为 {LocalDateTime -> Long};
119 */ 119 */
120 private static Map<LocalDateTime, Long> toDailyCountMap(List<Map<String, Object>> rows) { 120 private static Map<LocalDateTime, Long> toDailyCountMap(List<Map<String, Object>> rows) {
121 return convertMap(rows, 121 return convertMap(rows,
122 - row -> Convert.convert(LocalDate.class, row.get("date")).atStartOfDay(), 122 + row -> Convert.convert(LocalDate.class, row.get("statDate")).atStartOfDay(),
123 row -> Convert.toLong(row.get("count"))); 123 row -> Convert.toLong(row.get("count")));
124 } 124 }
125 125
yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/statistics/ImStatisticsManagerMapperTest.java
@@ -164,7 +164,7 @@ public class ImStatisticsManagerMapperTest extends BaseDbUnitTest { @@ -164,7 +164,7 @@ public class ImStatisticsManagerMapperTest extends BaseDbUnitTest {
164 164
165 // 断言:分桶包含「1-9 人」和「10-49 人」 165 // 断言:分桶包含「1-9 人」和「10-49 人」
166 Map<Object, Long> byRange = dist.stream().collect(java.util.stream.Collectors.toMap( 166 Map<Object, Long> byRange = dist.stream().collect(java.util.stream.Collectors.toMap(
167 - m -> m.get("range"), m -> ((Number) m.get("count")).longValue())); 167 + m -> m.get("sizeRange"), m -> ((Number) m.get("count")).longValue()));
168 assertEquals(1L, byRange.get("1-9 人")); 168 assertEquals(1L, byRange.get("1-9 人"));
169 assertEquals(1L, byRange.get("10-49 人")); 169 assertEquals(1L, byRange.get("10-49 人"));
170 } 170 }
yudao-module-im/src/test/java/cn/iocoder/yudao/module/im/service/statistics/ImStatisticsManagerServiceImplTest.java
@@ -76,15 +76,15 @@ public class ImStatisticsManagerServiceImplTest extends BaseMockitoUnitTest { @@ -76,15 +76,15 @@ public class ImStatisticsManagerServiceImplTest extends BaseMockitoUnitTest {
76 76
77 @Test 77 @Test
78 public void testGetNewUserDailyCountMap_dateConvert() { 78 public void testGetNewUserDailyCountMap_dateConvert() {
79 - // 准备:date 字段以 SQL Date / String 混入,count 混入多种 Number 79 + // 准备:statDate 字段以 SQL Date / String 混入,count 混入多种 Number
80 when(statisticsMapper.selectNewUserDailyCount(eq(BEGIN), eq(END))).thenReturn(Arrays.asList( 80 when(statisticsMapper.selectNewUserDailyCount(eq(BEGIN), eq(END))).thenReturn(Arrays.asList(
81 - row("date", java.sql.Date.valueOf("2026-01-10"), "count", BigInteger.valueOf(5)),  
82 - row("date", "2026-01-11", "count", 8))); 81 + row("statDate", java.sql.Date.valueOf("2026-01-10"), "count", BigInteger.valueOf(5)),
  82 + row("statDate", "2026-01-11", "count", 8)));
83 83
84 // 调用 84 // 调用
85 Map<LocalDateTime, Long> result = service.getNewUserDailyCountMap(BEGIN, END); 85 Map<LocalDateTime, Long> result = service.getNewUserDailyCountMap(BEGIN, END);
86 86
87 - // 断言:date → LocalDateTime 起始零点 87 + // 断言:statDate → LocalDateTime 起始零点
88 assertEquals(2, result.size()); 88 assertEquals(2, result.size());
89 assertEquals(5L, result.get(LocalDate.of(2026, 1, 10).atStartOfDay())); 89 assertEquals(5L, result.get(LocalDate.of(2026, 1, 10).atStartOfDay()));
90 assertEquals(8L, result.get(LocalDate.of(2026, 1, 11).atStartOfDay())); 90 assertEquals(8L, result.get(LocalDate.of(2026, 1, 11).atStartOfDay()));
@@ -93,7 +93,7 @@ public class ImStatisticsManagerServiceImplTest extends BaseMockitoUnitTest { @@ -93,7 +93,7 @@ public class ImStatisticsManagerServiceImplTest extends BaseMockitoUnitTest {
93 @Test 93 @Test
94 public void testGetActiveUserDailyCountMap_dateConvert() { 94 public void testGetActiveUserDailyCountMap_dateConvert() {
95 when(statisticsMapper.selectActiveUserDailyCount(any(), any())).thenReturn(Arrays.asList( 95 when(statisticsMapper.selectActiveUserDailyCount(any(), any())).thenReturn(Arrays.asList(
96 - row("date", "2026-01-10", "count", 3L))); 96 + row("statDate", "2026-01-10", "count", 3L)));
97 97
98 Map<LocalDateTime, Long> result = service.getActiveUserDailyCountMap(BEGIN, END); 98 Map<LocalDateTime, Long> result = service.getActiveUserDailyCountMap(BEGIN, END);
99 assertEquals(3L, result.get(LocalDate.of(2026, 1, 10).atStartOfDay())); 99 assertEquals(3L, result.get(LocalDate.of(2026, 1, 10).atStartOfDay()));
@@ -102,7 +102,7 @@ public class ImStatisticsManagerServiceImplTest extends BaseMockitoUnitTest { @@ -102,7 +102,7 @@ public class ImStatisticsManagerServiceImplTest extends BaseMockitoUnitTest {
102 @Test 102 @Test
103 public void testGetPrivateMessageDailyCountMap_dateConvert() { 103 public void testGetPrivateMessageDailyCountMap_dateConvert() {
104 when(statisticsMapper.selectPrivateMessageDailyCount(any(), any())).thenReturn(Arrays.asList( 104 when(statisticsMapper.selectPrivateMessageDailyCount(any(), any())).thenReturn(Arrays.asList(
105 - row("date", "2026-01-10", "count", 7L))); 105 + row("statDate", "2026-01-10", "count", 7L)));
106 assertEquals(7L, service.getPrivateMessageDailyCountMap(BEGIN, END) 106 assertEquals(7L, service.getPrivateMessageDailyCountMap(BEGIN, END)
107 .get(LocalDate.of(2026, 1, 10).atStartOfDay())); 107 .get(LocalDate.of(2026, 1, 10).atStartOfDay()));
108 } 108 }
@@ -110,7 +110,7 @@ public class ImStatisticsManagerServiceImplTest extends BaseMockitoUnitTest { @@ -110,7 +110,7 @@ public class ImStatisticsManagerServiceImplTest extends BaseMockitoUnitTest {
110 @Test 110 @Test
111 public void testGetGroupMessageDailyCountMap_dateConvert() { 111 public void testGetGroupMessageDailyCountMap_dateConvert() {
112 when(statisticsMapper.selectGroupMessageDailyCount(any(), any())).thenReturn(Arrays.asList( 112 when(statisticsMapper.selectGroupMessageDailyCount(any(), any())).thenReturn(Arrays.asList(
113 - row("date", "2026-01-10", "count", 9L))); 113 + row("statDate", "2026-01-10", "count", 9L)));
114 assertEquals(9L, service.getGroupMessageDailyCountMap(BEGIN, END) 114 assertEquals(9L, service.getGroupMessageDailyCountMap(BEGIN, END)
115 .get(LocalDate.of(2026, 1, 10).atStartOfDay())); 115 .get(LocalDate.of(2026, 1, 10).atStartOfDay()));
116 } 116 }
@@ -120,8 +120,8 @@ public class ImStatisticsManagerServiceImplTest extends BaseMockitoUnitTest { @@ -120,8 +120,8 @@ public class ImStatisticsManagerServiceImplTest extends BaseMockitoUnitTest {
120 @Test 120 @Test
121 public void testGetGroupSizeCountMap() { 121 public void testGetGroupSizeCountMap() {
122 when(statisticsMapper.selectGroupSizeDistribution()).thenReturn(Arrays.asList( 122 when(statisticsMapper.selectGroupSizeDistribution()).thenReturn(Arrays.asList(
123 - row("range", "1-9 人", "count", BigInteger.valueOf(3)),  
124 - row("range", "10-49 人", "count", 2))); 123 + row("sizeRange", "1-9 人", "count", BigInteger.valueOf(3)),
  124 + row("sizeRange", "10-49 人", "count", 2)));
125 125
126 Map<String, Long> result = service.getGroupSizeCountMap(); 126 Map<String, Long> result = service.getGroupSizeCountMap();
127 assertEquals(3L, result.get("1-9 人")); 127 assertEquals(3L, result.get("1-9 人"));
yudao-module-mall/yudao-module-statistics/src/main/resources/mapper/member/MemberStatisticsMapper.xml
@@ -50,9 +50,14 @@ @@ -50,9 +50,14 @@
50 </if> 50 </if>
51 </select> 51 </select>
52 52
53 - <select id="selectListByCreateTimeBetween"  
54 - resultType="cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberRegisterCountRespVO">  
55 - SELECT CAST(create_time AS DATE) AS date, 53 + <resultMap id="MemberRegisterCountRespVOMap"
  54 + type="cn.iocoder.yudao.module.statistics.controller.admin.member.vo.MemberRegisterCountRespVO">
  55 + <result column="statDate" property="date"/>
  56 + <result column="count" property="count"/>
  57 + </resultMap>
  58 +
  59 + <select id="selectListByCreateTimeBetween" resultMap="MemberRegisterCountRespVOMap">
  60 + SELECT CAST(create_time AS DATE) AS statDate,
56 count(1) AS count 61 count(1) AS count
57 FROM member_user 62 FROM member_user
58 WHERE create_time BETWEEN #{beginTime} AND #{endTime} 63 WHERE create_time BETWEEN #{beginTime} AND #{endTime}
@@ -60,8 +65,8 @@ @@ -60,8 +65,8 @@
60 <if test="@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getTenantId() != null"> 65 <if test="@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getTenantId() != null">
61 AND tenant_id = ${@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getTenantId()} 66 AND tenant_id = ${@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getTenantId()}
62 </if> 67 </if>
63 - GROUP BY date  
64 - ORDER BY date 68 + GROUP BY CAST(create_time AS DATE)
  69 + ORDER BY statDate
65 </select> 70 </select>
66 71
67 </mapper> 72 </mapper>
yudao-module-mall/yudao-module-statistics/src/main/resources/mapper/trade/TradeOrderStatisticsMapper.xml
@@ -6,14 +6,14 @@ @@ -6,14 +6,14 @@
6 resultType="cn.iocoder.yudao.module.statistics.service.member.bo.MemberAreaStatisticsRespBO"> 6 resultType="cn.iocoder.yudao.module.statistics.service.member.bo.MemberAreaStatisticsRespBO">
7 SELECT receiver_area_id AS areaId, 7 SELECT receiver_area_id AS areaId,
8 (SELECT COUNT(DISTINCT s.user_id) 8 (SELECT COUNT(DISTINCT s.user_id)
9 - FROM trade_order AS s 9 + FROM trade_order s
10 WHERE s.receiver_area_id = m.receiver_area_id 10 WHERE s.receiver_area_id = m.receiver_area_id
11 <if test="@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getTenantId() != null"> 11 <if test="@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getTenantId() != null">
12 AND s.tenant_id = ${@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getTenantId()} 12 AND s.tenant_id = ${@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getTenantId()}
13 </if> 13 </if>
14 ) AS orderCreateUserCount, 14 ) AS orderCreateUserCount,
15 (SELECT COUNT(DISTINCT s.user_id) 15 (SELECT COUNT(DISTINCT s.user_id)
16 - FROM trade_order AS s 16 + FROM trade_order s
17 WHERE s.receiver_area_id = m.receiver_area_id 17 WHERE s.receiver_area_id = m.receiver_area_id
18 AND s.pay_status = TRUE 18 AND s.pay_status = TRUE
19 AND s.deleted = 0 19 AND s.deleted = 0
@@ -22,7 +22,7 @@ @@ -22,7 +22,7 @@
22 </if> 22 </if>
23 ) AS orderPayUserCount, 23 ) AS orderPayUserCount,
24 (SELECT SUM(s.pay_price) 24 (SELECT SUM(s.pay_price)
25 - FROM trade_order AS s 25 + FROM trade_order s
26 WHERE s.receiver_area_id = m.receiver_area_id 26 WHERE s.receiver_area_id = m.receiver_area_id
27 AND s.pay_status = TRUE 27 AND s.pay_status = TRUE
28 AND s.deleted = 0 28 AND s.deleted = 0
@@ -82,7 +82,7 @@ @@ -82,7 +82,7 @@
82 82
83 <select id="selectSummaryPriceByPayTimeBetween" resultType="java.lang.Integer"> 83 <select id="selectSummaryPriceByPayTimeBetween" resultType="java.lang.Integer">
84 SELECT SUM(pay_price) 84 SELECT SUM(pay_price)
85 - FROM trade_order AS s 85 + FROM trade_order s
86 WHERE s.pay_status = TRUE 86 WHERE s.pay_status = TRUE
87 AND deleted = 0 87 AND deleted = 0
88 AND create_time BETWEEN #{beginTime} AND #{endTime} 88 AND create_time BETWEEN #{beginTime} AND #{endTime}
@@ -91,11 +91,17 @@ @@ -91,11 +91,17 @@
91 </if> 91 </if>
92 </select> 92 </select>
93 93
94 - <select id="selectListByPayTimeBetweenAndGroupByDay"  
95 - resultType="cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeOrderTrendRespVO">  
96 - SELECT CAST(pay_time AS DATE) AS date,  
97 - COUNT(1) AS orderPayCount,  
98 - SUM(pay_price) AS orderPayPrice 94 + <resultMap id="TradeOrderTrendRespVOMap"
  95 + type="cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeOrderTrendRespVO">
  96 + <result column="statDate" property="date"/>
  97 + <result column="order_pay_count" property="orderPayCount"/>
  98 + <result column="order_pay_price" property="orderPayPrice"/>
  99 + </resultMap>
  100 +
  101 + <select id="selectListByPayTimeBetweenAndGroupByDay" resultMap="TradeOrderTrendRespVOMap">
  102 + SELECT CAST(pay_time AS DATE) AS statDate,
  103 + COUNT(1) AS order_pay_count,
  104 + SUM(pay_price) AS order_pay_price
99 FROM trade_order 105 FROM trade_order
100 WHERE pay_status = TRUE 106 WHERE pay_status = TRUE
101 AND create_time BETWEEN #{beginTime} AND #{endTime} 107 AND create_time BETWEEN #{beginTime} AND #{endTime}
@@ -103,14 +109,13 @@ @@ -103,14 +109,13 @@
103 <if test="@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getTenantId() != null"> 109 <if test="@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getTenantId() != null">
104 AND tenant_id = ${@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getTenantId()} 110 AND tenant_id = ${@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getTenantId()}
105 </if> 111 </if>
106 - GROUP BY date  
107 - ORDER BY date 112 + GROUP BY CAST(pay_time AS DATE)
  113 + ORDER BY statDate
108 </select> 114 </select>
109 115
110 - <select id="selectListByPayTimeBetweenAndGroupByMonth"  
111 - resultType="cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeOrderTrendRespVO"> 116 + <select id="selectListByPayTimeBetweenAndGroupByMonth" resultMap="TradeOrderTrendRespVOMap">
112 SELECT CONCAT(EXTRACT(YEAR FROM pay_time), '-', 117 SELECT CONCAT(EXTRACT(YEAR FROM pay_time), '-',
113 - LPAD(CONCAT(EXTRACT(MONTH FROM pay_time)), 2, '0')) AS date, 118 + LPAD(CONCAT(EXTRACT(MONTH FROM pay_time)), 2, '0')) AS statDate,
114 COUNT(1) AS order_pay_count, 119 COUNT(1) AS order_pay_count,
115 SUM(pay_price) AS order_pay_price 120 SUM(pay_price) AS order_pay_price
116 FROM trade_order 121 FROM trade_order
@@ -120,8 +125,9 @@ @@ -120,8 +125,9 @@
120 <if test="@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getTenantId() != null"> 125 <if test="@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getTenantId() != null">
121 AND tenant_id = ${@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getTenantId()} 126 AND tenant_id = ${@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getTenantId()}
122 </if> 127 </if>
123 - GROUP BY date  
124 - ORDER BY date 128 + GROUP BY CONCAT(EXTRACT(YEAR FROM pay_time), '-',
  129 + LPAD(CONCAT(EXTRACT(MONTH FROM pay_time)), 2, '0'))
  130 + ORDER BY statDate
125 </select> 131 </select>
126 132
127 <select id="selectCountByStatusAndDeliveryType" resultType="java.lang.Long"> 133 <select id="selectCountByStatusAndDeliveryType" resultType="java.lang.Long">
yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageRecordMapper.java
@@ -80,7 +80,7 @@ public interface BrokerageRecordMapper extends BaseMapperX&lt;BrokerageRecordDO&gt; { @@ -80,7 +80,7 @@ public interface BrokerageRecordMapper extends BaseMapperX&lt;BrokerageRecordDO&gt; {
80 80
81 @Select("SELECT SUM(price) FROM trade_brokerage_record " + 81 @Select("SELECT SUM(price) FROM trade_brokerage_record " +
82 "WHERE user_id = #{userId} AND biz_type = #{bizType} AND status = #{status} " + 82 "WHERE user_id = #{userId} AND biz_type = #{bizType} AND status = #{status} " +
83 - "AND unfreeze_time BETWEEN #{beginTime} AND #{endTime} AND deleted = FALSE") 83 + "AND unfreeze_time BETWEEN #{beginTime} AND #{endTime} AND deleted = 0")
84 Integer selectSummaryPriceByUserIdAndBizTypeAndCreateTimeBetween(@Param("userId") Long userId, 84 Integer selectSummaryPriceByUserIdAndBizTypeAndCreateTimeBetween(@Param("userId") Long userId,
85 @Param("bizType") Integer bizType, 85 @Param("bizType") Integer bizType,
86 @Param("status") Integer status, 86 @Param("status") Integer status,
@@ -89,7 +89,7 @@ public interface BrokerageRecordMapper extends BaseMapperX&lt;BrokerageRecordDO&gt; { @@ -89,7 +89,7 @@ public interface BrokerageRecordMapper extends BaseMapperX&lt;BrokerageRecordDO&gt; {
89 89
90 // TODO @芋艿:收敛掉 @Select 注解操作,统一成 MyBatis-Plus 的方式,或者 xml 90 // TODO @芋艿:收敛掉 @Select 注解操作,统一成 MyBatis-Plus 的方式,或者 xml
91 @Select("SELECT user_id AS id, SUM(price) AS brokeragePrice FROM trade_brokerage_record " + 91 @Select("SELECT user_id AS id, SUM(price) AS brokeragePrice FROM trade_brokerage_record " +
92 - "WHERE biz_type = #{bizType} AND status = #{status} AND deleted = FALSE " + 92 + "WHERE biz_type = #{bizType} AND status = #{status} AND deleted = 0 " +
93 "AND unfreeze_time BETWEEN #{beginTime} AND #{endTime} " + 93 "AND unfreeze_time BETWEEN #{beginTime} AND #{endTime} " +
94 "GROUP BY user_id " + 94 "GROUP BY user_id " +
95 "ORDER BY brokeragePrice DESC") 95 "ORDER BY brokeragePrice DESC")
@@ -100,7 +100,7 @@ public interface BrokerageRecordMapper extends BaseMapperX&lt;BrokerageRecordDO&gt; { @@ -100,7 +100,7 @@ public interface BrokerageRecordMapper extends BaseMapperX&lt;BrokerageRecordDO&gt; {
100 @Param("endTime") LocalDateTime endTime); 100 @Param("endTime") LocalDateTime endTime);
101 101
102 @Select("SELECT COUNT(1) FROM trade_brokerage_record " + 102 @Select("SELECT COUNT(1) FROM trade_brokerage_record " +
103 - "WHERE biz_type = #{bizType} AND status = #{status} AND deleted = FALSE " + 103 + "WHERE biz_type = #{bizType} AND status = #{status} AND deleted = 0 " +
104 "AND unfreeze_time BETWEEN #{beginTime} AND #{endTime} " + 104 "AND unfreeze_time BETWEEN #{beginTime} AND #{endTime} " +
105 "GROUP BY user_id HAVING SUM(price) > #{brokeragePrice}") 105 "GROUP BY user_id HAVING SUM(price) > #{brokeragePrice}")
106 Integer selectCountByPriceGt(@Param("brokeragePrice") Integer brokeragePrice, 106 Integer selectCountByPriceGt(@Param("brokeragePrice") Integer brokeragePrice,
yudao-module-mall/yudao-module-trade/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageUserMapper.java
@@ -128,7 +128,7 @@ public interface BrokerageUserMapper extends BaseMapperX&lt;BrokerageUserDO&gt; { @@ -128,7 +128,7 @@ public interface BrokerageUserMapper extends BaseMapperX&lt;BrokerageUserDO&gt; {
128 } 128 }
129 129
130 @Select("SELECT bind_user_id AS id, COUNT(1) AS brokerageUserCount FROM trade_brokerage_user " + 130 @Select("SELECT bind_user_id AS id, COUNT(1) AS brokerageUserCount FROM trade_brokerage_user " +
131 - "WHERE bind_user_id IS NOT NULL AND deleted = FALSE " + 131 + "WHERE bind_user_id IS NOT NULL AND deleted = 0 " +
132 "AND bind_user_time BETWEEN #{beginTime} AND #{endTime} " + 132 "AND bind_user_time BETWEEN #{beginTime} AND #{endTime} " +
133 "GROUP BY bind_user_id " + 133 "GROUP BY bind_user_id " +
134 "ORDER BY brokerageUserCount DESC") 134 "ORDER BY brokerageUserCount DESC")
yudao-module-mall/yudao-module-trade/src/main/resources/mapper/brokerage/BrokerageUserMapper.xml
@@ -22,7 +22,7 @@ @@ -22,7 +22,7 @@
22 WHERE r.user_id = bu.id AND biz_type = #{bizType} AND r.status = #{status} AND r.deleted = 0) AS brokerageOrderCount, 22 WHERE r.user_id = bu.id AND biz_type = #{bizType} AND r.status = #{status} AND r.deleted = 0) AS brokerageOrderCount,
23 (SELECT COUNT(1) FROM trade_brokerage_user c 23 (SELECT COUNT(1) FROM trade_brokerage_user c
24 WHERE c.bind_user_id = bu.id AND c.deleted = 0) AS brokerageUserCount 24 WHERE c.bind_user_id = bu.id AND c.deleted = 0) AS brokerageUserCount
25 - FROM trade_brokerage_user AS bu 25 + FROM trade_brokerage_user bu
26 <where> 26 <where>
27 bu.deleted = 0 27 bu.deleted = 0
28 <if test="ids != null and ids.size() > 0"> 28 <if test="ids != null and ids.size() > 0">
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/home/MesHomeStatisticsMapper.java
@@ -59,7 +59,7 @@ public interface MesHomeStatisticsMapper { @@ -59,7 +59,7 @@ public interface MesHomeStatisticsMapper {
59 * 59 *
60 * @param beginTime >= 开始时间 60 * @param beginTime >= 开始时间
61 * @param endTime < 结束时间 61 * @param endTime < 结束时间
62 - * @return [{ "date": "2026-04-05", "quantity": 1234, "qualifiedQuantity": 1200, "unqualifiedQuantity": 34 }, ...] 62 + * @return [{ "statDate": "2026-04-05", "quantity": 1234, "qualifiedQuantity": 1200, "unqualifiedQuantity": 34 }, ...]
63 */ 63 */
64 List<Map<String, Object>> selectDailyFeedbackTrend(@Param("beginTime") LocalDateTime beginTime, 64 List<Map<String, Object>> selectDailyFeedbackTrend(@Param("beginTime") LocalDateTime beginTime,
65 @Param("endTime") LocalDateTime endTime); 65 @Param("endTime") LocalDateTime endTime);
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/task/MesProTaskMapper.java
@@ -62,9 +62,9 @@ public interface MesProTaskMapper extends BaseMapperX&lt;MesProTaskDO&gt; { @@ -62,9 +62,9 @@ public interface MesProTaskMapper extends BaseMapperX&lt;MesProTaskDO&gt; {
62 BigDecimal incrUnqualifyQuantity) { 62 BigDecimal incrUnqualifyQuantity) {
63 update(null, new LambdaUpdateWrapper<MesProTaskDO>() 63 update(null, new LambdaUpdateWrapper<MesProTaskDO>()
64 .eq(MesProTaskDO::getId, id) 64 .eq(MesProTaskDO::getId, id)
65 - .setSql("produced_quantity = IFNULL(produced_quantity, 0) + " + incrProducedQuantity)  
66 - .setSql("qualify_quantity = IFNULL(qualify_quantity, 0) + " + incrQualifyQuantity)  
67 - .setSql("unqualify_quantity = IFNULL(unqualify_quantity, 0) + " + incrUnqualifyQuantity)); 65 + .setIncrBy(MesProTaskDO::getProducedQuantity, incrProducedQuantity)
  66 + .setIncrBy(MesProTaskDO::getQualifyQuantity, incrQualifyQuantity)
  67 + .setIncrBy(MesProTaskDO::getUnqualifyQuantity, incrUnqualifyQuantity));
68 } 68 }
69 69
70 } 70 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/workorder/MesProWorkOrderMapper.java
@@ -38,7 +38,7 @@ public interface MesProWorkOrderMapper extends BaseMapperX&lt;MesProWorkOrderDO&gt; { @@ -38,7 +38,7 @@ public interface MesProWorkOrderMapper extends BaseMapperX&lt;MesProWorkOrderDO&gt; {
38 default void updateProducedQuantity(Long id, BigDecimal incrQuantityProduced) { 38 default void updateProducedQuantity(Long id, BigDecimal incrQuantityProduced) {
39 update(null, new LambdaUpdateWrapper<MesProWorkOrderDO>() 39 update(null, new LambdaUpdateWrapper<MesProWorkOrderDO>()
40 .eq(MesProWorkOrderDO::getId, id) 40 .eq(MesProWorkOrderDO::getId, id)
41 - .setSql("quantity_produced = IFNULL(quantity_produced, 0) + " + incrQuantityProduced)); 41 + .setIncrBy(MesProWorkOrderDO::getQuantityProduced, incrQuantityProduced));
42 } 42 }
43 43
44 default Long selectCountByVendorId(Long vendorId) { 44 default Long selectCountByVendorId(Long vendorId) {
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/home/MesHomeStatisticsServiceImpl.java
@@ -121,7 +121,7 @@ public class MesHomeStatisticsServiceImpl implements MesHomeStatisticsService { @@ -121,7 +121,7 @@ public class MesHomeStatisticsServiceImpl implements MesHomeStatisticsService {
121 List<Map<String, Object>> dbData = homeStatisticsMapper.selectDailyFeedbackTrend(beginTime, endTime); 121 List<Map<String, Object>> dbData = homeStatisticsMapper.selectDailyFeedbackTrend(beginTime, endTime);
122 Map<String, Map<String, Object>> dateMap = new LinkedHashMap<>(); 122 Map<String, Map<String, Object>> dateMap = new LinkedHashMap<>();
123 for (Map<String, Object> row : dbData) { 123 for (Map<String, Object> row : dbData) {
124 - dateMap.put((String) row.get("date"), row); 124 + dateMap.put((String) row.get("statDate"), row);
125 } 125 }
126 126
127 // 补全所有日期(无数据的日期填 0) 127 // 补全所有日期(无数据的日期填 0)
yudao-module-mes/src/main/resources/mapper/home/MesHomeStatisticsMapper.xml
@@ -12,9 +12,9 @@ @@ -12,9 +12,9 @@
12 12
13 <!-- 统计报工产量合计 --> 13 <!-- 统计报工产量合计 -->
14 <select id="selectFeedbackSummary" resultType="cn.iocoder.yudao.framework.mybatis.core.util.CaseInsensitiveResultMap"> 14 <select id="selectFeedbackSummary" resultType="cn.iocoder.yudao.framework.mybatis.core.util.CaseInsensitiveResultMap">
15 - SELECT IFNULL(SUM(feedback_quantity), 0) AS feedbackQuantity,  
16 - IFNULL(SUM(qualified_quantity), 0) AS qualifiedQuantity,  
17 - IFNULL(SUM(unqualified_quantity), 0) AS unqualifiedQuantity 15 + SELECT COALESCE(SUM(feedback_quantity), 0) AS feedbackQuantity,
  16 + COALESCE(SUM(qualified_quantity), 0) AS qualifiedQuantity,
  17 + COALESCE(SUM(unqualified_quantity), 0) AS unqualifiedQuantity
18 FROM mes_pro_feedback 18 FROM mes_pro_feedback
19 WHERE deleted = 0 19 WHERE deleted = 0
20 AND status = ${@cn.iocoder.yudao.module.mes.enums.pro.MesProFeedbackStatusEnum@FINISHED.status} 20 AND status = ${@cn.iocoder.yudao.module.mes.enums.pro.MesProFeedbackStatusEnum@FINISHED.status}
@@ -52,10 +52,10 @@ @@ -52,10 +52,10 @@
52 52
53 <!-- 按天聚合报工产量 --> 53 <!-- 按天聚合报工产量 -->
54 <select id="selectDailyFeedbackTrend" resultType="cn.iocoder.yudao.framework.mybatis.core.util.CaseInsensitiveResultMap"> 54 <select id="selectDailyFeedbackTrend" resultType="cn.iocoder.yudao.framework.mybatis.core.util.CaseInsensitiveResultMap">
55 - SELECT DATE_FORMAT(feedback_time, '%Y-%m-%d') AS date,  
56 - IFNULL(SUM(feedback_quantity), 0) AS quantity,  
57 - IFNULL(SUM(qualified_quantity), 0) AS qualifiedQuantity,  
58 - IFNULL(SUM(unqualified_quantity), 0) AS unqualifiedQuantity 55 + SELECT DATE_FORMAT(feedback_time, '%Y-%m-%d') AS statDate,
  56 + COALESCE(SUM(feedback_quantity), 0) AS quantity,
  57 + COALESCE(SUM(qualified_quantity), 0) AS qualifiedQuantity,
  58 + COALESCE(SUM(unqualified_quantity), 0) AS unqualifiedQuantity
59 FROM mes_pro_feedback 59 FROM mes_pro_feedback
60 WHERE deleted = 0 60 WHERE deleted = 0
61 AND status = ${@cn.iocoder.yudao.module.mes.enums.pro.MesProFeedbackStatusEnum@FINISHED.status} 61 AND status = ${@cn.iocoder.yudao.module.mes.enums.pro.MesProFeedbackStatusEnum@FINISHED.status}
@@ -64,7 +64,7 @@ @@ -64,7 +64,7 @@
64 AND feedback_time &lt; #{endTime} 64 AND feedback_time &lt; #{endTime}
65 </if> 65 </if>
66 GROUP BY DATE_FORMAT(feedback_time, '%Y-%m-%d') 66 GROUP BY DATE_FORMAT(feedback_time, '%Y-%m-%d')
67 - ORDER BY date 67 + ORDER BY statDate
68 </select> 68 </select>
69 69
70 </mapper> 70 </mapper>
yudao-module-mes/src/main/resources/mapper/qc/pendinginspect/MesQcPendingInspectMapper.xml
@@ -10,6 +10,7 @@ @@ -10,6 +10,7 @@
10 4. 销售出库单(OQC):单据状态=CONFIRMED(1),行的 oqc_check_flag=1 且 oqc_id 为空 10 4. 销售出库单(OQC):单据状态=CONFIRMED(1),行的 oqc_check_flag=1 且 oqc_id 为空
11 5. 生产退料单(RQC):单据状态=CONFIRMED(1),行的 rqc_check_flag=1 且 rqc_id 为空 且 quality_status=0(待检) 11 5. 生产退料单(RQC):单据状态=CONFIRMED(1),行的 rqc_check_flag=1 且 rqc_id 为空 且 quality_status=0(待检)
12 6. 销售退货单(RQC):单据状态=CONFIRMED(1),行的 rqc_check_flag=1 且 rqc_id 为空 且 quality_status=0(待检) 12 6. 销售退货单(RQC):单据状态=CONFIRMED(1),行的 rqc_check_flag=1 且 rqc_id 为空 且 quality_status=0(待检)
  13 + 注意:各分支无值的 ID 列使用 CASE WHEN 1 = 0 THEN 本分支 bigint 列 END 产生“带类型的 NULL”,避免 UNION 中裸 NULL 的类型推断差异
13 --> 14 -->
14 <select id="selectQcPendingPage" resultType="cn.iocoder.yudao.module.mes.controller.admin.qc.pendinginspect.vo.MesQcPendingInspectRespVO"> 15 <select id="selectQcPendingPage" resultType="cn.iocoder.yudao.module.mes.controller.admin.qc.pendinginspect.vo.MesQcPendingInspectRespVO">
15 SELECT * FROM ( 16 SELECT * FROM (
@@ -24,10 +25,10 @@ @@ -24,10 +25,10 @@
24 l.item_id AS itemId, 25 l.item_id AS itemId,
25 l.arrival_quantity AS quantity, 26 l.arrival_quantity AS quantity,
26 n.vendor_id AS vendorId, 27 n.vendor_id AS vendorId,
27 - NULL AS workOrderId,  
28 - NULL AS workstationId,  
29 - NULL AS taskId,  
30 - NULL AS clientId 28 + CASE WHEN 1 = 0 THEN n.id END AS workOrderId,
  29 + CASE WHEN 1 = 0 THEN n.id END AS workstationId,
  30 + CASE WHEN 1 = 0 THEN n.id END AS taskId,
  31 + CASE WHEN 1 = 0 THEN n.id END AS clientId
31 FROM mes_wm_arrival_notice n 32 FROM mes_wm_arrival_notice n
32 JOIN mes_wm_arrival_notice_line l ON n.id = l.notice_id 33 JOIN mes_wm_arrival_notice_line l ON n.id = l.notice_id
33 WHERE n.status = ${@cn.iocoder.yudao.module.mes.enums.wm.MesWmArrivalNoticeStatusEnum@PENDING_QC.status} 34 WHERE n.status = ${@cn.iocoder.yudao.module.mes.enums.wm.MesWmArrivalNoticeStatusEnum@PENDING_QC.status}
@@ -49,10 +50,10 @@ @@ -49,10 +50,10 @@
49 rl.item_id AS itemId, 50 rl.item_id AS itemId,
50 rl.quantity AS quantity, 51 rl.quantity AS quantity,
51 r.vendor_id AS vendorId, 52 r.vendor_id AS vendorId,
52 - NULL AS workOrderId,  
53 - NULL AS workstationId,  
54 - NULL AS taskId,  
55 - NULL AS clientId 53 + CASE WHEN 1 = 0 THEN r.id END AS workOrderId,
  54 + CASE WHEN 1 = 0 THEN r.id END AS workstationId,
  55 + CASE WHEN 1 = 0 THEN r.id END AS taskId,
  56 + CASE WHEN 1 = 0 THEN r.id END AS clientId
56 FROM mes_wm_outsource_receipt r 57 FROM mes_wm_outsource_receipt r
57 JOIN mes_wm_outsource_receipt_line rl ON r.id = rl.receipt_id 58 JOIN mes_wm_outsource_receipt_line rl ON r.id = rl.receipt_id
58 WHERE r.status = ${@cn.iocoder.yudao.module.mes.enums.wm.MesWmOutsourceReceiptStatusEnum@CONFIRMED.status} 59 WHERE r.status = ${@cn.iocoder.yudao.module.mes.enums.wm.MesWmOutsourceReceiptStatusEnum@CONFIRMED.status}
@@ -73,11 +74,11 @@ @@ -73,11 +74,11 @@
73 ${@cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum@IPQC.type} AS qcType, 74 ${@cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum@IPQC.type} AS qcType,
74 ppl.item_id AS itemId, 75 ppl.item_id AS itemId,
75 ppl.quantity AS quantity, 76 ppl.quantity AS quantity,
76 - NULL AS vendorId, 77 + CASE WHEN 1 = 0 THEN pf.id END AS vendorId,
77 pp.work_order_id AS workOrderId, 78 pp.work_order_id AS workOrderId,
78 pp.workstation_id AS workstationId, 79 pp.workstation_id AS workstationId,
79 pp.task_id AS taskId, 80 pp.task_id AS taskId,
80 - NULL AS clientId 81 + CASE WHEN 1 = 0 THEN pf.id END AS clientId
81 FROM mes_wm_product_produce_line ppl 82 FROM mes_wm_product_produce_line ppl
82 JOIN mes_wm_product_produce pp ON ppl.produce_id = pp.id 83 JOIN mes_wm_product_produce pp ON ppl.produce_id = pp.id
83 JOIN mes_pro_feedback pf ON pp.feedback_id = pf.id 84 JOIN mes_pro_feedback pf ON pp.feedback_id = pf.id
@@ -98,10 +99,10 @@ @@ -98,10 +99,10 @@
98 ${@cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum@OQC.type} AS qcType, 99 ${@cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum@OQC.type} AS qcType,
99 sl.item_id AS itemId, 100 sl.item_id AS itemId,
100 sl.quantity AS quantity, 101 sl.quantity AS quantity,
101 - NULL AS vendorId,  
102 - NULL AS workOrderId,  
103 - NULL AS workstationId,  
104 - NULL AS taskId, 102 + CASE WHEN 1 = 0 THEN s.id END AS vendorId,
  103 + CASE WHEN 1 = 0 THEN s.id END AS workOrderId,
  104 + CASE WHEN 1 = 0 THEN s.id END AS workstationId,
  105 + CASE WHEN 1 = 0 THEN s.id END AS taskId,
105 s.client_id AS clientId 106 s.client_id AS clientId
106 FROM mes_wm_product_sales s 107 FROM mes_wm_product_sales s
107 JOIN mes_wm_product_sales_line sl ON s.id = sl.sales_id 108 JOIN mes_wm_product_sales_line sl ON s.id = sl.sales_id
@@ -123,11 +124,11 @@ @@ -123,11 +124,11 @@
123 ${@cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum@RQC.type} AS qcType, 124 ${@cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum@RQC.type} AS qcType,
124 ril.item_id AS itemId, 125 ril.item_id AS itemId,
125 ril.quantity AS quantity, 126 ril.quantity AS quantity,
126 - NULL AS vendorId, 127 + CASE WHEN 1 = 0 THEN ri.id END AS vendorId,
127 ri.work_order_id AS workOrderId, 128 ri.work_order_id AS workOrderId,
128 ri.workstation_id AS workstationId, 129 ri.workstation_id AS workstationId,
129 - NULL AS taskId,  
130 - NULL AS clientId 130 + CASE WHEN 1 = 0 THEN ri.id END AS taskId,
  131 + CASE WHEN 1 = 0 THEN ri.id END AS clientId
131 FROM mes_wm_return_issue ri 132 FROM mes_wm_return_issue ri
132 JOIN mes_wm_return_issue_line ril ON ri.id = ril.issue_id 133 JOIN mes_wm_return_issue_line ril ON ri.id = ril.issue_id
133 WHERE ri.status = ${@cn.iocoder.yudao.module.mes.enums.wm.MesWmReturnIssueStatusEnum@CONFIRMED.status} 134 WHERE ri.status = ${@cn.iocoder.yudao.module.mes.enums.wm.MesWmReturnIssueStatusEnum@CONFIRMED.status}
@@ -149,10 +150,10 @@ @@ -149,10 +150,10 @@
149 ${@cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum@RQC.type} AS qcType, 150 ${@cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum@RQC.type} AS qcType,
150 rl.item_id AS itemId, 151 rl.item_id AS itemId,
151 rl.quantity AS quantity, 152 rl.quantity AS quantity,
152 - NULL AS vendorId,  
153 - NULL AS workOrderId,  
154 - NULL AS workstationId,  
155 - NULL AS taskId, 153 + CASE WHEN 1 = 0 THEN r.id END AS vendorId,
  154 + CASE WHEN 1 = 0 THEN r.id END AS workOrderId,
  155 + CASE WHEN 1 = 0 THEN r.id END AS workstationId,
  156 + CASE WHEN 1 = 0 THEN r.id END AS taskId,
156 r.client_id AS clientId 157 r.client_id AS clientId
157 FROM mes_wm_return_sales r 158 FROM mes_wm_return_sales r
158 JOIN mes_wm_return_sales_line rl ON r.id = rl.return_id 159 JOIN mes_wm_return_sales_line rl ON r.id = rl.return_id
yudao-module-mes/src/test/resources/sql/clean.sql
@@ -20,5 +20,6 @@ DELETE FROM &quot;mes_wm_item_receipt_detail&quot;; @@ -20,5 +20,6 @@ DELETE FROM &quot;mes_wm_item_receipt_detail&quot;;
20 DELETE FROM "mes_wm_batch"; 20 DELETE FROM "mes_wm_batch";
21 DELETE FROM "mes_wm_material_stock"; 21 DELETE FROM "mes_wm_material_stock";
22 DELETE FROM "mes_pro_task"; 22 DELETE FROM "mes_pro_task";
  23 +DELETE FROM "mes_pro_work_order";
23 DELETE FROM "mes_pro_route_process"; 24 DELETE FROM "mes_pro_route_process";
24 DELETE FROM "mes_qc_indicator_result"; 25 DELETE FROM "mes_qc_indicator_result";
yudao-module-mes/src/test/resources/sql/create_tables.sql
@@ -756,9 +756,9 @@ CREATE TABLE IF NOT EXISTS &quot;mes_pro_task&quot; ( @@ -756,9 +756,9 @@ CREATE TABLE IF NOT EXISTS &quot;mes_pro_task&quot; (
756 "process_id" bigint DEFAULT NULL, 756 "process_id" bigint DEFAULT NULL,
757 "item_id" bigint DEFAULT NULL, 757 "item_id" bigint DEFAULT NULL,
758 "quantity" decimal(14,2) DEFAULT NULL, 758 "quantity" decimal(14,2) DEFAULT NULL,
759 - "produced_quantity" decimal(14,2) DEFAULT NULL,  
760 - "qualify_quantity" decimal(14,2) DEFAULT NULL,  
761 - "unqualify_quantity" decimal(14,2) DEFAULT NULL, 759 + "produced_quantity" decimal(14,2) NOT NULL DEFAULT 0,
  760 + "qualify_quantity" decimal(14,2) NOT NULL DEFAULT 0,
  761 + "unqualify_quantity" decimal(14,2) NOT NULL DEFAULT 0,
762 "changed_quantity" decimal(14,2) DEFAULT NULL, 762 "changed_quantity" decimal(14,2) DEFAULT NULL,
763 "client_id" bigint DEFAULT NULL, 763 "client_id" bigint DEFAULT NULL,
764 "start_time" timestamp DEFAULT NULL, 764 "start_time" timestamp DEFAULT NULL,
@@ -779,6 +779,39 @@ CREATE TABLE IF NOT EXISTS &quot;mes_pro_task&quot; ( @@ -779,6 +779,39 @@ CREATE TABLE IF NOT EXISTS &quot;mes_pro_task&quot; (
779 ); 779 );
780 780
781 -- ---------------------------- 781 -- ----------------------------
  782 +-- MES 生产工单
  783 +-- ----------------------------
  784 +CREATE TABLE IF NOT EXISTS "mes_pro_work_order" (
  785 + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
  786 + "code" varchar(64) DEFAULT NULL,
  787 + "name" varchar(255) DEFAULT NULL,
  788 + "type" tinyint DEFAULT NULL,
  789 + "order_source_type" tinyint DEFAULT NULL,
  790 + "order_source_code" varchar(64) DEFAULT NULL,
  791 + "product_id" bigint DEFAULT NULL,
  792 + "quantity" decimal(14,2) NOT NULL DEFAULT 0,
  793 + "quantity_produced" decimal(14,2) NOT NULL DEFAULT 0,
  794 + "quantity_changed" decimal(14,2) NOT NULL DEFAULT 0,
  795 + "quantity_scheduled" decimal(14,2) NOT NULL DEFAULT 0,
  796 + "client_id" bigint DEFAULT NULL,
  797 + "vendor_id" bigint DEFAULT NULL,
  798 + "batch_code" varchar(64) DEFAULT NULL,
  799 + "request_date" timestamp DEFAULT NULL,
  800 + "parent_id" bigint NOT NULL DEFAULT 0,
  801 + "finish_date" timestamp DEFAULT NULL,
  802 + "cancel_date" timestamp DEFAULT NULL,
  803 + "status" tinyint NOT NULL DEFAULT 0,
  804 + "remark" varchar(500) DEFAULT '',
  805 + "creator" varchar(64) DEFAULT '',
  806 + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  807 + "updater" varchar(64) DEFAULT '',
  808 + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  809 + "deleted" bit NOT NULL DEFAULT FALSE,
  810 + "tenant_id" bigint NOT NULL DEFAULT 0,
  811 + PRIMARY KEY ("id")
  812 +);
  813 +
  814 +-- ----------------------------
782 -- MES 工艺路线工序 815 -- MES 工艺路线工序
783 -- ---------------------------- 816 -- ----------------------------
784 CREATE TABLE IF NOT EXISTS "mes_pro_route_process" ( 817 CREATE TABLE IF NOT EXISTS "mes_pro_route_process" (
yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/account/MpAccountServiceImpl.java
@@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil; @@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.util.ObjUtil; 4 import cn.hutool.core.util.ObjUtil;
5 import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil; 5 import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
6 import cn.iocoder.yudao.framework.common.pojo.PageResult; 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  7 +import cn.iocoder.yudao.framework.common.util.jdbc.JdbcExceptionUtils;
7 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; 8 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
8 import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountCreateReqVO; 9 import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountCreateReqVO;
9 import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountPageReqVO; 10 import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountPageReqVO;
@@ -73,7 +74,7 @@ public class MpAccountServiceImpl implements MpAccountService { @@ -73,7 +74,7 @@ public class MpAccountServiceImpl implements MpAccountService {
73 try { 74 try {
74 accounts = mpAccountMapper.selectList(); 75 accounts = mpAccountMapper.selectList();
75 } catch (Throwable ex) { 76 } catch (Throwable ex) {
76 - if (!ex.getMessage().contains("doesn't exist")) { 77 + if (!JdbcExceptionUtils.isTableNotExists(ex)) {
77 throw ex; 78 throw ex;
78 } 79 }
79 log.error("[微信公众号 yudao-module-mp - 表结构未导入][参考 https://doc.iocoder.cn/mp/build/ 开启]"); 80 log.error("[微信公众号 yudao-module-mp - 表结构未导入][参考 https://doc.iocoder.cn/mp/build/ 开启]");
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/dal/mysql/announcement/OaAnnouncementMapper.java
@@ -40,7 +40,7 @@ public interface OaAnnouncementMapper extends BaseMapperX&lt;OaAnnouncementDO&gt; { @@ -40,7 +40,7 @@ public interface OaAnnouncementMapper extends BaseMapperX&lt;OaAnnouncementDO&gt; {
40 .selectAll(OaAnnouncementDO.class) 40 .selectAll(OaAnnouncementDO.class)
41 .innerJoin(OaAnnouncementReceiverDO.class, OaAnnouncementReceiverDO::getAnnouncementId,OaAnnouncementDO::getId) 41 .innerJoin(OaAnnouncementReceiverDO.class, OaAnnouncementReceiverDO::getAnnouncementId,OaAnnouncementDO::getId)
42 .eq(OaAnnouncementReceiverDO::getReceiverUserId, receiverUserId) 42 .eq(OaAnnouncementReceiverDO::getReceiverUserId, receiverUserId)
43 - .eq(OaAnnouncementReceiverDO::getDeleted, false) 43 + .eq(OaAnnouncementReceiverDO::getDeleted, 0)
44 .eqIfPresent(OaAnnouncementReceiverDO::getReadStatus, pageReqVO.getReadStatus()) 44 .eqIfPresent(OaAnnouncementReceiverDO::getReadStatus, pageReqVO.getReadStatus())
45 .likeIfPresent(OaAnnouncementDO::getTitle, pageReqVO.getTitle()) 45 .likeIfPresent(OaAnnouncementDO::getTitle, pageReqVO.getTitle())
46 .eqIfPresent(OaAnnouncementDO::getType, pageReqVO.getType()) 46 .eqIfPresent(OaAnnouncementDO::getType, pageReqVO.getType())
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/dal/mysql/contact/OaContactShareMapper.java
@@ -28,7 +28,7 @@ public interface OaContactShareMapper extends BaseMapperX&lt;OaContactShareDO&gt; { @@ -28,7 +28,7 @@ public interface OaContactShareMapper extends BaseMapperX&lt;OaContactShareDO&gt; {
28 MPJLambdaWrapperX<OaContactShareDO> query = new MPJLambdaWrapperX<>(); 28 MPJLambdaWrapperX<OaContactShareDO> query = new MPJLambdaWrapperX<>();
29 query.selectAll(OaContactShareDO.class) 29 query.selectAll(OaContactShareDO.class)
30 .innerJoin(OaContactDO.class, OaContactDO::getId, OaContactShareDO::getContactId) 30 .innerJoin(OaContactDO.class, OaContactDO::getId, OaContactShareDO::getContactId)
31 - .eq(OaContactDO::getDeleted, false) 31 + .eq(OaContactDO::getDeleted, 0)
32 .eq(OaContactShareDO::getCreator, userId.toString()) 32 .eq(OaContactShareDO::getCreator, userId.toString())
33 .ne(OaContactShareDO::getUserId, userId) 33 .ne(OaContactShareDO::getUserId, userId)
34 .in(OaContactShareDO::getContactId, contactIds) 34 .in(OaContactShareDO::getContactId, contactIds)
yudao-module-oa/src/test/java/cn/iocoder/yudao/module/oa/service/announcement/OaAnnouncementServiceImplTest.java
@@ -439,6 +439,28 @@ public class OaAnnouncementServiceImplTest extends BaseDbUnitTest { @@ -439,6 +439,28 @@ public class OaAnnouncementServiceImplTest extends BaseDbUnitTest {
439 } 439 }
440 440
441 @Test 441 @Test
  442 + public void testGetReceivedAnnouncementPage_excludeDeletedReceiver() {
  443 + // mock 数据:同一用户的两条接收关系,其中一条被逻辑删除
  444 + Long userId = randomLongId();
  445 + OaAnnouncementDO keptAnnouncement = buildAnnouncementDO(randomLongId()).setTitle("保留的公告");
  446 + announcementMapper.insert(keptAnnouncement);
  447 + announcementReceiverMapper.insert(buildReceiverDO(keptAnnouncement.getId(), userId, false));
  448 + OaAnnouncementDO removedAnnouncement = buildAnnouncementDO(randomLongId()).setTitle("已移除的公告");
  449 + announcementMapper.insert(removedAnnouncement);
  450 + OaAnnouncementReceiverDO removedReceiver = buildReceiverDO(removedAnnouncement.getId(), userId, true);
  451 + announcementReceiverMapper.insert(removedReceiver);
  452 + announcementReceiverMapper.deleteById(removedReceiver.getId());
  453 +
  454 + // 调用
  455 + PageResult<OaAnnouncementDO> result = announcementService.getReceivedAnnouncementPage(
  456 + new OaAnnouncementPageReqVO(), userId);
  457 +
  458 + // 断言:接收关系 deleted = 0 的条件以整数绑定后,仍只返回未删除的接收关系
  459 + assertEquals(Collections.singletonList(keptAnnouncement.getId()),
  460 + convertList(result.getList(), OaAnnouncementDO::getId));
  461 + }
  462 +
  463 + @Test
442 public void testUpdateAnnouncement_preserveCreateTime() { 464 public void testUpdateAnnouncement_preserveCreateTime() {
443 // mock 数据 465 // mock 数据
444 Long userId = randomLongId(); 466 Long userId = randomLongId();
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/mysql/home/WmsHomeStatisticsMapper.java
@@ -30,7 +30,7 @@ public interface WmsHomeStatisticsMapper { @@ -30,7 +30,7 @@ public interface WmsHomeStatisticsMapper {
30 * @param beginTime >= 开始时间 30 * @param beginTime >= 开始时间
31 * @param endTime < 结束时间 31 * @param endTime < 结束时间
32 * @param warehouseId 仓库编号 32 * @param warehouseId 仓库编号
33 - * @return [{ "date": "2026-05-14", "orderType": 1, "count": 5 }, ...] 33 + * @return [{ "statDate": "2026-05-14", "orderType": 1, "count": 5 }, ...]
34 */ 34 */
35 List<Map<String, Object>> selectDailyOrderTrend(@Param("beginTime") LocalDateTime beginTime, 35 List<Map<String, Object>> selectDailyOrderTrend(@Param("beginTime") LocalDateTime beginTime,
36 @Param("endTime") LocalDateTime endTime, 36 @Param("endTime") LocalDateTime endTime,
yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/home/WmsHomeStatisticsServiceImpl.java
@@ -68,7 +68,7 @@ public class WmsHomeStatisticsServiceImpl implements WmsHomeStatisticsService { @@ -68,7 +68,7 @@ public class WmsHomeStatisticsServiceImpl implements WmsHomeStatisticsService {
68 List<Map<String, Object>> dbData = homeStatisticsMapper.selectDailyOrderTrend(beginTime, endTime, warehouseId); 68 List<Map<String, Object>> dbData = homeStatisticsMapper.selectDailyOrderTrend(beginTime, endTime, warehouseId);
69 Map<String, Map<Integer, Long>> dateMap = new LinkedHashMap<>(); 69 Map<String, Map<Integer, Long>> dateMap = new LinkedHashMap<>();
70 for (Map<String, Object> row : dbData) { 70 for (Map<String, Object> row : dbData) {
71 - String date = MapUtil.getStr(row, "date"); 71 + String date = MapUtil.getStr(row, "statDate");
72 Integer orderType = MapUtil.getInt(row, "orderType"); 72 Integer orderType = MapUtil.getInt(row, "orderType");
73 Long count = MapUtil.getLong(row, "count", 0L); 73 Long count = MapUtil.getLong(row, "count", 0L);
74 dateMap.computeIfAbsent(date, key -> new HashMap<>()).put(orderType, count); 74 dateMap.computeIfAbsent(date, key -> new HashMap<>()).put(orderType, count);
yudao-module-wms/src/main/resources/mapper/home/WmsHomeStatisticsMapper.xml
@@ -52,9 +52,9 @@ @@ -52,9 +52,9 @@
52 52
53 <!-- 按天聚合单据数量 --> 53 <!-- 按天聚合单据数量 -->
54 <select id="selectDailyOrderTrend" resultType="cn.iocoder.yudao.framework.mybatis.core.util.CaseInsensitiveResultMap"> 54 <select id="selectDailyOrderTrend" resultType="cn.iocoder.yudao.framework.mybatis.core.util.CaseInsensitiveResultMap">
55 - SELECT date, orderType, SUM(count) AS count 55 + SELECT statDate, orderType, SUM(count) AS count
56 FROM ( 56 FROM (
57 - SELECT DATE_FORMAT(order_time, '%Y-%m-%d') AS date, 57 + SELECT DATE_FORMAT(order_time, '%Y-%m-%d') AS statDate,
58 ${@cn.iocoder.yudao.module.wms.enums.order.WmsOrderTypeEnum@RECEIPT.type} AS orderType, 58 ${@cn.iocoder.yudao.module.wms.enums.order.WmsOrderTypeEnum@RECEIPT.type} AS orderType,
59 COUNT(*) AS count 59 COUNT(*) AS count
60 FROM wms_receipt_order 60 FROM wms_receipt_order
@@ -66,7 +66,7 @@ @@ -66,7 +66,7 @@
66 </if> 66 </if>
67 GROUP BY DATE_FORMAT(order_time, '%Y-%m-%d') 67 GROUP BY DATE_FORMAT(order_time, '%Y-%m-%d')
68 UNION ALL 68 UNION ALL
69 - SELECT DATE_FORMAT(order_time, '%Y-%m-%d') AS date, 69 + SELECT DATE_FORMAT(order_time, '%Y-%m-%d') AS statDate,
70 ${@cn.iocoder.yudao.module.wms.enums.order.WmsOrderTypeEnum@SHIPMENT.type} AS orderType, 70 ${@cn.iocoder.yudao.module.wms.enums.order.WmsOrderTypeEnum@SHIPMENT.type} AS orderType,
71 COUNT(*) AS count 71 COUNT(*) AS count
72 FROM wms_shipment_order 72 FROM wms_shipment_order
@@ -78,7 +78,7 @@ @@ -78,7 +78,7 @@
78 </if> 78 </if>
79 GROUP BY DATE_FORMAT(order_time, '%Y-%m-%d') 79 GROUP BY DATE_FORMAT(order_time, '%Y-%m-%d')
80 UNION ALL 80 UNION ALL
81 - SELECT DATE_FORMAT(order_time, '%Y-%m-%d') AS date, 81 + SELECT DATE_FORMAT(order_time, '%Y-%m-%d') AS statDate,
82 ${@cn.iocoder.yudao.module.wms.enums.order.WmsOrderTypeEnum@MOVEMENT.type} AS orderType, 82 ${@cn.iocoder.yudao.module.wms.enums.order.WmsOrderTypeEnum@MOVEMENT.type} AS orderType,
83 COUNT(*) AS count 83 COUNT(*) AS count
84 FROM wms_movement_order 84 FROM wms_movement_order
@@ -90,7 +90,7 @@ @@ -90,7 +90,7 @@
90 </if> 90 </if>
91 GROUP BY DATE_FORMAT(order_time, '%Y-%m-%d') 91 GROUP BY DATE_FORMAT(order_time, '%Y-%m-%d')
92 UNION ALL 92 UNION ALL
93 - SELECT DATE_FORMAT(order_time, '%Y-%m-%d') AS date, 93 + SELECT DATE_FORMAT(order_time, '%Y-%m-%d') AS statDate,
94 ${@cn.iocoder.yudao.module.wms.enums.order.WmsOrderTypeEnum@CHECK.type} AS orderType, 94 ${@cn.iocoder.yudao.module.wms.enums.order.WmsOrderTypeEnum@CHECK.type} AS orderType,
95 COUNT(*) AS count 95 COUNT(*) AS count
96 FROM wms_check_order 96 FROM wms_check_order
@@ -102,13 +102,13 @@ @@ -102,13 +102,13 @@
102 </if> 102 </if>
103 GROUP BY DATE_FORMAT(order_time, '%Y-%m-%d') 103 GROUP BY DATE_FORMAT(order_time, '%Y-%m-%d')
104 ) t 104 ) t
105 - GROUP BY date, orderType  
106 - ORDER BY date, orderType 105 + GROUP BY statDate, orderType
  106 + ORDER BY statDate, orderType
107 </select> 107 </select>
108 108
109 <!-- 统计库存总数量 --> 109 <!-- 统计库存总数量 -->
110 <select id="selectInventoryTotalQuantity" resultType="java.math.BigDecimal"> 110 <select id="selectInventoryTotalQuantity" resultType="java.math.BigDecimal">
111 - SELECT IFNULL(SUM(quantity), 0) 111 + SELECT COALESCE(SUM(quantity), 0)
112 FROM wms_inventory 112 FROM wms_inventory
113 WHERE deleted = 0 113 WHERE deleted = 0
114 <if test="warehouseId != null"> 114 <if test="warehouseId != null">
@@ -120,7 +120,7 @@ @@ -120,7 +120,7 @@
120 <select id="selectInventoryItemRank" resultType="cn.iocoder.yudao.framework.mybatis.core.util.CaseInsensitiveResultMap"> 120 <select id="selectInventoryItemRank" resultType="cn.iocoder.yudao.framework.mybatis.core.util.CaseInsensitiveResultMap">
121 SELECT item.id AS id, 121 SELECT item.id AS id,
122 item.name AS name, 122 item.name AS name,
123 - IFNULL(SUM(inventory.quantity), 0) AS quantity 123 + COALESCE(SUM(inventory.quantity), 0) AS quantity
124 FROM wms_inventory inventory 124 FROM wms_inventory inventory
125 INNER JOIN wms_item_sku sku ON sku.id = inventory.sku_id AND sku.deleted = 0 125 INNER JOIN wms_item_sku sku ON sku.id = inventory.sku_id AND sku.deleted = 0
126 INNER JOIN wms_item item ON item.id = sku.item_id AND item.deleted = 0 126 INNER JOIN wms_item item ON item.id = sku.item_id AND item.deleted = 0
@@ -138,7 +138,7 @@ @@ -138,7 +138,7 @@
138 <select id="selectInventoryWarehouseRank" resultType="cn.iocoder.yudao.framework.mybatis.core.util.CaseInsensitiveResultMap"> 138 <select id="selectInventoryWarehouseRank" resultType="cn.iocoder.yudao.framework.mybatis.core.util.CaseInsensitiveResultMap">
139 SELECT warehouse.id AS id, 139 SELECT warehouse.id AS id,
140 warehouse.name AS name, 140 warehouse.name AS name,
141 - IFNULL(SUM(inventory.quantity), 0) AS quantity 141 + COALESCE(SUM(inventory.quantity), 0) AS quantity
142 FROM wms_inventory inventory 142 FROM wms_inventory inventory
143 INNER JOIN wms_warehouse warehouse ON warehouse.id = inventory.warehouse_id AND warehouse.deleted = 0 143 INNER JOIN wms_warehouse warehouse ON warehouse.id = inventory.warehouse_id AND warehouse.deleted = 0
144 WHERE inventory.deleted = 0 144 WHERE inventory.deleted = 0