fuyuchuan 3 недель назад
Родитель
Сommit
ab2b882f74
69 измененных файлов с 7112 добавлено и 9 удалено
  1. 2 0
      pom.xml
  2. 3 3
      service-iot/service-iot-api/src/main/java/com/usky/iot/RemoteIotService.java
  3. 0 2
      service-iot/service-iot-api/src/main/java/com/usky/iot/RemoteIotTaskService.java
  4. 40 0
      service-iot/service-iot-api/src/main/java/com/usky/iot/factory/RemoteIotFactory.java
  5. 2 2
      service-iot/service-iot-biz/src/main/resources/logback.xml
  6. 20 0
      service-pm/pom.xml
  7. 27 0
      service-pm/service-pm-api/pom.xml
  8. 15 0
      service-pm/service-pm-api/src/main/java/com/usky/pm/RemotePmService.java
  9. 105 0
      service-pm/service-pm-api/src/main/java/com/usky/pm/domain/SysUserVO.java
  10. 2 2
      service-pm/service-pm-api/src/main/java/com/usky/pm/factory/RemotePmFactory.java
  11. 174 0
      service-pm/service-pm-biz/pom.xml
  12. 49 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/PmApplication.java
  13. 54 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/constant/dingTalkConstant.java
  14. 107 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/controller/MybatisGeneratorUtils.java
  15. 34 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/controller/api/PmTimedSendingApi.java
  16. 162 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/controller/web/PmProjectController.java
  17. 41 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/controller/web/PmReceiveController.java
  18. 116 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/controller/web/PmTimeConfController.java
  19. 175 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/controller/web/PmWorkContentController.java
  20. 88 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/controller/web/PmWorkReportController.java
  21. 81 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/domain/MceMbuser.java
  22. 122 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/domain/PmProject.java
  23. 82 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/domain/PmReceive.java
  24. 92 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/domain/PmTimeConf.java
  25. 94 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/domain/PmWorkContent.java
  26. 160 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/domain/PmWorkReport.java
  27. 18 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/mapper/MceMbuserMapper.java
  28. 18 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/mapper/PmProjectMapper.java
  29. 18 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/mapper/PmReceiveMapper.java
  30. 16 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/mapper/PmTimeConfMapper.java
  31. 41 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/mapper/PmWorkContentMapper.java
  32. 18 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/mapper/PmWorkReportMapper.java
  33. 15 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/mapper/SysUserMapper.java
  34. 125 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/service/PmProjectService.java
  35. 32 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/service/PmReceiveService.java
  36. 54 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/service/PmTimeConfService.java
  37. 108 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/service/PmWorkContentService.java
  38. 63 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/service/PmWorkReportService.java
  39. 346 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/service/config/DingTalkAndMessage.java
  40. 75 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/service/config/websocket/WebSocket.java
  41. 16 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/service/config/websocket/WebSocketConfig.java
  42. 693 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/service/impl/PmProjectServiceImpl.java
  43. 74 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/service/impl/PmReceiveServiceImpl.java
  44. 413 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/service/impl/PmTimeConfServiceImpl.java
  45. 1474 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/service/impl/PmWorkContentServiceImpl.java
  46. 783 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/service/impl/PmWorkReportServiceImpl.java
  47. 25 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/service/vo/PmProjectTotalWorkTimeVo.java
  48. 19 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/service/vo/PmProjectWorkTimeTwoVo.java
  49. 29 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/service/vo/PmProjectWorkTimeVo.java
  50. 27 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/service/vo/PmReportReadersVO.java
  51. 65 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/service/vo/PmSubmitCountResponseVO.java
  52. 19 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/service/vo/PmUsersProjectWorkTimeVO.java
  53. 44 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/service/vo/PmWorkHourStatisticRequestVO.java
  54. 26 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/service/vo/PmWorkReportSlideVO.java
  55. 18 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/service/vo/ProjectWorkTimeVO.java
  56. 31 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/service/vo/SendWeChatMessageRequestVO.java
  57. 18 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/service/vo/UserWorkTimeVO.java
  58. 31 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/service/vo/WorkHoursStatisticsVO.java
  59. 55 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/service/vo/WorkTimeExportTwoVO.java
  60. 61 0
      service-pm/service-pm-biz/src/main/java/com/usky/pm/service/vo/WorkTimeExportVO.java
  61. 25 0
      service-pm/service-pm-biz/src/main/resources/bootstrap.yml
  62. 108 0
      service-pm/service-pm-biz/src/main/resources/doc/index.adoc
  63. 94 0
      service-pm/service-pm-biz/src/main/resources/logback.xml
  64. 27 0
      service-pm/service-pm-biz/src/main/resources/mapper/pm/PmProjectMapper.xml
  65. 20 0
      service-pm/service-pm-biz/src/main/resources/mapper/pm/PmReceiveMapper.xml
  66. 22 0
      service-pm/service-pm-biz/src/main/resources/mapper/pm/PmTimeConfMapper.xml
  67. 57 0
      service-pm/service-pm-biz/src/main/resources/mapper/pm/PmWorkContentMapper.xml
  68. 29 0
      service-pm/service-pm-biz/src/main/resources/mapper/pm/PmWorkReportMapper.xml
  69. 15 0
      service-pm/service-pm-biz/src/main/resources/smart-doc.json

+ 2 - 0
pom.xml

@@ -93,6 +93,8 @@
 
     <module>service-ai</module>
 
+    <module>service-pm</module>
+
   </modules>
           
   

+ 3 - 3
service-iot/service-iot-api/src/main/java/com/usky/iot/RemotePmService.java → service-iot/service-iot-api/src/main/java/com/usky/iot/RemoteIotService.java

@@ -1,12 +1,12 @@
 package com.usky.iot;
 
 
-import com.usky.iot.factory.RemotePmFactory;
+import com.usky.iot.factory.RemoteIotFactory;
 import org.springframework.cloud.openfeign.FeignClient;
 import org.springframework.web.bind.annotation.GetMapping;
 
-@FeignClient(contextId = "remoteIotService", value = "service-iot" , fallbackFactory = RemotePmFactory.class)
-public interface RemotePmService {
+@FeignClient(contextId = "remoteIotService", value = "service-iot" , fallbackFactory = RemoteIotFactory.class)
+public interface RemoteIotService {
 
     @GetMapping("/executeTimedSending")
     void executeTimedSending();

+ 0 - 2
service-iot/service-iot-api/src/main/java/com/usky/iot/RemoteIotTaskService.java

@@ -2,8 +2,6 @@ package com.usky.iot;
 
 
 import com.usky.iot.factory.RemoteIotTaskFactory;
-import com.usky.iot.factory.RemotePmFactory;
-import org.apache.ibatis.annotations.Param;
 import org.springframework.cloud.openfeign.FeignClient;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestParam;

+ 40 - 0
service-iot/service-iot-api/src/main/java/com/usky/iot/factory/RemoteIotFactory.java

@@ -0,0 +1,40 @@
+package com.usky.iot.factory;
+
+import com.usky.common.core.exception.FeignBadRequestException;
+import com.usky.iot.RemoteIotService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.cloud.openfeign.FallbackFactory;
+import org.springframework.stereotype.Component;
+
+
+/**
+ * 用户服务降级处理
+ *
+ * @author ruoyi
+ */
+@Component
+public class RemoteIotFactory implements FallbackFactory<RemoteIotService>
+{
+    private static final Logger log = LoggerFactory.getLogger(RemoteIotFactory.class);
+
+    @Override
+    public RemoteIotService create(Throwable throwable)
+    {
+        return new RemoteIotService()
+        {
+            @Override
+            public void executeTimedSending() {
+                log.error("工作报告定时发送异常:{}", throwable.getMessage());
+                throw new FeignBadRequestException(500,"工作报告定时发送异常"+throwable.getMessage());
+            }
+
+            @Override
+            public void reportSubmissionReminder() {
+                log.error("工作报告提交提醒发送异常:{}", throwable.getMessage());
+                throw new FeignBadRequestException(500,"工作报告提交提醒发送异常"+throwable.getMessage());
+            }
+
+        };
+    }
+}

+ 2 - 2
service-iot/service-iot-biz/src/main/resources/logback.xml

@@ -1,13 +1,13 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <configuration scan="true" scanPeriod="60 seconds" debug="false">
     <!-- 日志存放路径 -->
-    <property name="log.path" value="/var/log/uskycloud/service-iot" />
+    <property name="log.path" value="/var/log/uskycloud/service-pm" />
     <!-- 日志输出格式 -->
     <property name="log.pattern" value="%d{MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{26}:%line: %msg%n" />
     <!--    	<property name="log.pattern" value="%gray(%d{MM-dd HH:mm:ss.SSS}) %highlight(%-5level) &#45;&#45; [%gray(%thread)] %cyan(%logger{26}:%line): %msg%n" />-->
 
 
-    <property name="SQL_PACKAGE" value="com.usky.iot.mapper"/>
+    <property name="SQL_PACKAGE" value="com.usky.pm.mapper"/>
 
     <!-- 控制台输出 -->
     <appender name="console" class="ch.qos.logback.core.ConsoleAppender">

+ 20 - 0
service-pm/pom.xml

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>usky-modules</artifactId>
+        <groupId>com.usky</groupId>
+        <version>0.0.1</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>service-pm</artifactId>
+
+    <packaging>pom</packaging>
+    <version>0.0.1</version>
+
+    <modules>
+        <module>service-pm-biz</module>
+        <module>service-pm-api</module>
+    </modules>
+</project>

+ 27 - 0
service-pm/service-pm-api/pom.xml

@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>service-pm</artifactId>
+        <groupId>com.usky</groupId>
+        <version>0.0.1</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>service-pm-api</artifactId>
+    <!-- SpringCloud Openfeign -->
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-openfeign</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.usky</groupId>
+            <artifactId>usky-common-core</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <finalName>${project.artifactId}</finalName>
+    </build>
+
+</project>

+ 15 - 0
service-pm/service-pm-api/src/main/java/com/usky/pm/RemotePmService.java

@@ -0,0 +1,15 @@
+package com.usky.pm;
+
+import com.usky.pm.factory.RemotePmFactory;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.GetMapping;
+
+@FeignClient(contextId = "remotePmService", value = "service-pm" , fallbackFactory = RemotePmFactory.class)
+public interface RemotePmService {
+
+    @GetMapping("/executeTimedSending")
+    void executeTimedSending();
+
+    @GetMapping("/reportSubmissionReminder")
+    void reportSubmissionReminder();
+}

+ 105 - 0
service-pm/service-pm-api/src/main/java/com/usky/pm/domain/SysUserVO.java

@@ -0,0 +1,105 @@
+package com.usky.pm.domain;
+
+
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+public class SysUserVO {
+
+    /**
+     * 用户ID
+     */
+    private Long userId;
+
+    /**
+     * 部门ID
+     */
+    private Long deptId;
+
+    /**
+     * 用户账号
+     */
+    private String userName;
+
+    /**
+     * 用户昵称
+     */
+    private String nickName;
+
+    /**
+     * 用户类型(00系统用户)
+     */
+    private String userType;
+
+    /**
+     * 用户邮箱
+     */
+    private String email;
+
+    /**
+     * 手机号码
+     */
+    private String phonenumber;
+
+    /**
+     * 用户性别(0男 1女 2未知)
+     */
+    private String sex;
+
+    /**
+     * 头像地址
+     */
+    private String avatar;
+
+    /**
+     * 密码
+     */
+    private String password;
+
+    /**
+     * 帐号状态(0正常 1停用)
+     */
+    private String status;
+
+    /**
+     * 删除标志(0代表存在 2代表删除)
+     */
+    private String delFlag;
+
+    /**
+     * 最后登录IP
+     */
+    private String loginIp;
+
+    /**
+     * 最后登录时间
+     */
+    private LocalDateTime loginDate;
+
+    /**
+     * 创建者
+     */
+    private String createBy;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 更新者
+     */
+    private String updateBy;
+
+    /**
+     * 更新时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 备注
+     */
+    private String remark;
+}

+ 2 - 2
service-iot/service-iot-api/src/main/java/com/usky/iot/factory/RemotePmFactory.java → service-pm/service-pm-api/src/main/java/com/usky/pm/factory/RemotePmFactory.java

@@ -1,7 +1,7 @@
-package com.usky.iot.factory;
+package com.usky.pm.factory;
 
 import com.usky.common.core.exception.FeignBadRequestException;
-import com.usky.iot.RemotePmService;
+import com.usky.pm.RemotePmService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.cloud.openfeign.FallbackFactory;

+ 174 - 0
service-pm/service-pm-biz/pom.xml

@@ -0,0 +1,174 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>service-pm</artifactId>
+        <groupId>com.usky</groupId>
+        <version>0.0.1</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>service-pm-biz</artifactId>
+    <dependencies>
+        <dependency>
+            <groupId>com.usky</groupId>
+            <artifactId>common-cloud-starter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.usky</groupId>
+            <artifactId>service-backend-api</artifactId>
+            <version>0.0.1</version>
+        </dependency>
+        <dependency>
+            <groupId>com.usky</groupId>
+            <artifactId>data-tsdb-proxy-api</artifactId>
+            <version>0.0.1</version>
+        </dependency>
+        <dependency>
+            <groupId>com.usky</groupId>
+            <artifactId>data-transfer-api</artifactId>
+            <version>0.0.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.usky</groupId>
+            <artifactId>service-pm-api</artifactId>
+            <version>0.0.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.afterturn</groupId>
+            <artifactId>easypoi-spring-boot-starter</artifactId>
+            <version>4.1.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>aliyun-java-sdk-core</artifactId>
+            <version>4.5.16</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>aliyun-java-sdk-dysmsapi</artifactId>
+            <version>1.1.0</version>
+        </dependency>
+
+        <!--MQTT依赖-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-integration</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.integration</groupId>
+            <artifactId>spring-integration-mqtt</artifactId>
+        </dependency>
+        <!--websocket依赖-->
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-websocket</artifactId>
+            <version>5.2.8.RELEASE</version>
+        </dependency>
+        <dependency>
+            <groupId>com.usky</groupId>
+            <artifactId>service-agbox-api</artifactId>
+            <version>0.0.1</version>
+            <scope>compile</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>com.usky</groupId>
+            <artifactId>service-system-api</artifactId>
+            <version>0.0.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.binarywang</groupId>
+            <artifactId>weixin-java-mp</artifactId>
+            <version>4.3.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-amqp</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.usky</groupId>
+            <artifactId>service-alarm-api</artifactId>
+            <version>0.0.1</version>
+            <scope>compile</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>com.usky</groupId>
+            <artifactId>ruoyi-common-core</artifactId>
+        </dependency>
+
+        <!--钉钉-->
+        <!--获取企业accessToken(企业内部应用) 新版SDK-->
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>dingtalk</artifactId>
+            <version>2.1.34</version>
+        </dependency>
+        <!--旧版SDK-->
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>alibaba-dingtalk-service-sdk</artifactId>
+            <version>2.0.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.usky</groupId>
+            <artifactId>service-pm</artifactId>
+            <version>0.0.1</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.usky</groupId>
+            <artifactId>service-pm-api</artifactId>
+            <version>0.0.1</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.usky</groupId>
+            <artifactId>service-pm-api</artifactId>
+            <version>0.0.1</version>
+            <scope>compile</scope>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <finalName>${project.artifactId}</finalName>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>2.2.6.RELEASE</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>com.github.shalousun</groupId>
+                <artifactId>smart-doc-maven-plugin</artifactId>
+                <version>2.1.1</version>
+                <configuration>
+                    <!--指定生成文档的使用的配置文件,配置文件放在自己的项目中-->
+                    <configFile>./src/main/resources/smart-doc.json</configFile>
+                    <!--指定项目名称-->
+                    <projectName>test</projectName>
+                    <!--                    <excludes>-->
+                    <!--                        <exclude>com.bizmatics:product-service-provider</exclude>-->
+                    <!--                        <exclude>cn.afterturn:easypoi-web</exclude>-->
+                    <!--                    </excludes>-->
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 49 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/PmApplication.java

@@ -0,0 +1,49 @@
+package com.usky.pm;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.core.env.Environment;
+import org.springframework.scheduling.annotation.EnableAsync;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * 系统模块
+ * 
+ * @author ruoyi
+ */
+
+//@EnableSwagger2
+@EnableFeignClients(basePackages = "com.usky")
+@MapperScan(value = "com.usky.pm.mapper")
+@ComponentScan("com.usky")
+@SpringBootApplication
+//开启异步
+@EnableAsync
+//@EnableRabbit
+public class PmApplication
+{
+    private static final Logger LOGGER = LoggerFactory.getLogger(PmApplication.class);
+
+    public static void main(String[] args) throws UnknownHostException {
+        ConfigurableApplicationContext application = SpringApplication.run(PmApplication.class, args);
+        Environment env = application.getEnvironment();
+        String ip = InetAddress.getLocalHost().getHostAddress();
+        String port = env.getProperty("server.port");
+        String path = env.getProperty("server.servlet.context-path");
+        LOGGER.info("\n----------------------------------------------------------\n\t" +
+                "Application is running! Access URLs:\n\t" +
+                "Local: \t\thttp://localhost:" + port + (null==path?"":path) + "/\n\t" +
+                "External: \thttp://" + ip + ":" + port + (null==path?"":path) + "/\n\t" +
+                "Api: \t\thttp://" + ip + ":" + port + (null==path?"":path) + "/swagger-ui/index.html\n\t" +
+                "----------------------------------------------------------");
+    }
+}

+ 54 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/constant/dingTalkConstant.java

@@ -0,0 +1,54 @@
+package com.usky.pm.constant;
+
+public class dingTalkConstant {
+
+    // 钉钉组织id CorpId
+    public static final String DING_TALK_CORP_ID = "dingefbc6eb33c7f3d80";
+
+    // App Id(应用id)
+    public static final String DING_TALK_APP_ID = "e9908c06-c9cf-49a9-b8ae-a80c4a298782";
+
+    // 原企业内部应用AgentId
+    public static final String DING_TALK_AGENT_ID = "3130849582";
+
+    // Client ID (原 AppKey 和 SuiteKey)
+    public static final String DING_TALK_CLIENT_ID = "dingbrwslwgx0bykrjbc";
+
+    // Client Secret (原 AppSecret 和 SuiteSecret)
+    public static final String DING_TALK_CLIENT_SECRET = "G77mhy4tOJJxoBctvmRUIFpGphV39h8dv9hlVI9kJHrq3sLIDBzggwi22mTpLjpa";
+
+    // 企业内部 access_token 的获取URL(2个参数:Client ID 和 Client Secret)
+    public static final String DING_TALK_ACCESS_TOKEN_URL = "https://oapi.dingtalk.com/gettoken";
+
+    // 用户 userId 获取URL      根据手机号获取(2个参数:手机号 mobile ,access_token)
+    public static final String DING_TALK_USERID_URL = "https://oapi.dingtalk.com/topapi/v2/user/getbymobile";
+
+    // 日报模板id
+    public static final String DING_TALK_DAILY_REPORT_TEMPLATE_ID = "153cb1ea26eee62842e14fa40fa92b95";
+
+    // 钉钉日报模板名
+    public static final String DING_TALK_DAILY_REPORT_TEMPLATE_NAME = "日报";
+
+    // 日报是否发送聊天消息到抄送人
+    public static final Boolean DING_TALK_DAILY_REPORT_TO_CHAT = false;
+
+    // 日报内容类型
+    public static final String DING_TALK_DAILY_REPORT_CONTENT_TYPE = "markdown";
+
+    //日报内容key 今日完成工作
+    public static final String DING_TALK_DAILY_REPORT_COMPLETED_WORK = "今日完成工作";
+
+    //日报内容key 明日工作计划
+    public static final String DING_TALK_DAILY_REPORT_TOMORROW_PLAN = "明日工作计划";
+
+    //日报内容key 需协调工作
+    public static final String DING_TALK_DAILY_REPORT_COORDINATE_WORK = "需协调工作";
+
+    // 日志发送到的群id
+    public static final String DING_TALK_DAILY_REPORT_TO_CIDS = "";
+
+    // 钉钉日报发送地址
+    public static final String DING_TALK_CREATE_DAILY_REPORT_URL = "https://oapi.dingtalk.com/topapi/report/create";
+
+
+}

+ 107 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/controller/MybatisGeneratorUtils.java

@@ -0,0 +1,107 @@
+package com.usky.pm.controller;
+
+import com.baomidou.mybatisplus.core.toolkit.StringPool;
+import com.baomidou.mybatisplus.generator.AutoGenerator;
+import com.baomidou.mybatisplus.generator.InjectionConfig;
+import com.baomidou.mybatisplus.generator.config.*;
+import com.baomidou.mybatisplus.generator.config.po.TableInfo;
+import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author yq
+ * @date 2021/7/6 11:42
+ */
+public class MybatisGeneratorUtils {
+    public static void main(String[] args) {
+
+            shell("service-pm","service-pm-biz");
+    }
+
+    private static void shell(String parentName,String model) {
+
+        AutoGenerator mpg = new AutoGenerator();
+        //1、全局配置
+        GlobalConfig gc = new GlobalConfig();
+//        File file = new File(model);
+//        String path = file.getAbsolutePath();
+        String projectPath = System.getProperty("user.dir");
+        projectPath+="/"+parentName;
+        projectPath+="/"+model;
+        gc.setOutputDir(projectPath+ "/src/main/java");  //生成路径(一般都是生成在此项目的src/main/java下面)
+        //修改为自己的名字
+        gc.setAuthor("fu"); //设置作者
+        gc.setOpen(false);
+        gc.setFileOverride(true); //第二次生成会把第一次生成的覆盖掉
+        gc.setServiceName("%sService"); //生成的service接口名字首字母是否为I,这样设置就没有
+        gc.setBaseResultMap(true); //生成resultMap
+        mpg.setGlobalConfig(gc);
+
+        //2、数据源配置
+        //修改数据源
+        DataSourceConfig dsc = new DataSourceConfig();
+        dsc.setUrl("jdbc:mysql://192.168.10.165:3306/usky-cloud?useUnicode=true&serverTimezone=GMT&useSSL=false&characterEncoding=utf8");
+        dsc.setDriverName("com.mysql.jdbc.Driver");
+        dsc.setUsername("root");
+        dsc.setPassword("yt123456");
+        mpg.setDataSource(dsc);
+
+        // 3、包配置
+        PackageConfig pc = new PackageConfig();
+        pc.setParent("com.usky.pm");
+        pc.setController("controller.web");
+        pc.setEntity("domain");
+        pc.setMapper("mapper");
+        pc.setService("service");
+        pc.setServiceImpl("service.impl");
+//        pc.setXml("mapper.demo");
+        //pc.setModuleName("test");
+        mpg.setPackageInfo(pc);
+
+        // 4、策略配置
+        StrategyConfig strategy = new StrategyConfig();
+        strategy.setNaming(NamingStrategy.underline_to_camel);
+        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
+        strategy.setSuperMapperClass("com.usky.common.mybatis.core.CrudMapper");
+        strategy.setSuperServiceClass("com.usky.common.mybatis.core.CrudService");
+        strategy.setSuperServiceImplClass("com.usky.common.mybatis.core.AbstractCrudService");
+        // strategy.setTablePrefix("t_"); // 表名前缀
+        strategy.setEntityLombokModel(true); //使用lombok
+        //修改自己想要生成的表
+        strategy.setInclude("pm_time_conf");  // 逆向工程使用的表   如果要生成多个,这里可以传入String[]
+        mpg.setStrategy(strategy);
+
+        // 关闭默认 xml 生成,调整生成 至 根目录
+        //修改对应的模块名称
+        TemplateConfig tc = new TemplateConfig();
+        // 自定义配置
+        InjectionConfig cfg = new InjectionConfig() {
+            @Override
+            public void initMap() {
+                // to do nothing
+            }
+        };
+        //如果模板引擎是 velocity
+        String templatePath = "/templates/mapper.xml.vm";
+        // 自定义输出配置
+        List<FileOutConfig> focList = new ArrayList<>();
+        // 自定义配置会被优先输出
+        String finalProjectPath = projectPath;
+        focList.add(new FileOutConfig(templatePath) {
+            @Override
+            public String outputFile(TableInfo tableInfo) {
+                // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
+                return finalProjectPath + "/src/main/resources/mapper/pm" + "/"
+                        + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
+            }
+        });
+        cfg.setFileOutConfigList(focList);
+        mpg.setCfg(cfg);
+        tc.setXml(null);
+        mpg.setTemplate(tc);
+        //5、执行
+        mpg.execute();
+    }
+}

+ 34 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/controller/api/PmTimedSendingApi.java

@@ -0,0 +1,34 @@
+package com.usky.pm.controller.api;
+
+import com.usky.pm.RemotePmService;
+import com.usky.pm.service.PmWorkReportService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.time.LocalDateTime;
+
+/**
+ * <p>
+ *  数据统一查询 前端控制器
+ * </p>
+ *
+ * @author f
+ */
+@RestController
+public class PmTimedSendingApi implements RemotePmService {
+
+    @Autowired
+    private PmWorkReportService pmWorkReportService;
+
+    @Override
+    public void executeTimedSending() {
+        LocalDateTime now = LocalDateTime.now();
+        pmWorkReportService.timedSending(now);
+    }
+
+    @Override
+    public void reportSubmissionReminder() {
+        pmWorkReportService.reportSubmissionReminder();
+    }
+
+}

+ 162 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/controller/web/PmProjectController.java

@@ -0,0 +1,162 @@
+package com.usky.pm.controller.web;
+
+
+import com.usky.common.core.bean.ApiResult;
+import com.usky.common.core.bean.CommonPage;
+import com.usky.common.log.annotation.Log;
+import com.usky.common.log.enums.BusinessType;
+import com.usky.pm.domain.PmProject;
+import com.usky.pm.service.PmProjectService;
+import com.usky.pm.service.vo.PmProjectWorkTimeTwoVo;
+import com.usky.pm.service.vo.PmUsersProjectWorkTimeVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * <p>
+ * 项目表 前端控制器
+ * </p>
+ *
+ * @author fu
+ * @since 2024-05-20
+ */
+@RestController
+@RequestMapping("/pmProject")
+public class PmProjectController {
+    @Autowired
+    private PmProjectService pmProjectService;
+
+    /**
+     * 查询项目名(下拉框)
+     *
+     * @return
+     */
+    @GetMapping("/projects")
+    public ApiResult<List<PmProject>> queryProjectName() {
+        return ApiResult.success(pmProjectService.queryProject());
+    }
+
+    /**
+     * 项目列表分页
+     *
+     * @param projectName
+     * @param projectType
+     * @param projectStatus
+     * @param projectAscription
+     * @param pageNum
+     * @param pageSize
+     * @return
+     */
+    @GetMapping("/page")
+    public ApiResult<CommonPage<PmProject>> pageList(@RequestParam(value = "projectName", required = false) String projectName,
+                                                     @RequestParam(value = "projectType", required = false) Integer projectType,
+                                                     @RequestParam(value = "projectStatus", required = false) Integer projectStatus,
+                                                     @RequestParam(value = "projectAscription", required = false, defaultValue = "0") Integer projectAscription,
+                                                     @RequestParam(value = "pageNum", required = false, defaultValue = "1") Integer pageNum,
+                                                     @RequestParam(value = "pageSize", required = false, defaultValue = "10") Integer pageSize,
+                                                     @RequestParam(value = "projectId", required = false) Integer projectId,
+                                                     @RequestParam(value = "visibleRange", required = false) Byte visibleRange) {
+        return ApiResult.success(pmProjectService.projectList(projectName, projectType, projectStatus, projectAscription, pageNum, pageSize, projectId, visibleRange));
+    }
+
+    /**
+     * 统计
+     *
+     * @return
+     */
+    @GetMapping("/counting")
+    public ApiResult<Map<String, Integer>> count() {
+        return ApiResult.success(pmProjectService.sum());
+    }
+
+    /**
+     * 新增、编辑项目
+     *
+     * @param pmProject
+     */
+    @Log(title = "新增/编辑项目", businessType = BusinessType.INSERT)
+    @PostMapping("/add")
+    public void add(@RequestBody PmProject pmProject) {
+        pmProjectService.addProject(pmProject);
+    }
+
+    /**
+     * 删除
+     *
+     * @param projectId 项目id
+     */
+    @Log(title = "删除项目", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{projectId}")
+    public void del(@RequestParam Integer projectId) {
+        pmProjectService.delProject(projectId);
+    }
+
+    //回退代码
+    @Log(title = "删除项目", businessType = BusinessType.DELETE)
+    @GetMapping("/del")
+    public void del2(@RequestParam Integer projectId) {
+        pmProjectService.delProject(projectId);
+    }
+
+    /**
+     * 人员、项目下拉
+     *
+     * @param identifying
+     * @return
+     */
+    @GetMapping("/userOrProject")
+    public ApiResult<List<Map<String, Object>>> userOrProject(@RequestParam(value = "identifying", required = false) Integer identifying) {
+        return ApiResult.success(pmProjectService.userOrProject(identifying));
+    }
+
+    /**
+     * 退出项目
+     *
+     * @param projectId 项目id
+     */
+    @Log(title = "退出项目", businessType = BusinessType.UPDATE)
+    @GetMapping("/exitProject")
+    public void exitProject(@RequestParam Integer projectId) {
+        pmProjectService.exitProject(projectId);
+    }
+
+    /**
+     * 项目工时统计
+     *
+     * @param projectId
+     * @param startDate
+     * @param endDate
+     * @return
+     */
+    @GetMapping("/projectWorkTime")
+    public ApiResult<PmProjectWorkTimeTwoVo> projectIdName(@RequestParam(value = "projectId") Integer projectId,
+                                                               @RequestParam(value = "startDate", required = false) String startDate,
+                                                               @RequestParam(value = "endDate", required = false) String endDate) {
+        return ApiResult.success(pmProjectService.projectWorkTime(projectId, startDate, endDate));
+    }
+
+    /**
+     * 项目工时统计(人员)
+     *
+     * @param projectId
+     * @return
+     */
+    @GetMapping("/usersProjectWorkTime")
+    public ApiResult<PmUsersProjectWorkTimeVO> usersProjectWorkTime(@RequestParam(value = "projectId") Integer projectId,
+                                                                    @RequestParam(value = "startDate", required = false) String startDate,
+                                                                    @RequestParam(value = "endDate", required = false) String endDate) {
+        return ApiResult.success(pmProjectService.usersProjectWorkTime(projectId, startDate, endDate));
+    }
+
+    // 加入项目
+    @Log(title = "加入项目", businessType = BusinessType.UPDATE)
+    @GetMapping("/joinProject")
+    public void projectIdName(@RequestParam(value = "projectId") Integer projectId) {
+        pmProjectService.joinProject(projectId);
+    }
+
+}
+

+ 41 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/controller/web/PmReceiveController.java

@@ -0,0 +1,41 @@
+package com.usky.pm.controller.web;
+
+
+import com.usky.pm.service.PmReceiveService;
+import com.usky.system.domain.SysUser;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 报告接收表 前端控制器
+ * </p>
+ *
+ * @author han
+ * @since 2024-07-09
+ */
+@RestController
+@RequestMapping("/pmReceive")
+public class PmReceiveController {
+
+    @Autowired
+    private PmReceiveService pmReceiveService;
+
+    @GetMapping("/updateReadFlag")
+    public void updateReadFlag(Integer reportId){
+        pmReceiveService.updateReadFlag(reportId);
+    }
+
+    @GetMapping("/deptTest")
+    public List<SysUser> deptTest(Integer tenantId){
+        return pmReceiveService.test(tenantId);
+    }
+
+
+}
+

+ 116 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/controller/web/PmTimeConfController.java

@@ -0,0 +1,116 @@
+package com.usky.pm.controller.web;
+
+
+import com.usky.common.core.bean.CommonPage;
+import com.usky.common.log.annotation.Log;
+import com.usky.common.log.enums.BusinessType;
+import com.usky.common.security.utils.SecurityUtils;
+import com.usky.pm.domain.PmTimeConf;
+import com.usky.pm.service.PmTimeConfService;
+import com.usky.pm.service.vo.PmSubmitCountResponseVO;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+
+/**
+ * <p>
+ * 工作报告提交时间配置表 前端控制器
+ * </p>
+ *
+ * @author fu
+ * @since 2025-01-10
+ */
+@RestController
+@RequestMapping("/pmTimeConf")
+public class PmTimeConfController {
+
+    @Autowired
+    private PmTimeConfService pmTimeConfService;
+
+    /**
+     * 提交统计
+     * @param submitDate 统计日期
+     * @return 统计结果
+     */
+    @GetMapping("/submitCount")
+    public PmSubmitCountResponseVO submitCount(@RequestParam(value = "submitDate", required = false) String submitDate) {
+        if (submitDate == null) {
+            LocalDateTime now = LocalDateTime.now();
+            LocalDateTime today1800 = LocalDateTime.of(now.toLocalDate(), LocalTime.of(18, 0));
+            if (now.isBefore(today1800)) {
+                submitDate = now.toLocalDate().minusDays(1).toString();
+            } else {
+                submitDate = now.toLocalDate().toString();
+            }
+        }
+        return pmTimeConfService.submitCount(submitDate);
+    }
+
+    /**
+     * 提交记录分页
+     * @param queryType 查询类型(0:按时提交,1:迟交,2:未提交)
+     * @param submitDate 提交日期
+     * @param reportId 报告id
+     * @param pageNum 页码
+     * @param pageSize 页大小
+     * @return 分页结果
+     */
+    @GetMapping("/submitPage")
+    public CommonPage<Object> submitPage(@RequestParam(value = "queryType", required = false, defaultValue = "0") Integer queryType,
+                                         @RequestParam(value = "submitDate", required = false) String submitDate,
+                                         @RequestParam(value = "reportId", required = false) Integer reportId,
+                                         @RequestParam(value = "pageNum", required = false, defaultValue = "1") Integer pageNum,
+                                         @RequestParam(value = "pageSize", required = false, defaultValue = "10") Integer pageSize) {
+        if (StringUtils.isBlank(submitDate)) {
+            submitDate = LocalDate.now().toString();
+        }
+        return pmTimeConfService.submitPage(submitDate, queryType, reportId, pageNum, pageSize);
+    }
+
+    /**
+     * 获取配置时间
+     * @return 配置时间
+     */
+    @GetMapping("/timeQuery")
+    public PmTimeConf timeQuery() {
+        Integer tenantId = SecurityUtils.getTenantId();
+        return pmTimeConfService.getTimeConf(tenantId);
+    }
+
+    /**
+     * 添加配置时间
+     * @param pmTimeConf 配置时间
+     */
+    @Log(title = "新增配置时间", businessType = BusinessType.INSERT)
+    @PostMapping("/addTimeConf")
+    public void addTimeConf(@RequestBody PmTimeConf pmTimeConf) {
+        pmTimeConfService.addOrUpdateTimeConf(pmTimeConf);
+    }
+
+    /**
+     * 修改配置时间
+     * @param pmTimeConf 配置时间
+     */
+    @Log(title = "修改配置时间", businessType = BusinessType.UPDATE)
+    @PutMapping("/updateTimeConf")
+    public void updateTimeConf(@RequestBody PmTimeConf pmTimeConf) {
+        pmTimeConfService.addOrUpdateTimeConf(pmTimeConf);
+    }
+
+    /**
+     * 打开或关闭提醒
+     * @param isOpen 是否开启 0:关闭 1:开启
+     * @param id 配置id
+     */
+    @PutMapping("/isOpen")
+    public void isOpen(@RequestParam(value = "isOpen") Integer isOpen,
+                       @RequestParam(value = "id") Integer id) {
+        pmTimeConfService.isOpen(isOpen, id);
+    }
+
+}
+

+ 175 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/controller/web/PmWorkContentController.java

@@ -0,0 +1,175 @@
+package com.usky.pm.controller.web;
+
+
+import com.ruoyi.common.core.utils.poi.ExcelUtil;
+import com.usky.common.core.bean.ApiResult;
+import com.usky.common.core.bean.CommonPage;
+import com.usky.common.log.annotation.Log;
+import com.usky.common.log.enums.BusinessType;
+import com.usky.pm.domain.PmWorkContent;
+import com.usky.pm.domain.PmWorkReport;
+import com.usky.pm.service.PmWorkContentService;
+import com.usky.pm.service.vo.PmWorkHourStatisticRequestVO;
+import com.usky.pm.service.vo.PmWorkReportSlideVO;
+import com.usky.pm.service.vo.WorkTimeExportTwoVO;
+import com.usky.pm.service.vo.WorkTimeExportVO;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.temporal.TemporalAdjusters;
+import java.util.List;
+
+/**
+ * <p>
+ * 工作内容表 前端控制器
+ * </p>
+ *
+ * @author fu
+ * @since 2024-05-20
+ */
+@RestController
+@RequestMapping("/pmWorkContent")
+public class PmWorkContentController {
+    @Autowired
+    private PmWorkContentService pmWorkContentService;
+
+    /**
+     * 项目名
+     *
+     * @param startDate
+     * @param endDate
+     * @param projectName
+     * @return
+     */
+    @GetMapping("/projectName")
+    public ApiResult<List<PmWorkContent>> pageList(@RequestParam(value = "startDate", required = false) String startDate,
+                                                   @RequestParam(value = "endDate", required = false) String endDate,
+                                                   @RequestParam(value = "projectName", required = false) String projectName,
+                                                   @RequestParam(value = "projectAscription", required = false, defaultValue = "1") Integer projectAscription,
+                                                   @RequestParam(value = "submitterId", required = false) Long submitterId) {
+        return ApiResult.success(pmWorkContentService.projectQuery(startDate, endDate, projectName, projectAscription, submitterId));
+    }
+
+    /**
+     * 报告记录
+     *
+     * @param startDate
+     * @param endDate
+     * @param projectName
+     * @param reportId
+     * @param projectAscription
+     * @return
+     */
+    @GetMapping("/reportRecord")
+    public ApiResult<List<PmWorkReport>> reportRecord(@RequestParam(value = "startDate", required = false) String startDate,
+                                                      @RequestParam(value = "endDate", required = false) String endDate,
+                                                      @RequestParam(value = "projectName", required = false) String projectName,
+                                                      @RequestParam(value = "reportId", required = false) Integer reportId,
+                                                      @RequestParam(value = "projectAscription", required = false, defaultValue = "1") Integer projectAscription) {
+        if (StringUtils.isBlank(startDate) || StringUtils.isBlank(endDate)) {
+            startDate = LocalDate.now().toString();
+            endDate = LocalDate.now().toString();
+        }
+        return ApiResult.success(pmWorkContentService.workReportQuery(startDate, endDate, projectName, projectAscription, reportId));
+    }
+
+    /**
+     * 报告详情分页列表
+     *
+     * @param projectAscription 1:我负责;2:我收到;3:我发出
+     * @param pageNum           页数
+     * @param pageSize          页大小
+     * @return
+     */
+    @GetMapping("/page")
+    public ApiResult<CommonPage<PmWorkReport>> page(@RequestParam(value = "projectAscription", required = false, defaultValue = "-1") Integer projectAscription,
+                                                    @RequestParam(value = "pageNum", required = false, defaultValue = "1") Integer pageNum,
+                                                    @RequestParam(value = "pageSize", required = false, defaultValue = "10") Integer pageSize,
+                                                    @RequestParam(value = "reportId", required = false) Integer reportId,
+                                                    @RequestParam(value = "startDate", required = false) String startDate,
+                                                    @RequestParam(value = "endDate", required = false) String endDate,
+                                                    @RequestParam(value = "projectId", required = false) Integer projectId,
+                                                    @RequestParam(value = "submitterId", required = false) Long submitterId,
+                                                    @RequestParam(value = "upOrDown", required = false) Integer upOrDown,
+                                                    @RequestParam(value = "slideSum", required = false) Integer slideSum,
+                                                    @RequestParam(value = "submitDate", required = false) String submitDate) {
+        return ApiResult.success(pmWorkContentService.reportPage(projectAscription, pageNum, pageSize, reportId, startDate, endDate, projectId, submitterId, upOrDown, slideSum, submitDate));
+    }
+
+    /**
+     * 上下滑动
+     * @param upOrDown 0:上滑;1:下滑
+     * @param slideSum 滑动数量
+     * @param submitDate 提交时间
+     * @return
+     */
+    @GetMapping("/slide")
+    public ApiResult<PmWorkReportSlideVO> slide(@RequestParam(value = "upOrDown", required = false) Integer upOrDown,
+                                                @RequestParam(value = "slideSum", required = false) Integer slideSum,
+                                                @RequestParam(value = "submitDate") LocalDateTime submitDate){
+        return ApiResult.success(pmWorkContentService.slide(upOrDown, slideSum, submitDate));
+    }
+
+    /**
+     * 工时统计-新
+     * @return
+     */
+    @PostMapping("/workHourStatisticNew")
+    public ApiResult<List<Object>> workHourStatistic(@RequestBody PmWorkHourStatisticRequestVO requestVO) {
+        return ApiResult.success(pmWorkContentService.workHourStatisticNew(requestVO));
+    }
+
+    //回退代码
+    @GetMapping("/workHourStatistic")
+    public ApiResult<List<Object>> workHourStatistic(@RequestParam(value = "userId", required = false) Long userId,
+                                                     @RequestParam(value = "projectId", required = false) Integer projectId,
+                                                     @RequestParam(value = "filter", required = false, defaultValue = "1") Integer filter,
+                                                     @RequestParam(value = "startDate", required = false) String startDate,
+                                                     @RequestParam(value = "endDate", required = false) String endDate,
+                                                     @RequestParam(value = "workerOrProject", required = false, defaultValue = "1") Integer workerOrProject) {
+        if (StringUtils.isBlank(startDate) || StringUtils.isBlank(endDate)) {
+            startDate = LocalDate.now().with(TemporalAdjusters.firstDayOfYear()).toString();
+            endDate = LocalDate.now().with(TemporalAdjusters.lastDayOfYear()).toString();
+        }
+        return ApiResult.success(pmWorkContentService.workHourStatistic(userId, projectId, filter, startDate, endDate, workerOrProject));
+    }
+
+/**
+ * 工时明细导出
+ *
+ * @param response
+ */
+    @Log(title = "导出工时明细", businessType = BusinessType.EXPORT)
+    @PostMapping("/workHourStatisticExport")
+    public void export(HttpServletResponse response, @RequestBody PmWorkHourStatisticRequestVO requestVO){
+        List<WorkTimeExportVO> list = pmWorkContentService.workHourStatisticExport(requestVO);
+        ExcelUtil<WorkTimeExportVO> util = new ExcelUtil<WorkTimeExportVO>(WorkTimeExportVO.class);
+        util.exportExcel(response, list, "工时明细", "工时明细");
+    }
+
+    /**
+     * 工时导出
+     *
+     * @param response
+     */
+    @Log(title = "导出工时统计", businessType = BusinessType.EXPORT)
+    @PostMapping("/workHourExport")
+    public void export(HttpServletResponse response,
+                       @RequestParam(value = "userId", required = false) Long userId,
+                       @RequestParam(value = "projectId", required = false) Integer projectId,
+                       @RequestParam(value = "filter", required = false, defaultValue = "1") Integer filter,
+                       @RequestParam(value = "startDate", required = false) String startDate,
+                       @RequestParam(value = "endDate", required = false) String endDate,
+                       @RequestParam(value = "workerOrProject", required = false, defaultValue = "1") Integer workerOrProject) throws IOException {
+        List<WorkTimeExportTwoVO> list = pmWorkContentService.workHourExport(userId, projectId, filter, startDate, endDate, workerOrProject);
+        ExcelUtil<WorkTimeExportTwoVO> util = new ExcelUtil<WorkTimeExportTwoVO>(WorkTimeExportTwoVO.class);
+        util.exportExcel(response, list, "工时统计", "工时统计");
+    }
+
+}
+

+ 88 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/controller/web/PmWorkReportController.java

@@ -0,0 +1,88 @@
+package com.usky.pm.controller.web;
+
+
+import com.usky.common.core.bean.ApiResult;
+import com.usky.common.log.annotation.Log;
+import com.usky.common.log.enums.BusinessType;
+import com.usky.pm.domain.PmWorkReport;
+import com.usky.pm.service.PmWorkReportService;
+import com.usky.pm.service.vo.PmProjectTotalWorkTimeVo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * <p>
+ * 工作报告表 前端控制器
+ * </p>
+ *
+ * @author fu
+ * @since 2024-05-20
+ */
+@RestController
+@RequestMapping("/pmWorkReport")
+public class PmWorkReportController {
+
+    @Autowired
+    private PmWorkReportService pmWorkReportService;
+
+    /**
+     * 周工作报告查询
+     *
+     * @param startDate 开始时间
+     * @param endDate   结束时间
+     * @param id        报告id
+     * @return
+     */
+    @GetMapping("/week")
+    public ApiResult<List<Map<String, List<PmWorkReport>>>> weekWork(@RequestParam(required = false) String startDate,
+                                                                     @RequestParam(required = false) String endDate,
+                                                                     @RequestParam(required = false, defaultValue = "0") Integer id) {
+        return ApiResult.success(pmWorkReportService.weekWork(startDate, endDate, id));
+    }
+
+    /**
+     * 添加工作报告
+     *
+     * @param
+     */
+    @Log(title = "新增/编辑工作报告", businessType = BusinessType.INSERT)
+    @PostMapping("/add")
+    public void add(@RequestBody PmWorkReport pmWorkReport) {
+        pmWorkReportService.addReport(pmWorkReport);
+    }
+
+    /**
+     * 统计工时
+     *
+     * @return
+     */
+    @GetMapping("/workTimeTotal")
+    public ApiResult<PmProjectTotalWorkTimeVo> weekWork(@RequestParam(value = "nowYear", required = false) Integer nowYear,
+                                                        @RequestParam(value = "dateType") Integer dateType,
+                                                        @RequestParam(value = "dateNum") Integer dateNum) {
+        return ApiResult.success(pmWorkReportService.countTime(nowYear, dateType, dateNum));
+    }
+
+    /**
+     * 删除工作报告
+     *
+     * @param reportId 报告id
+     */
+    @Log(title = "删除工作报告", businessType = BusinessType.DELETE)
+    @DeleteMapping("/del/{reportId}")
+    public void del(@PathVariable("reportId") Integer reportId) {
+        pmWorkReportService.deleteContent(reportId);
+    }
+
+    /**
+     * 定时报告查询
+     */
+    @GetMapping("/timedReports")
+    public ApiResult<List<PmWorkReport>> timedReportQuery() {
+        return ApiResult.success(pmWorkReportService.timedReportQuery());
+    }
+
+}

+ 81 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/domain/MceMbuser.java

@@ -0,0 +1,81 @@
+package com.usky.pm.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * <p>
+ * 消息中心_移动端注册表
+ * </p>
+ *
+ * @author han
+ * @since 2024-05-10
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class MceMbuser implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 电话号码
+     */
+    private String phone;
+
+    /**
+     * 微信用户id
+     */
+    private String openid;
+
+    /**
+     * 用户ID
+     */
+    private Long userId;
+
+    /**
+     * 应用ID
+     */
+    private String cids;
+
+    /**
+     * 钉钉用户id
+     */
+    private String dingTalkId;
+
+    /**
+     * 平台token
+     */
+    @TableField(exist = false)
+    private String accessToken;
+
+    /**
+     * 创建人
+     */
+    private String createBy;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 更新人
+     */
+    private String updateBy;
+
+    /**
+     * 更新时间
+     */
+    private LocalDateTime updateTime;
+
+
+}

+ 122 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/domain/PmProject.java

@@ -0,0 +1,122 @@
+package com.usky.pm.domain;
+
+import java.math.BigDecimal;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import java.time.LocalDateTime;
+import java.io.Serializable;
+
+import lombok.*;
+
+/**
+ * <p>
+ * 项目表
+ * </p>
+ *
+ * @author fu
+ * @since 2024-05-20
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class PmProject implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 项目表主键ID
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 项目名称
+     */
+    private String projectName;
+
+    /**
+     * 项目开始时间
+     */
+    private LocalDateTime startTime;
+
+    /**
+     * 项目结束时间
+     */
+    private LocalDateTime endTime;
+
+    /**
+     * 项目描述
+     */
+    private String projectDescribe;
+
+    /**
+     * 项目类型(1:人力外包,2:项目研发,3:采购项目,4:过标项目,5:集成项目,6:其他)
+     */
+    private Integer projectType;
+
+    /**
+     * 项目状态(1:未开始;2;进行中;3:已完成;4:已暂停;5:已作废)
+     */
+    private Integer projectStatus;
+
+    /**
+     * 负责人
+     */
+    private Long projectHead;
+
+    /**
+     * 项目成员
+     */
+    private String projectMember;
+
+    /**
+     * 项目工作量(计划人/天)
+     */
+    private BigDecimal projectWorkload;
+
+    /**
+     * 创建者
+     */
+    private String createBy;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 更新者
+     */
+    private String updateBy;
+
+    /**
+     * 更新时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 部门ID
+     */
+    private Long deptId;
+
+    /**
+     * 租户ID
+     */
+    private Integer tenantId;
+
+    /**
+     * 删除标识(默认0,已删除1)
+     */
+    private Integer delFlag;
+
+    /**
+     * 提交次数
+     */
+    @TableField(exist = false)
+    private Long submissions;
+
+    /**
+     * 项目可见范围(1:公开;2:私有,指定人(创建人、负责人、项目成员)可见)
+     */
+    private Byte visibleRange;
+}

+ 82 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/domain/PmReceive.java

@@ -0,0 +1,82 @@
+package com.usky.pm.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import java.time.LocalDateTime;
+import java.io.Serializable;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * <p>
+ * 报告接收表
+ * </p>
+ *
+ * @author han
+ * @since 2024-07-09
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class PmReceive implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 接收人ID
+     */
+    private Long receiverId;
+
+    /**
+     * 接收人
+     */
+    private String receiverName;
+
+    /**
+     * 报告id
+     */
+    private Integer reportId;
+
+
+    /**
+     * 租户号
+     */
+    private Integer tenantId;
+
+    /**
+     * 组织机构ID
+     */
+    private Long deptId;
+
+    /**
+     * 创建人
+     */
+    private String createBy;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 更新人
+     */
+    private String updateBy;
+
+    /**
+     * 更新时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 是否已读;0、未读,1、已读
+     */
+    private Integer readFlag;
+
+
+}

+ 92 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/domain/PmTimeConf.java

@@ -0,0 +1,92 @@
+package com.usky.pm.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import java.time.LocalTime;
+import java.time.LocalDateTime;
+import java.io.Serializable;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * <p>
+ * 提交时间配置表
+ * </p>
+ *
+ * @author fu
+ * @since 2025-01-10
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class PmTimeConf implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 时间配置表主键ID
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 开始提交时间
+     */
+    private LocalTime startTime;
+
+    /**
+     * 正常提交时间
+     */
+    private LocalTime onTime;
+
+    /**
+     * 结束提交时间
+     */
+    private LocalTime endTime;
+
+    /**
+     * 配置名称
+     */
+    private String confName;
+
+    /**
+     * 配置类型(PM:工作报告提交时间配置)
+     */
+    private String confType;
+
+    /**
+     * 创建者
+     */
+    private String createBy;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 更新者
+     */
+    private String updateBy;
+
+    /**
+     * 更新时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 部门ID
+     */
+    private Long deptId;
+
+    /**
+     * 租户ID
+     */
+    private Integer tenantId;
+
+    /**
+     * 通知人
+     */
+    private String notifier;
+
+
+}

+ 94 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/domain/PmWorkContent.java

@@ -0,0 +1,94 @@
+package com.usky.pm.domain;
+
+import java.math.BigDecimal;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import java.time.LocalDateTime;
+import java.io.Serializable;
+import java.util.List;
+
+import lombok.*;
+import org.springframework.data.annotation.Transient;
+
+/**
+ * <p>
+ * 工作内容表
+ * </p>
+ *
+ * @author fu
+ * @since 2024-05-20
+ */
+
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class PmWorkContent implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 工作内容表主键ID
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 工作报告表主键ID
+     */
+    private Integer reportId;
+
+    /**
+     * 项目ID
+     */
+    private Integer projectId;
+
+    /**
+     * 项目名称
+     */
+    private String projectName;
+
+    /**
+     * 提交人ID
+     */
+    private Long submitterId;
+
+    /**
+     * 工作内容
+     */
+    private String workContent;
+
+    /**
+     * 工时
+     */
+    private BigDecimal workTime;
+
+    /**
+     * 创建者
+     */
+    private String createBy;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 更新者
+     */
+    private String updateBy;
+
+    /**
+     * 更新时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 部门ID
+     */
+    private Long deptId;
+
+    /**
+     * 租户ID
+     */
+    private Integer tenantId;
+
+}

+ 160 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/domain/PmWorkReport.java

@@ -0,0 +1,160 @@
+package com.usky.pm.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.usky.pm.service.vo.PmReportReadersVO;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * <p>
+ * 工作报告表
+ * </p>
+ *
+ * @author fu
+ * @since 2024-05-20
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class PmWorkReport implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 工作报告表主键ID
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 提交人ID
+     */
+    private Long submitterId;
+
+    /**
+     * 报告时间
+     */
+    private LocalDate reportDate;
+
+    /**
+     * 提交时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime submitDate;
+
+    /**
+     * 总工时
+     */
+    private BigDecimal totalHours;
+
+    /**
+     * 抄送人
+     */
+    private String ccTo;
+
+    /**
+     * 工作协调
+     */
+    private String coordinateWork;
+
+    /**
+     * 明日计划
+     */
+    private String tomorrowPlan;
+
+    /**
+     * 创建者
+     */
+    private String createBy;
+
+    /**
+     * 创建时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime createTime;
+
+    /**
+     * 更新者
+     */
+    private String updateBy;
+
+    /**
+     * 更新时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime updateTime;
+
+    /**
+     * 部门ID
+     */
+    private Long deptId;
+
+    /**
+     * 租户ID
+     */
+    private Integer tenantId;
+
+    /**
+     * 工作报告对应内容
+     */
+    @TableField(exist = false)
+    private List<PmWorkContent> workContents;
+
+    /**
+     * 已/未读标识
+     */
+    @TableField(exist = false)
+    private Integer readFlag;
+
+    /**
+     * 是否发送钉钉 (默认:0,否;1:是)
+     */
+    private Integer sendDingTalk;
+
+    /**
+     * 定时时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime timingTime;
+
+    /**
+     * 报告状态(默认1,0:保存状态;1:发送状态)
+     */
+    private Integer reportStatus;
+
+    /**
+     * 是否定时(默认0:否,1:是)
+     */
+    private Integer isRegularlySend;
+
+    /**
+     * 已读,未读人员
+     */
+    @TableField(exist = false)
+    private PmReportReadersVO pmReportReaders;
+
+    /**
+     * 头像
+     */
+    @TableField(exist = false)
+    private String avatar;
+
+    /**
+     * 报告附件
+     */
+    private String reportFile;
+
+    /**
+     * 报告图片
+     */
+    private String reportImage;
+
+}

+ 18 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/mapper/MceMbuserMapper.java

@@ -0,0 +1,18 @@
+package com.usky.pm.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.pm.domain.MceMbuser;
+import org.springframework.stereotype.Repository;
+
+/**
+ * <p>
+ * 消息中心_移动端注册表 Mapper 接口
+ * </p>
+ *
+ * @author han
+ * @since 2024-05-10
+ */
+@Repository
+public interface MceMbuserMapper extends CrudMapper<MceMbuser> {
+
+}

+ 18 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/mapper/PmProjectMapper.java

@@ -0,0 +1,18 @@
+package com.usky.pm.mapper;
+
+import com.usky.pm.domain.PmProject;
+import com.usky.common.mybatis.core.CrudMapper;
+import org.springframework.stereotype.Repository;
+
+/**
+ * <p>
+ * 项目表 Mapper 接口
+ * </p>
+ *
+ * @author fu
+ * @since 2024-05-20
+ */
+@Repository
+public interface PmProjectMapper extends CrudMapper<PmProject> {
+
+}

+ 18 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/mapper/PmReceiveMapper.java

@@ -0,0 +1,18 @@
+package com.usky.pm.mapper;
+
+import com.usky.pm.domain.PmReceive;
+import com.usky.common.mybatis.core.CrudMapper;
+import org.springframework.stereotype.Repository;
+
+/**
+ * <p>
+ * 报告接收表 Mapper 接口
+ * </p>
+ *
+ * @author han
+ * @since 2024-07-09
+ */
+@Repository
+public interface PmReceiveMapper extends CrudMapper<PmReceive> {
+
+}

+ 16 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/mapper/PmTimeConfMapper.java

@@ -0,0 +1,16 @@
+package com.usky.pm.mapper;
+
+import com.usky.pm.domain.PmTimeConf;
+import com.usky.common.mybatis.core.CrudMapper;
+
+/**
+ * <p>
+ * 工作报告提交时间配置表 Mapper 接口
+ * </p>
+ *
+ * @author fu
+ * @since 2025-01-10
+ */
+public interface PmTimeConfMapper extends CrudMapper<PmTimeConf> {
+
+}

+ 41 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/mapper/PmWorkContentMapper.java

@@ -0,0 +1,41 @@
+package com.usky.pm.mapper;
+
+import com.usky.pm.domain.PmWorkContent;
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.pm.service.vo.PmProjectWorkTimeVo;
+import com.usky.pm.service.vo.PmWorkHourStatisticRequestVO;
+import com.usky.pm.service.vo.WorkTimeExportVO;
+import org.apache.ibatis.annotations.Param;
+import org.springframework.stereotype.Repository;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * <p>
+ * 工作内容表 Mapper 接口
+ * </p>
+ *
+ * @author fu
+ * @since 2024-05-20
+ */
+@Repository
+public interface PmWorkContentMapper extends CrudMapper<PmWorkContent> {
+
+    List<PmProjectWorkTimeVo> workTimeCount(@Param("startTime") LocalDateTime startTime,
+                                            @Param("endTime") LocalDateTime endTime,
+                                            @Param("userId") Long userId,
+                                            @Param("tenantId") Integer tenantId);
+
+/*    List<WorkHoursStatisticsVO> workHoursStatistics(@Param("startTime") LocalDate startTime,
+                                                    @Param("endTime") LocalDate endTime,
+                                                    @Param("userId") Long userId,
+                                                    @Param("tenantId") Integer tenantId);*/
+
+
+    /**
+     * 导出工时明细
+     * @return
+     */
+    List<WorkTimeExportVO> workHourStatisticExport(PmWorkHourStatisticRequestVO requestVO);
+}

+ 18 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/mapper/PmWorkReportMapper.java

@@ -0,0 +1,18 @@
+package com.usky.pm.mapper;
+
+import com.usky.pm.domain.PmWorkReport;
+import com.usky.common.mybatis.core.CrudMapper;
+import org.springframework.stereotype.Repository;
+
+/**
+ * <p>
+ * 工作报告表 Mapper 接口
+ * </p>
+ *
+ * @author fu
+ * @since 2024-05-20
+ */
+@Repository
+public interface PmWorkReportMapper extends CrudMapper<PmWorkReport> {
+
+}

+ 15 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/mapper/SysUserMapper.java

@@ -0,0 +1,15 @@
+package com.usky.pm.mapper;
+
+import com.usky.common.mybatis.core.CrudMapper;
+import com.usky.system.domain.SysUser;
+import org.springframework.stereotype.Repository;
+
+/**
+ * @description:TODO
+ * @author: fu
+ * @create: 2024-06-24 15:47
+ */
+@Repository
+public interface SysUserMapper extends CrudMapper<SysUser> {
+
+}

+ 125 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/service/PmProjectService.java

@@ -0,0 +1,125 @@
+package com.usky.pm.service;
+
+import com.usky.common.core.bean.CommonPage;
+import com.usky.pm.domain.PmProject;
+import com.usky.common.mybatis.core.CrudService;
+import com.usky.pm.service.vo.PmProjectWorkTimeTwoVo;
+import com.usky.pm.service.vo.PmUsersProjectWorkTimeVO;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * <p>
+ * 项目表 服务类
+ * </p>
+ *
+ * @author fu
+ * @since 2024-05-20
+ */
+public interface PmProjectService extends CrudService<PmProject> {
+
+    /**
+     * 新增、编辑项目
+     *
+     * @param project
+     */
+    void addProject(PmProject project);
+
+    /**
+     * 删除
+     *
+     * @param projectId 项目id
+     */
+    void delProject(Integer projectId);
+
+    /**
+     * 查询当前租户所有项目
+     *
+     * @return
+     */
+    List<PmProject> queryProject();
+
+    /**
+     * 分页
+     *
+     * @param projectName       项目名
+     * @param projectType       类型
+     * @param projectStatus     状态
+     * @param projectAscription 项目归属类型
+     * @param pageNum           页码
+     * @param pageSize          页大小
+     * @return
+     */
+    CommonPage<PmProject> projectList(String projectName, Integer projectType, Integer projectStatus, Integer projectAscription, Integer pageNum, Integer pageSize, Integer projectId, Byte visibleRange);
+
+    /**
+     * 查询项目名
+     *
+     * @param projectIds
+     * @return
+     */
+    List<PmProject> projectName(List<Integer> projectIds);
+
+    /**
+     * 所有、我负责、我参与项目数量统计
+     *
+     * @return
+     */
+    Map<String, Integer> sum();
+
+    /**
+     * 人员查询(项目负责人、成员)
+     *
+     * @return
+     */
+    List<Map<String, Object>> projectUsers(Long userId);
+
+    /**
+     * 所有项目id和名称
+     *
+     * @return
+     */
+    List<Map<String, Object>> projectIdName(Integer projectId);
+
+    /**
+     * 人员或项目下拉
+     *
+     * @param identifying 标识:1,返回人员userId和nickName;2:返回ProjectId和ProjectName
+     * @return
+     */
+    List<Map<String, Object>> userOrProject(Integer identifying);
+
+    /**
+     * 退出项目
+     *
+     * @param projectId
+     */
+    void exitProject(Integer projectId);
+
+    /**
+     * 项目概览-项目-工时统计
+     *
+     * @param projectId
+     * @param startDate
+     * @param endDate
+     * @return
+     */
+    PmProjectWorkTimeTwoVo projectWorkTime(Integer projectId, String startDate, String endDate);
+
+    /**
+     * 项目概览-人员-项目工时统计
+     *
+     * @param projectId
+     * @return
+     */
+    PmUsersProjectWorkTimeVO usersProjectWorkTime(Integer projectId, String startDate, String endDate);
+
+    /**
+     * 加入项目
+     *
+     * @param projectId
+     * @return
+     */
+    void joinProject(Integer projectId);
+}

+ 32 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/service/PmReceiveService.java

@@ -0,0 +1,32 @@
+package com.usky.pm.service;
+
+import com.usky.pm.domain.PmReceive;
+import com.usky.common.mybatis.core.CrudService;
+import com.usky.system.domain.SysUser;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 报告接收表 服务类
+ * </p>
+ *
+ * @author han
+ * @since 2024-07-09
+ */
+public interface PmReceiveService extends CrudService<PmReceive> {
+
+    /**
+     * 新增接收记录
+     */
+    void add(PmReceive receive);
+
+    /**
+     * 已/未读状态更新
+     */
+    void updateReadFlag(Integer reportId);
+
+    List<SysUser> test( Integer tenantId);
+
+
+}

+ 54 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/service/PmTimeConfService.java

@@ -0,0 +1,54 @@
+package com.usky.pm.service;
+
+import com.usky.common.core.bean.CommonPage;
+import com.usky.pm.domain.PmTimeConf;
+import com.usky.common.mybatis.core.CrudService;
+import com.usky.pm.service.vo.PmSubmitCountResponseVO;
+
+/**
+ * <p>
+ * 工作报告提交时间配置表 服务类
+ * </p>
+ *
+ * @author fu
+ * @since 2025-01-10
+ */
+public interface PmTimeConfService extends CrudService<PmTimeConf> {
+
+    /**
+     * 获取提交统计
+     * @param submitDate
+     * @return
+     */
+    PmSubmitCountResponseVO submitCount(String submitDate);
+
+    /**
+     * 提交记录分页
+     * @param queryType 查询类型(0:按时提交,1:迟交,2:未提交)
+     * @param submitDate 提交日期
+     * @param reportId 报告id
+     * @param pageNum 页码
+     * @param pageSize 页大小
+     * @return 分页结果
+     */
+    CommonPage<Object> submitPage(String submitDate, Integer queryType, Integer reportId, Integer pageNum, Integer pageSize);
+
+    /**
+     * 获取提交时间
+     * @return
+     */
+    public PmTimeConf getTimeConf(Integer tenantId);
+
+    /**
+     * 添加或修改报告统计时间配置
+     * @param pmTimeConf
+     */
+    public void addOrUpdateTimeConf(PmTimeConf pmTimeConf);
+
+    /**
+     * 获取迟交报告
+     * @param isOpen 是否开启(0:关闭,1:开启)
+     * @param id 配置id
+     */
+    void isOpen(Integer isOpen, Integer id);
+}

+ 108 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/service/PmWorkContentService.java

@@ -0,0 +1,108 @@
+package com.usky.pm.service;
+
+import com.usky.common.core.bean.CommonPage;
+import com.usky.pm.domain.PmWorkContent;
+import com.usky.common.mybatis.core.CrudService;
+import com.usky.pm.domain.PmWorkReport;
+import com.usky.pm.service.vo.PmWorkHourStatisticRequestVO;
+import com.usky.pm.service.vo.PmWorkReportSlideVO;
+import com.usky.pm.service.vo.WorkTimeExportTwoVO;
+import com.usky.pm.service.vo.WorkTimeExportVO;
+
+import com.usky.system.domain.SysUser;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * <p>
+ * 工作内容表 服务类
+ * </p>
+ *
+ * @author fu
+ * @since 2024-05-20
+ */
+public interface PmWorkContentService extends CrudService<PmWorkContent> {
+
+    /**
+     * 清空报告内容
+     *
+     * @param reportId 报告id
+     */
+    void deleteContent(Integer reportId);
+
+    /**
+     * 报告记录中查询项目名与id
+     *
+     * @param startDate         开始时间
+     * @param endDate           结束时间
+     * @param projectName       项目名
+     * @param projectAscription 查询标识(1:我负责的;2:我收到,3:我发出的)
+     * @return
+     */
+    List<PmWorkContent> projectQuery(String startDate, String endDate, String projectName, Integer projectAscription, Long submitterId);
+
+    /**
+     * 报告记录
+     *
+     * @param startDate         开始时间
+     * @param endDate           结束时间
+     * @param projectName       项目名
+     * @param projectAscription 查询标识(1:我负责的;2:抄送我的;3;我发出)
+     * @return
+     */
+    List<PmWorkReport> workReportQuery(String startDate, String endDate, String projectName, Integer projectAscription, Integer reportId);
+
+    /**
+     * 获取用户信息
+     *
+     * @param userIds
+     * @return
+     */
+    List<SysUser> nickNames(List<Long> userIds);
+
+    /**
+     * 报告详情分页列表
+     * @param projectAscription 1:我负责;2:我收到;3:我发出
+     * @param pageNum 页数
+     * @param pageSize 页大小
+     * @param upOrDown 上下滑动(0:下滑;1:上滑)
+     * @param slideSum 滑动数量
+     * @param submitDate 提交时间
+     * @return''
+     */
+    CommonPage<PmWorkReport> reportPage(Integer projectAscription, Integer pageNum, Integer pageSize, Integer report, String startDate,
+                                        String endDate, Integer projectId, Long userId, Integer upOrDown, Integer slideSum, String submitDate);
+
+    /**
+     * @description:
+     * @author: fu
+     * @date: 2024/12/9 23:01
+     * @param: [upOrDown 上下滑动(0:上滑;1:下滑), slideSum(滑动数量), submitDate(提交时间)]
+     * @return: java.util.List<com.usky.pm.service.vo.PmWorkReportSlideVO>
+     **/
+    PmWorkReportSlideVO slide(Integer upOrDown, Integer slideSum, LocalDateTime submitDate);
+
+    /** 工时统计页面
+     * @return 返回项目名(或牛马名)以及项目工时
+     */
+    List<Object> workHourStatisticNew(PmWorkHourStatisticRequestVO requestVO);
+    //回退代码
+    List<Object> workHourStatistic(Long userId, Integer projectId, Integer filter, String startDate, String endDate, Integer workerOrProject);
+
+
+    /** 工时明细导出
+     * @return
+     */
+    List<WorkTimeExportVO> workHourStatisticExport(PmWorkHourStatisticRequestVO requestVO);
+
+
+    /** 工时统计导出
+     * @param userId          用户id
+     * @param startDate       开始时间
+     * @param endDate         结束时间
+     * @return 返回项目名(或牛马名)以及项目工时
+     */
+    List<WorkTimeExportTwoVO> workHourExport(Long userId, Integer projectId, Integer filter, String startDate, String endDate, Integer workerOrProject);
+
+}

+ 63 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/service/PmWorkReportService.java

@@ -0,0 +1,63 @@
+package com.usky.pm.service;
+
+import com.usky.pm.domain.PmWorkReport;
+import com.usky.common.mybatis.core.CrudService;
+import com.usky.pm.service.vo.PmProjectTotalWorkTimeVo;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * <p>
+ * 工作报告表 服务类
+ * </p>
+ *
+ * @author fu
+ * @since 2024-05-20
+ */
+public interface PmWorkReportService extends CrudService<PmWorkReport> {
+
+    /**
+     * 获取时间内工作报告
+     * @param startDate 开始时间
+     * @param endDate 结束时间
+     * @param reportId 报告id
+     * @return
+     */
+    List<Map<String, List<PmWorkReport>>> weekWork(String startDate, String endDate, Integer reportId);
+
+    /**
+     * 新增、编辑工作报告
+     * @param pmWorkReport 工作报告
+     */
+    void addReport(PmWorkReport pmWorkReport);
+
+    /**
+     * 工时计算
+     * @return
+     */
+    PmProjectTotalWorkTimeVo countTime(Integer nowYear, Integer dateType, Integer dateNum);
+
+    /**
+     * 定时发送
+     */
+    void timedSending(LocalDateTime time);
+
+    /**
+     * 删除报告
+     * @param reportId 报告id
+     */
+    void deleteContent(Integer reportId);
+
+    /**
+     * 定时报告查询
+     * @return
+     */
+    List<PmWorkReport> timedReportQuery();
+
+    /**
+     * 报告提交定时提醒
+     */
+    void reportSubmissionReminder();
+}

+ 346 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/service/config/DingTalkAndMessage.java

@@ -0,0 +1,346 @@
+package com.usky.pm.service.config;
+
+import com.alibaba.fastjson.JSONObject;
+import com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenResponse;
+import com.aliyun.tea.TeaException;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.dingtalk.api.DefaultDingTalkClient;
+import com.dingtalk.api.DingTalkClient;
+import com.dingtalk.api.request.OapiReportCreateRequest;
+import com.dingtalk.api.request.OapiV2UserGetbymobileRequest;
+import com.dingtalk.api.response.OapiReportCreateResponse;
+import com.dingtalk.api.response.OapiV2UserGetbymobileResponse;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.usky.common.core.bean.ApiResult;
+import com.usky.common.core.exception.BusinessException;
+import com.usky.pm.constant.dingTalkConstant;
+import com.usky.pm.domain.MceMbuser;
+import com.usky.pm.domain.PmWorkContent;
+import com.usky.pm.domain.PmWorkReport;
+import com.usky.pm.mapper.MceMbuserMapper;
+import com.usky.pm.mapper.PmWorkReportMapper;
+import com.usky.pm.mapper.SysUserMapper;
+import com.usky.system.RemoteMceService;
+import com.usky.system.domain.SysUser;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.Async;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @description:TODO
+ * @author: fu
+ * @create: 2024-07-30 10:32
+ */
+@Slf4j
+@Configuration
+public class DingTalkAndMessage {
+
+    private static final String INFO_TITLE = "报告提醒";
+    private static final String INFO_CONTENT = "的报告";
+    private static final int INFO_TYPE = 5;
+
+    @Autowired
+    private RemoteMceService remoteMceService;
+
+    @Autowired
+    private MceMbuserMapper mceMbuserMapper;
+
+    @Autowired
+    private SysUserMapper sysUserMapper;
+
+    @Autowired
+    private PmWorkReportMapper pmWorkReportMapper;
+
+    /**
+     * 指定请求
+     *
+     * @return
+     * @throws Exception
+     */
+    public static com.aliyun.dingtalkoauth2_1_0.Client createClient() throws Exception {
+        com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config();
+        config.protocol = "https";
+        config.regionId = "central";
+        config.method = "POST";
+        return new com.aliyun.dingtalkoauth2_1_0.Client(config);
+    }
+
+    /**
+     * 获取钉钉企业内部AccessToken
+     *
+     * @return
+     * @throws Exception
+     */
+    public String getDingTalkToken() {
+        GetAccessTokenResponse responseBody = new GetAccessTokenResponse();
+        com.aliyun.dingtalkoauth2_1_0.Client client = null;
+        try {
+            client = createClient();
+        } catch (Exception e) {
+            log.error("DingTalkAndMessage client = createClient() 获取钉钉token异常:" + e);
+        }
+        com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenRequest getAccessTokenRequest = new com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenRequest()
+                .setAppKey(dingTalkConstant.DING_TALK_CLIENT_ID)
+                .setAppSecret(dingTalkConstant.DING_TALK_CLIENT_SECRET);
+        try {
+            responseBody = client.getAccessToken(getAccessTokenRequest);
+        } catch (TeaException err) {
+            if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
+                log.error("获取钉钉token异常:" + err.code + err.message);
+            }
+        } catch (Exception _err) {
+            TeaException err = new TeaException(_err.getMessage(), _err);
+            if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
+                log.error("DingTalkAndMessage TeaException err = new TeaException(_err.getMessage(), _err) 获取钉钉token异常:" + err.code + err.message);
+            }
+        }
+        return responseBody.getBody().accessToken;
+    }
+
+    /**
+     * 获取钉钉userId,优先从数据库中找,没有则调用钉钉接口查
+     *
+     * @param userId userid
+     * @return
+     */
+    public String getDingTalkUserId(Long userId) {
+        String dingTalkId = "";
+        if (userId == null) {
+            return dingTalkId;
+        }
+        SysUser user = userPhoneNumber(userId);
+        String mobile = user.getPhonenumber();
+        String userName = user.getUserName();
+        LocalDateTime now = LocalDateTime.now();
+
+        dingTalkId = getDingTalkId(userId, mobile, dingTalkId);
+        MceMbuser mceMbuser = getMceMbuser(userId);
+
+        if (mceMbuser != null) {
+            String dingId = mceMbuser.getDingTalkId();
+            if (StringUtils.isNotBlank(dingId)) {
+                return dingId;
+            } else {
+                // 钉钉userid存入注册表
+                MceMbuser mbuser = new MceMbuser();
+                mbuser.setDingTalkId(dingTalkId);
+                mbuser.setPhone(mobile);
+                mbuser.setUserId(userId);
+                mbuser.setUpdateBy(userName);
+                mbuser.setUpdateTime(now);
+                mceMbuserMapper.updateById(mbuser);
+            }
+        } else {
+            // 钉钉userid存入注册表
+            MceMbuser mbuser = new MceMbuser();
+            mbuser.setDingTalkId(dingTalkId);
+            mbuser.setPhone(mobile);
+            mbuser.setUserId(userId);
+            mbuser.setCreateBy(userName);
+            mbuser.setCreateTime(now);
+            mceMbuserMapper.insert(mbuser);
+        }
+        return dingTalkId;
+    }
+
+    /**
+     * 获取用户 消息中心_注册表信息
+     *
+     * @param userId
+     * @return
+     */
+    private MceMbuser getMceMbuser(Long userId) {
+        LambdaQueryWrapper<MceMbuser> dingTalkQuery = Wrappers.lambdaQuery();
+        dingTalkQuery.eq(MceMbuser::getUserId, userId);
+        return mceMbuserMapper.selectOne(dingTalkQuery);
+    }
+
+    /**
+     * 获取钉钉userId
+     *
+     * @param userId 平台userid
+     * @return
+     */
+    private String getDingTalkId(Long userId, String mobile, String dingTalkId) {
+        try {
+            String accessToken = getDingTalkToken();
+            DingTalkClient client = new DefaultDingTalkClient(dingTalkConstant.DING_TALK_USERID_URL);
+            OapiV2UserGetbymobileRequest req = new OapiV2UserGetbymobileRequest();
+            req.setMobile(mobile);
+            OapiV2UserGetbymobileResponse rsp = client.execute(req, accessToken);
+            ObjectMapper objectMapper = new ObjectMapper();
+            JsonNode rootNode = objectMapper.readTree(rsp.getBody());
+            JsonNode resultNode = rootNode.get("result");
+            dingTalkId = resultNode.get("userid").asText();
+        } catch (Exception e) {
+            log.error("获取userid:" + userId + "的钉钉ID失败", e);
+        }
+        return dingTalkId;
+    }
+
+    /**
+     * @description: 获取用户信息
+     * @author: fu
+     * @date: 2024/8/22 15:14
+     * @param: [userId]
+     * @return: java.lang.String
+     **/
+    private SysUser userPhoneNumber(Long userId) {
+        LambdaQueryWrapper<SysUser> phoneQuery = Wrappers.lambdaQuery();
+        phoneQuery.select(SysUser::getUserId, SysUser::getPhonenumber, SysUser::getUserName, SysUser::getNickName,
+                        SysUser::getAvatar, SysUser::getSex, SysUser::getDeptId, SysUser::getTenantId)
+                .eq(SysUser::getUserId, userId);
+        return sysUserMapper.selectOne(phoneQuery);
+    }
+
+    @Async// 异步发送
+    public void sendDingTalkDailyReport(PmWorkReport workReport, List<PmWorkContent> workContents) {
+        String userName = workReport.getCreateBy();
+        log.info(userName + "的工作报告开始发送钉钉-----------------------------------");
+        try {
+            String ccToStr = workReport.getCcTo();
+            List<String> ccTo = new ArrayList<>();
+            if (StringUtils.isNotBlank(ccToStr)) {
+                String[] items = ccToStr.split(",");
+                List<Long> userIds = new ArrayList<>();
+                for (String item : items) {
+                    long number = Long.parseLong(item.trim());
+                    userIds.add(number);
+                }
+                for (Long userId : userIds) {
+                    String dingTalkUserId = getDingTalkUserId(userId);
+                    ccTo.add(dingTalkUserId);
+                }
+            }
+            String reportCoordinateWork = workReport.getCoordinateWork();
+            String reportTomorrowPlan = workReport.getTomorrowPlan();
+            String coordinateWork1 = "-";
+            String tomorrowPlan1 = "-";
+
+            if (reportCoordinateWork != null && reportCoordinateWork != "") {
+                coordinateWork1 = reportCoordinateWork;
+            }
+            if (reportTomorrowPlan != null && reportTomorrowPlan != "") {
+                tomorrowPlan1 = reportTomorrowPlan;
+            }
+
+            StringBuilder contentBuilder = new StringBuilder();
+            for (PmWorkContent content : workContents) {
+                String projectName = content.getProjectName();
+                BigDecimal workTime = content.getWorkTime();
+                String workContent = content.getWorkContent();
+                StringBuilder markdown = new StringBuilder();
+                markdown.append("#### ").append(projectName).append(" ").append(workTime).append("h\n\n");
+                markdown.append(workContent).append("\n");
+                contentBuilder.append(markdown).append("\n\n");
+            }
+            String completedWork = contentBuilder.toString();
+            DingTalkClient client = new DefaultDingTalkClient(dingTalkConstant.DING_TALK_CREATE_DAILY_REPORT_URL);
+            OapiReportCreateRequest req = new OapiReportCreateRequest();
+            OapiReportCreateRequest.OapiCreateReportParam obj1 = new OapiReportCreateRequest.OapiCreateReportParam();
+            List<OapiReportCreateRequest.OapiReportContentVo> list3 = new ArrayList<>();
+            OapiReportCreateRequest.OapiReportContentVo completedWorkD = new OapiReportCreateRequest.OapiReportContentVo();
+            completedWorkD.setSort(0L);
+            completedWorkD.setType(1L);
+            completedWorkD.setContentType(dingTalkConstant.DING_TALK_DAILY_REPORT_CONTENT_TYPE);
+            completedWorkD.setContent(completedWork);
+            completedWorkD.setKey(dingTalkConstant.DING_TALK_DAILY_REPORT_COMPLETED_WORK);
+            list3.add(completedWorkD);
+            OapiReportCreateRequest.OapiReportContentVo tomorrowPlan = new OapiReportCreateRequest.OapiReportContentVo();
+            tomorrowPlan.setSort(1L);
+            tomorrowPlan.setType(1L);
+            tomorrowPlan.setContentType(dingTalkConstant.DING_TALK_DAILY_REPORT_CONTENT_TYPE);
+            tomorrowPlan.setContent(tomorrowPlan1);
+            tomorrowPlan.setKey(dingTalkConstant.DING_TALK_DAILY_REPORT_TOMORROW_PLAN);
+            list3.add(tomorrowPlan);
+            OapiReportCreateRequest.OapiReportContentVo coordinateWork = new OapiReportCreateRequest.OapiReportContentVo();
+            coordinateWork.setSort(2L);
+            coordinateWork.setType(1L);
+            coordinateWork.setContentType(dingTalkConstant.DING_TALK_DAILY_REPORT_CONTENT_TYPE);
+            coordinateWork.setContent(coordinateWork1);
+            coordinateWork.setKey(dingTalkConstant.DING_TALK_DAILY_REPORT_COORDINATE_WORK);
+            list3.add(coordinateWork);
+            obj1.setContents(list3);
+            if (!ccTo.isEmpty()) {
+                obj1.setToUserids(ccTo);
+            }
+            obj1.setTemplateId(dingTalkConstant.DING_TALK_DAILY_REPORT_TEMPLATE_ID);
+            obj1.setToChat(dingTalkConstant.DING_TALK_DAILY_REPORT_TO_CHAT);
+            obj1.setDdFrom(dingTalkConstant.DING_TALK_CORP_ID);
+            obj1.setUserid(getDingTalkUserId(workReport.getSubmitterId()));
+            // obj1.setToChat(true);
+            // obj1.setToCids(dingTalkConstant.DING_TALK_DAILY_REPORT_TO_CIDS);//发送到群,群id
+            req.setCreateReportParam(obj1);
+            OapiReportCreateResponse rsp = client.execute(req, getDingTalkToken());
+            if (rsp.isSuccess()) {
+                log.info(userName + "的钉钉报告发送成功");
+                workReport.setReportStatus(1);
+            } else {
+                workReport.setSendDingTalk(0);
+                log.error(userName + "的钉钉报告发送失败: " + rsp.getErrmsg());
+            }
+        } catch (Exception e) {
+            workReport.setSendDingTalk(0);
+            log.error(userName + "的钉钉报告发送消息时发生异常", e);
+        }
+        log.info(userName + "的工作报告发送钉钉结束-----------------------------------");
+        pmWorkReportMapper.updateById(workReport);
+    }
+
+
+    //@Async
+    @Async
+    public void sendAsyncMessage(PmWorkReport newReport) {
+        String username = newReport.getCreateBy();
+        Long submitterId = newReport.getSubmitterId();
+        log.info(username + "的工作报告开始发送消息中心-----------------------------------");
+        List<Long> userId = new ArrayList<>();
+        if (!newReport.getCcTo().isEmpty()) {
+            userId = Optional.of(newReport.getCcTo())
+                    .map(ccTo -> Arrays.stream(ccTo.split(","))
+                            .map(Long::parseLong)
+                            .collect(Collectors.toList()))
+                    .orElse(Collections.emptyList());
+        } else {
+            throw new BusinessException( newReport.getCreateBy() + "的报告:" + newReport.getId() + ",抄送人为空,无需发送消息中心");
+        }
+        LambdaQueryWrapper<SysUser> nickNameQuery = Wrappers.lambdaQuery();
+        nickNameQuery.select(SysUser::getNickName)
+                .eq(SysUser::getUserId, submitterId);
+        SysUser nickName = sysUserMapper.selectOne(nickNameQuery);
+        JSONObject jsonObject = new JSONObject();
+        jsonObject.put("infoTitle", INFO_TITLE);
+        jsonObject.put("infoContent", nickName.getNickName() + INFO_CONTENT);
+        jsonObject.put("infoType", INFO_TYPE);
+        jsonObject.put("id", newReport.getId());
+        jsonObject.put("infoTypeName", INFO_TITLE);
+        jsonObject.put("userName", newReport.getCreateBy());
+        if (!userId.isEmpty()) {
+            jsonObject.put("userIds", userId);
+        }
+        try {
+            // 推送消息中心
+            ApiResult<Void> voidApiResult = remoteMceService.addMce(jsonObject.toString());
+
+            if (voidApiResult.isSuccess()) {
+                log.info("报告消息发送成功!");
+            } else {
+                log.error("报告消息发送失败!");
+            }
+        } catch (Exception e) {
+            log.error("报告发送消息时发生异常", e);
+        }
+        log.info(username + "的工作报告发送消息中心完成-----------------------------------");
+    }
+
+}
+

+ 75 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/service/config/websocket/WebSocket.java

@@ -0,0 +1,75 @@
+package com.usky.pm.service.config.websocket;
+
+import cn.hutool.json.JSONUtil;
+import org.springframework.stereotype.Component;
+
+import javax.websocket.OnClose;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+@ServerEndpoint(value = "/websocket/{userId}")
+@Component
+public class WebSocket {
+    private static ConcurrentHashMap<String, WebSocket> webSocketMap = new ConcurrentHashMap<>();
+    //实例一个session,这个session是websocket的session
+    private Session session;
+
+    //新增一个方法用于主动向客户端发送消息
+    public void sendMessage(Object message, String userId) {
+        Iterator<Map.Entry<String, WebSocket>> iterator = webSocketMap.entrySet().iterator();
+        while (iterator.hasNext()) {
+            Map.Entry<String, WebSocket> entry = iterator.next();
+            int index = entry.getKey().indexOf(",");
+            String result = entry.getKey().substring(0, index);
+            if (result.equals(userId)){
+                if (entry.getValue() != null) {
+                    try {
+                        entry.getValue().session.getBasicRemote().sendText(JSONUtil.toJsonStr(message));
+                        System.out.println("【websocket消息】发送消息成功,用户=" + userId + ",消息内容" + message.toString());
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+        }
+    }
+
+    public static ConcurrentHashMap<String, WebSocket> getWebSocketMap() {
+        return webSocketMap;
+    }
+
+    public static void setWebSocketMap(ConcurrentHashMap<String, WebSocket> webSocketMap) {
+        WebSocket.webSocketMap = webSocketMap;
+    }
+
+    //前端请求时一个websocket时
+    @OnOpen
+    public void onOpen(Session session, @PathParam("userId") String userId) {
+        this.session = session;
+        webSocketMap.put(userId, this);
+        sendMessage("CONNECT_SUCCESS", userId);
+        System.out.println("【websocket消息】有新的连接,连接id" + userId);
+    }
+
+    //前端关闭时一个websocket时
+    @OnClose
+    public void onClose(@PathParam("userId") String userId) {
+        webSocketMap.remove(userId);
+        System.out.println("【websocket消息】连接断开,总数:" + webSocketMap.size());
+    }
+
+    //前端向后端发送消息
+    @OnMessage
+    public void onMessage(String message) {
+        if (!message.equals("ping")) {
+            System.out.println("【websocket消息】收到客户端发来的消息:" + message);
+        }
+    }
+}

+ 16 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/service/config/websocket/WebSocketConfig.java

@@ -0,0 +1,16 @@
+package com.usky.pm.service.config.websocket;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.socket.server.standard.ServerEndpointExporter;
+
+//开启WebSocket的支持,并把该类注入到spring容器中
+@Configuration
+public class WebSocketConfig {
+
+    @Bean
+    public ServerEndpointExporter serverEndpointExporter() {
+        return new ServerEndpointExporter();
+    }
+
+}

+ 693 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/service/impl/PmProjectServiceImpl.java

@@ -0,0 +1,693 @@
+package com.usky.pm.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.common.datascope.annotation.DataScope;
+import com.ruoyi.common.datascope.context.DataScopeContextHolder;
+import com.usky.common.core.bean.CommonPage;
+import com.usky.common.core.exception.BusinessException;
+import com.usky.common.security.utils.SecurityUtils;
+import com.usky.pm.domain.PmProject;
+import com.usky.pm.domain.PmWorkContent;
+import com.usky.pm.domain.PmWorkReport;
+import com.usky.pm.mapper.PmProjectMapper;
+import com.usky.pm.mapper.PmWorkContentMapper;
+import com.usky.pm.mapper.PmWorkReportMapper;
+import com.usky.pm.mapper.SysUserMapper;
+import com.usky.pm.service.PmProjectService;
+import com.usky.common.mybatis.core.AbstractCrudService;
+import com.usky.pm.service.PmWorkContentService;
+import com.usky.pm.service.vo.PmProjectWorkTimeTwoVo;
+import com.usky.pm.service.vo.PmUsersProjectWorkTimeVO;
+import com.usky.system.domain.SysUser;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+/**
+ * <p>
+ * 项目表 服务实现类
+ * </p>
+ *
+ * @author fu
+ * @since 2024-05-20
+ */
+@Slf4j
+@Service
+public class PmProjectServiceImpl extends AbstractCrudService<PmProjectMapper, PmProject> implements PmProjectService {
+
+    @Autowired
+    private PmWorkContentMapper pmWorkContentMapper;
+
+    @Autowired
+    private SysUserMapper sysUserMapper;
+
+    @Autowired
+    private PmProjectMapper pmProjectMapper;
+
+    @Autowired
+    private PmWorkReportMapper pmWorkReportMapper;
+
+    @Autowired
+    private PmWorkContentService pmWorkContentService;
+
+    private static final String FIND_IN_SET_SQL = "FIND_IN_SET(?, project_member) > 0";
+
+    @Override
+    public void addProject(PmProject project) {
+        Long deptId = SecurityUtils.getLoginUser().getSysUser().getDeptId();
+        String userName = SecurityUtils.getUsername();
+        Integer tenantId = SecurityUtils.getTenantId();
+        LocalDateTime now = LocalDateTime.now();
+
+        // 检查项目名称是否为空或超过长度限制
+        String projectName = project.getProjectName();
+        if (StringUtils.isBlank(projectName)) {
+            throw new BusinessException("项目名称不能为空!");
+        } else if (projectName.length() > 50) {
+            throw new BusinessException("项目名称长度不能超过50个字符!");
+        }
+
+        // 检查项目名称是否已存在
+        if (Objects.isNull(project.getId())) {
+            checkProjectNameExistence(project, tenantId);
+        }
+
+        // 检查项目类型是否为空或不在有效范围内
+        Integer projectType = project.getProjectType();
+        if (projectType == null) {
+            throw new BusinessException("项目类型不能为空且");
+        } else if (projectType < 1 || projectType > 6) {
+            throw new BusinessException("项目类型传参有误!");
+        }
+
+        // 检查项目状态是否为空或不在有效范围内
+        Integer projectStatus = project.getProjectStatus();
+        if (projectStatus == null) {
+            throw new BusinessException("项目状态不能为空且!");
+        } else if (projectStatus < 1 || projectStatus > 5) {
+            throw new BusinessException("项目状态传参有误!");
+        }
+
+        // 设置项目工作量和校验
+        handleProjectWorkload(project);
+
+        // 保存或更新项目
+        saveOrUpdateProject(project, userName, now, deptId, tenantId);
+    }
+
+    // 检查项目名称是否已存在
+    private void checkProjectNameExistence(PmProject project, Integer tenantId) {
+        LambdaQueryWrapper<PmProject> wrapper = Wrappers.lambdaQuery();
+        wrapper.eq(PmProject::getProjectName, project.getProjectName())
+                .eq(PmProject::getTenantId, tenantId)
+                .eq(PmProject::getDelFlag, 0);
+        PmProject existingProject = pmProjectMapper.selectOne(wrapper);
+        if (Objects.nonNull(existingProject)) {
+            throw new BusinessException("已存在同名项目!请检查后重试");
+        }
+    }
+
+    // 处理项目工作量
+    private void handleProjectWorkload(PmProject project) {
+        if (Objects.isNull(project.getProjectWorkload())) {
+            return;
+        }
+
+        if (project.getProjectWorkload().scale() > 1) {
+            throw new BusinessException("计划人/天小数位超出长度,请重新输入!");
+        } else if (project.getProjectWorkload().precision() - project.getProjectWorkload().scale() > 5) {
+            throw new BusinessException("计划人/天整数位超出长度,请重新输入!");
+        }
+    }
+
+    // 保存或更新项目
+    private void saveOrUpdateProject(PmProject project, String userName, LocalDateTime now, Long deptId, Integer tenantId) {
+        if (Objects.isNull(project.getId())) {
+            project.setCreateBy(userName);
+            project.setCreateTime(now);
+            project.setDeptId(deptId);
+            project.setTenantId(tenantId);
+            this.save(project);
+        } else {
+            project.setUpdateBy(userName);
+            project.setUpdateTime(now);
+            this.updateById(project);
+        }
+    }
+
+    /**
+     * 删除项目
+     *
+     * @param projectId 项目id
+     */
+    @Override
+    public void delProject(Integer projectId) {
+        LambdaQueryWrapper<PmWorkContent> queryWrapper = Wrappers.lambdaQuery();
+        queryWrapper.eq(PmWorkContent::getProjectId, projectId);
+        List<PmWorkContent> pmWorkContent = pmWorkContentMapper.selectList(queryWrapper);
+        if (pmWorkContent.size() != 0) {
+            throw new BusinessException("该项目下存在日报,无法删除!若有疑问请联系管理员");
+        } else {
+            LambdaQueryWrapper<PmProject> query = Wrappers.lambdaQuery();
+            query.eq(PmProject::getId, projectId);
+            PmProject project = baseMapper.selectOne(query);
+            project.setDelFlag(1);
+            project.setUpdateBy(SecurityUtils.getUsername());
+            project.setUpdateTime(LocalDateTime.now());
+            baseMapper.updateById(project);
+        }
+    }
+
+    /**
+     * 添加工作报告项目下拉框
+     * 查询当前租户所有项目
+     *
+     * @return 只返回项目id和名称
+     */
+    @Override
+    public List<PmProject> queryProject() {
+        Integer tenantId = SecurityUtils.getTenantId();
+        Long userId = SecurityUtils.getUserId();
+        LambdaQueryWrapper<PmProject> wrapper = Wrappers.lambdaQuery();
+        wrapper.select(PmProject::getId, PmProject::getProjectName)
+                .eq(PmProject::getTenantId, tenantId)
+                .eq(PmProject::getDelFlag, 0)
+                .and(qw -> qw
+                        .eq(PmProject::getProjectHead, userId)
+                        .or().apply("FIND_IN_SET('" + SecurityUtils.getUserId() + "', project_member) > 0")
+                )
+                .in(PmProject::getProjectStatus, 1, 2, 3);
+        List<PmProject> list = this.list(wrapper);
+        if (list.isEmpty()) {
+            return list;
+        }
+
+        List<Integer> projectIds = list.stream().map(PmProject::getId).collect(Collectors.toList());
+        LambdaQueryWrapper<PmWorkContent> queryWrapper = Wrappers.lambdaQuery();
+        queryWrapper.select(PmWorkContent::getProjectId)
+                .eq(PmWorkContent::getSubmitterId, userId)
+                .in(PmWorkContent::getProjectId, projectIds)
+                .between(PmWorkContent::getCreateTime, LocalDateTime.now().minusDays(29), LocalDateTime.now());
+        List<PmWorkContent> pmWorkContents = pmWorkContentMapper.selectList(queryWrapper);
+        if (pmWorkContents.isEmpty()) {
+            for (PmProject project : list) {
+                project.setSubmissions(0L);
+            }
+            return list;
+        }
+
+        Map<Integer, Long> projectIdCountMap = pmWorkContents.stream()
+                .collect(Collectors.groupingBy(PmWorkContent::getProjectId, Collectors.counting()));
+
+        for (PmProject project : list) {
+            Integer id = project.getId();
+            Long aLong = projectIdCountMap.getOrDefault(id, 0L);
+            project.setSubmissions(aLong);
+        }
+        // 降序排序
+        Collections.sort(list, new Comparator<PmProject>() {
+            @Override
+            public int compare(PmProject p1, PmProject p2) {
+                return Long.compare(p2.getSubmissions(), p1.getSubmissions());
+            }
+        });
+
+        return list;
+    }
+
+    /**
+     * 分页
+     *
+     * @param projectName       项目名
+     * @param projectType       类型(1:人力外包,2:项目研发,3:采购项目,4:过标项目,5:集成项目,6:其他)
+     * @param projectStatus     状态(1:未开始;2;进行中;3:已完成;4:已暂停;5:已作废)
+     * @param projectAscription 项目归属类型(0:全部;1:我负责;2:我参与)
+     * @param pageNum           页码
+     * @param pageSize          页大小
+     * @param projectId         项目id
+     * @return
+     */
+    @Override
+    public CommonPage<PmProject> projectList(String projectName, Integer projectType, Integer projectStatus, Integer projectAscription, Integer pageNum, Integer pageSize, Integer projectId, Byte visibleRange) {
+        IPage<PmProject> page = new Page<>(pageNum, pageSize);
+        Long userId = SecurityUtils.getUserId();
+        String username = SecurityUtils.getUsername();
+        String nickName = SecurityUtils.getLoginUser().getSysUser().getNickName();
+        Integer tenantId = SecurityUtils.getTenantId();
+
+        LambdaQueryWrapper<PmProject> lambdaQuery = Wrappers.lambdaQuery();
+        lambdaQuery.eq(PmProject::getTenantId, tenantId)
+                .eq(PmProject::getDelFlag, 0);
+
+        if (projectId != null && projectId != 0) {
+            lambdaQuery.eq(PmProject::getId, projectId);
+        }
+
+        switch (projectAscription) {
+            case 0:
+                if (visibleRange == null) {
+                    lambdaQuery.and(wrapper -> wrapper
+                            .and(qw -> qw
+                                    .eq(PmProject::getProjectHead, userId)
+                                    .or()
+                                    .apply("FIND_IN_SET('" + userId + "', project_member) > 0")
+                                    .or()
+                                    .eq(PmProject::getCreateBy, username)
+                            )
+                            .or()
+                            .eq(PmProject::getVisibleRange, 1)
+                    );
+                } else if (visibleRange == 2) {
+                    lambdaQuery.and(wrapper -> wrapper
+                            .eq(PmProject::getProjectHead, userId)
+                            .or()
+                            .apply("FIND_IN_SET('" + userId + "', project_member) > 0")
+                            .or()
+                            .eq(PmProject::getCreateBy, username)
+                    );
+                }
+                lambdaQuery.like(StringUtils.isNotBlank(projectName), PmProject::getProjectName, projectName)
+                        .eq(projectType != null, PmProject::getProjectType, projectType)
+                        .eq(projectStatus != null, PmProject::getProjectStatus, projectStatus);
+                break;
+            case 1:
+                lambdaQuery.eq(PmProject::getProjectHead, userId)
+                        .like(StringUtils.isNotBlank(projectName), PmProject::getProjectName, projectName)
+                        .eq(projectType != null, PmProject::getProjectType, projectType)
+                        .eq(projectStatus != null, PmProject::getProjectStatus, projectStatus);
+                break;
+            case 2:
+                lambdaQuery.apply("FIND_IN_SET('" + userId + "', project_member) > 0")
+                        .like(StringUtils.isNotBlank(projectName), PmProject::getProjectName, projectName)
+                        .eq(projectType != null, PmProject::getProjectType, projectType)
+                        .eq(projectStatus != null, PmProject::getProjectStatus, projectStatus);
+                break;
+            default:
+                throw new BusinessException("查询项目列表参数错误");
+        }
+
+        lambdaQuery.eq(visibleRange != null, PmProject::getVisibleRange, visibleRange)
+                .orderByDesc(PmProject::getCreateTime);
+        List<PmProject> pmProjects = pmProjectMapper.selectList(lambdaQuery);
+        if (pmProjects.isEmpty()) {
+            return new CommonPage<>(pmProjects, 0, pageSize, pageNum);
+        }
+
+        // 替换创建人和更新人名字(username→nickname)
+        LambdaQueryWrapper<SysUser> query = Wrappers.lambdaQuery();
+        query.select(SysUser::getUserName, SysUser::getNickName)
+                .eq(SysUser::getTenantId, tenantId)
+                .eq(SysUser::getDelFlag, 0)
+                .eq(SysUser::getStatus, "0");
+        List<SysUser> sysUsers = sysUserMapper.selectList(query);
+
+        Map<String, String> userNameToNickName = new HashMap<>();
+        for (SysUser sysUser : sysUsers) {
+            if (sysUser != null && sysUser.getUserName() != null) {
+                userNameToNickName.put(sysUser.getUserName(), nickName);
+            }
+        }
+
+        page.getRecords().forEach(project -> {
+            if (project != null) {
+                String createBy = project.getCreateBy();
+                String updateBy = project.getUpdateBy();
+                if (createBy != null) {
+                    project.setCreateBy(userNameToNickName.getOrDefault(createBy, createBy));
+                }
+                if (updateBy != null) {
+                    project.setUpdateBy(userNameToNickName.getOrDefault(updateBy, updateBy));
+                }
+            }
+        });
+
+        page = this.page(page, lambdaQuery);
+        if (page.getRecords() == null || page.getRecords().isEmpty()) {
+            return new CommonPage<>(Collections.emptyList(), 0, pageSize, pageNum);
+        }
+        return new CommonPage<>(page.getRecords(), page.getTotal(), pageSize, pageNum);
+    }
+
+    /**
+     * 查询项目名
+     *
+     * @param projectIds
+     * @return
+     */
+    @Override
+    public List<PmProject> projectName(List<Integer> projectIds) {
+        LambdaQueryWrapper<PmProject> wrapper = Wrappers.lambdaQuery();
+        wrapper.select(PmProject::getId, PmProject::getProjectName)
+                .in(PmProject::getId, projectIds);
+        return this.list(wrapper);
+    }
+
+    /**
+     * 统计
+     *
+     * @return
+     */
+    @Override
+    public Map<String, Integer> sum() {
+        Integer tenantId = SecurityUtils.getTenantId();
+        Long userId = SecurityUtils.getUserId();
+
+        LambdaQueryWrapper<PmProject> lambdaQuery = Wrappers.lambdaQuery();
+        lambdaQuery.eq(PmProject::getTenantId, tenantId)
+                .eq(PmProject::getDelFlag, 0)
+                .eq(PmProject::getVisibleRange, 2)
+                .and(q -> q.or().eq(PmProject::getProjectHead, userId)
+                        .or().apply("FIND_IN_SET('" + userId + "', project_member) > 0")
+                        .or().eq(PmProject::getCreateBy, SecurityUtils.getUsername()));
+        Integer allA = this.count(lambdaQuery);
+
+        LambdaQueryWrapper<PmProject> lambdaQuery3 = Wrappers.lambdaQuery();
+        lambdaQuery3.eq(PmProject::getTenantId, tenantId)
+                .eq(PmProject::getVisibleRange, 1)
+                .eq(PmProject::getDelFlag, 0);
+        Integer allB = this.count(lambdaQuery3);
+        int all = allA + allB;
+
+        LambdaQueryWrapper<PmProject> lambdaQuery1 = Wrappers.lambdaQuery();
+        lambdaQuery1.eq(PmProject::getTenantId, tenantId)
+                .eq(PmProject::getDelFlag, 0)
+                .eq(PmProject::getProjectHead, userId);
+        Integer head = this.count(lambdaQuery1);
+
+        LambdaQueryWrapper<PmProject> lambdaQuery2 = Wrappers.lambdaQuery();
+        lambdaQuery2.eq(PmProject::getTenantId, tenantId)
+                .eq(PmProject::getDelFlag, 0)
+                .apply("FIND_IN_SET('" + userId + "', project_member) > 0");
+        Integer join = this.count(lambdaQuery2);
+
+        Map<String, Integer> sumMap = new HashMap<>();
+        sumMap.put("allProject", all);
+        sumMap.put("headProject", head);
+        sumMap.put("joinProject", join);
+        return sumMap;
+    }
+
+
+    /**
+     * 查询所有项目负责人、成员和userid(此处逻辑为查询所有项目中的负责人、团队成员然后进行去重)
+     *
+     * @return
+     */
+    @Override
+    public List<Map<String, Object>> projectUsers(Long userId) {
+        List<Map<String, Object>> returnList = new ArrayList<>();
+        List<SysUser> userList = new ArrayList<>();
+        LambdaQueryWrapper<SysUser> queryNameId = Wrappers.lambdaQuery();
+        if (userId != null) {
+            queryNameId.select(SysUser::getUserId, SysUser::getNickName, SysUser::getUserName)
+                    .eq(SysUser::getUserId, userId);
+        } else {
+            queryNameId.select(SysUser::getUserId, SysUser::getNickName, SysUser::getUserName)
+                    .eq(SysUser::getDelFlag, 0)
+                    .eq(SysUser::getStatus, "0")
+                    .apply(Objects.nonNull(DataScopeContextHolder.getDataScopeSql()), DataScopeContextHolder.getDataScopeSql());
+        }
+        userList = sysUserMapper.selectList(queryNameId);
+        for (SysUser user : userList) {
+            Map<String, Object> map = new HashMap<>();
+            map.put("id", user.getUserId());
+            map.put("name", user.getNickName());
+            returnList.add(map);
+        }
+        return returnList;
+    }
+
+    /**
+     * 查询所有项目和id
+     *
+     * @return
+     */
+    @DataScope
+    @Override
+    public List<Map<String, Object>> projectIdName(Integer projectId) {
+        Long userId = SecurityUtils.getUserId();
+        List<Map<String, Object>> returnList = new ArrayList<>();
+        LambdaQueryWrapper<PmProject> wrapper = Wrappers.lambdaQuery();
+        wrapper.select(PmProject::getId, PmProject::getProjectName);
+        if (projectId != null) {
+            wrapper.eq(PmProject::getId, projectId);
+        } else {
+            wrapper.eq(PmProject::getDelFlag, 0)
+                    .and(qw -> qw
+                            .apply(Objects.nonNull(DataScopeContextHolder.getDataScopeSql()), DataScopeContextHolder.getDataScopeSql())
+                            .or().eq(PmProject::getProjectHead, userId)
+                            .or().eq(PmProject::getCreateBy, SecurityUtils.getUsername())
+                            .or().apply("FIND_IN_SET('" + SecurityUtils.getUserId() + "', project_member) > 0")
+                    );
+        }
+
+        List<PmProject> projects = pmProjectMapper.selectList(wrapper);
+        for (PmProject project : projects) {
+            Map<String, Object> returnMap = new HashMap<>();
+            returnMap.put("id", project.getId());
+            returnMap.put("name", project.getProjectName());
+            returnList.add(returnMap);
+        }
+        return returnList;
+    }
+
+    /**
+     * @param identifying 标识:1,返回人员userId和nickName;2:返回ProjectId和ProjectName
+     * @return
+     */
+    @Override
+    public List<Map<String, Object>> userOrProject(Integer identifying) {
+        Integer projectId = null;
+        Long userId = null;
+        List<Map<String, Object>> returnList = new ArrayList<>();
+        if (identifying == 1) {
+            returnList = projectUsers(userId);
+        } else if (identifying == 2) {
+            returnList = projectIdName(projectId);
+        }
+        return returnList;
+    }
+
+    /**
+     * 退出项目
+     *
+     * @param projectId
+     */
+    @Override
+    public void exitProject(Integer projectId) {
+        Long userId = SecurityUtils.getUserId();
+        PmProject project = pmProjectMapper.selectById(projectId);
+        String projectMember = project.getProjectMember();
+        String[] parts = projectMember.split(",");
+        String result = String.join(",", java.util.stream.Stream.of(parts)
+                .filter(part -> !part.equals(String.valueOf(userId)))
+                .collect(Collectors.toList()));
+        project.setProjectMember(result);
+        pmProjectMapper.updateById(project);
+    }
+
+    /**
+     * 查询项目工时统计-时间
+     *
+     * @param projectId
+     * @param startDate
+     * @param endDate
+     * @return
+     */
+    @Override
+    public PmProjectWorkTimeTwoVo projectWorkTime(Integer projectId, String startDate, String endDate) {
+
+        if (projectId == null || projectId <= 0) {
+            throw new BusinessException("查询项目工时项目id有误!");
+        }
+
+        PmProjectWorkTimeTwoVo workTimeCount = new PmProjectWorkTimeTwoVo();
+        List<LocalDate> dateList = new ArrayList<>();
+        List<BigDecimal> workTimeList = new ArrayList<>();
+
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+        LocalDate startTime = null;
+        LocalDate endTime = null;
+        if (StringUtils.isNotBlank(startDate) && StringUtils.isNotBlank(endDate)) {
+            try {
+                startTime = LocalDate.parse(startDate, formatter);
+                endTime = LocalDate.parse(endDate, formatter);
+            } catch (Exception e) {
+                log.error("查询项目工时时间统计时间格式有误!" + e);
+                throw new BusinessException("传入时间参数有误!");
+            }
+        } else {
+            endTime = LocalDate.now();
+            startTime = LocalDate.now().minusDays(29);
+        }
+
+        LambdaQueryWrapper<PmWorkReport> wrapper2 = Wrappers.lambdaQuery();
+        wrapper2.select(PmWorkReport::getId, PmWorkReport::getReportDate)
+                .eq(PmWorkReport::getTenantId, SecurityUtils.getTenantId())
+                .between(PmWorkReport::getReportDate, startTime, endTime)
+                .orderByAsc(PmWorkReport::getReportDate);
+        List<PmWorkReport> pmWorkReports = pmWorkReportMapper.selectList(wrapper2);
+
+        List<Integer> reportIds = pmWorkReports.stream().map(PmWorkReport::getId).collect(Collectors.toList());
+        if (reportIds.isEmpty()) {
+            return workTimeCount;
+        }
+
+        LambdaQueryWrapper<PmWorkContent> wrapper = Wrappers.lambdaQuery();
+        wrapper.select(PmWorkContent::getReportId, PmWorkContent::getSubmitterId, PmWorkContent::getWorkTime)
+                .eq(PmWorkContent::getProjectId, projectId)
+                .in(PmWorkContent::getReportId, reportIds);
+        List<PmWorkContent> pmWorkContents = pmWorkContentMapper.selectList(wrapper);
+        for (PmWorkReport report : pmWorkReports) {
+            Integer id = report.getId();
+            LocalDate reportDate = report.getReportDate();
+            if (pmWorkContents.stream().anyMatch(content -> content.getReportId().equals(id))) {
+                BigDecimal totalWorkTime = pmWorkContents.stream()
+                        .filter(content -> content.getReportId().equals(id))
+                        .map(PmWorkContent::getWorkTime)
+                        .reduce(BigDecimal.ZERO, BigDecimal::add);
+                dateList.add(reportDate);
+                workTimeList.add(totalWorkTime);
+            }
+        }
+
+        Map<LocalDate, BigDecimal> accumulatedWorkTimeMap = new LinkedHashMap<>();
+        for (int i = 0; i < dateList.size(); i++) {
+            LocalDate date = dateList.get(i);
+            BigDecimal workTime = workTimeList.get(i);
+            accumulatedWorkTimeMap.merge(date, workTime, BigDecimal::add);
+        }
+        List<LocalDate> newDateList = new ArrayList<>(accumulatedWorkTimeMap.keySet());
+        List<BigDecimal> newWorkTimeList = new ArrayList<>(accumulatedWorkTimeMap.values());
+
+        workTimeCount.setDateList(newDateList);
+        workTimeCount.setWorkTimeList(newWorkTimeList);
+
+        return workTimeCount;
+    }
+
+    /**
+     * @description: 查询人员项目工时统计
+     * @author: fu
+     * @date: 2024/8/12 15:44
+     * @param: [projectId]
+     * @return: java.util.List<java.util.Map < java.lang.String, java.math.BigDecimal>>
+     **/
+    @Override
+    public PmUsersProjectWorkTimeVO usersProjectWorkTime(Integer projectId, String startDate, String endDate) {
+
+        if (projectId == null || projectId <= 0) {
+            throw new BusinessException("查询项目工时项目id有误!");
+        }
+
+        PmUsersProjectWorkTimeVO workTimeCount = new PmUsersProjectWorkTimeVO();
+
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+        LocalDate startTime = null;
+        LocalDate endTime = null;
+        if (StringUtils.isNotBlank(startDate) && StringUtils.isNotBlank(endDate)) {
+            try {
+                startTime = LocalDate.parse(startDate, formatter);
+                endTime = LocalDate.parse(endDate, formatter);
+            } catch (Exception e) {
+                log.error("查询人员项目工时统计时间格式有误!" + e);
+                throw new BusinessException("传入时间参数有误!");
+            }
+        } else {
+            startTime = LocalDate.now();
+            endTime = LocalDate.now().minusDays(29);
+        }
+
+        LambdaQueryWrapper<PmWorkReport> wrapper2 = Wrappers.lambdaQuery();
+        wrapper2.select(PmWorkReport::getId);
+        wrapper2.between(PmWorkReport::getReportDate, startTime, endTime);
+        List<Integer> reportIds = pmWorkReportMapper.selectList(wrapper2).stream().map(PmWorkReport::getId).collect(Collectors.toList());
+
+        // 项目成员与实际填写报告的人员可能会有差异
+        LambdaQueryWrapper<PmWorkContent> wrapper = Wrappers.lambdaQuery();
+        wrapper.select(PmWorkContent::getSubmitterId, PmWorkContent::getWorkTime)
+                .eq(PmWorkContent::getProjectId, projectId)
+                .eq(PmWorkContent::getTenantId, SecurityUtils.getTenantId());
+        if (!reportIds.isEmpty()) {
+            wrapper.in(PmWorkContent::getReportId, reportIds);
+        }
+        List<PmWorkContent> pmWorkContents = pmWorkContentMapper.selectList(wrapper);
+        Set<Long> userIds = pmWorkContents.stream().map(PmWorkContent::getSubmitterId).collect(Collectors.toSet());
+
+        LambdaQueryWrapper<PmProject> wrapper1 = Wrappers.lambdaQuery();
+        wrapper1.select(PmProject::getProjectMember)
+                .eq(PmProject::getId, projectId);
+        if (StringUtils.isNotBlank(pmProjectMapper.selectOne(wrapper1).getProjectMember())) {
+            List<Long> userIds1 = Arrays.stream(pmProjectMapper.selectOne(wrapper1).getProjectMember().split(",")).map(Long::parseLong).collect(Collectors.toList());
+            userIds.addAll(userIds1);
+        }
+
+        List<Long> userIds1 = new ArrayList<>(userIds);
+
+        if (userIds1.isEmpty()) {
+            return workTimeCount;
+        }
+
+        List<SysUser> sysUsers = pmWorkContentService.nickNames(userIds1);
+        BigDecimal totalWorkTime = BigDecimal.ZERO;
+
+        List<String> users2 = new ArrayList<>();
+        List<BigDecimal> workTime1 = new ArrayList<>();
+
+        for (SysUser sysUser : sysUsers) {
+            users2.add(sysUser.getNickName());
+            BigDecimal userWorkTime = pmWorkContents.stream()
+                    .filter(pmWorkContent -> pmWorkContent.getSubmitterId().equals(sysUser.getUserId()))
+                    .map(PmWorkContent::getWorkTime)
+                    .reduce(BigDecimal.ZERO, BigDecimal::add);
+            workTime1.add(userWorkTime);
+            totalWorkTime = totalWorkTime.add(userWorkTime);
+        }
+        workTimeCount.setUsers(users2);
+        if (reportIds.isEmpty()) {
+            List<BigDecimal> workTime = IntStream.range(0, users2.size())
+                    .mapToObj(index -> BigDecimal.ZERO)
+                    .collect(Collectors.toList());
+            workTimeCount.setWorkTime(workTime);
+            return workTimeCount;
+        }
+        workTimeCount.setWorkTime(workTime1);
+
+        return workTimeCount;
+    }
+
+    /**
+     * @description: 项目成员加入项目
+     * @author: fu
+     * @date: 2024/8/12 15:44
+     * @param: [projectId]
+     * @return: void
+     **/
+    @Override
+    public void joinProject(Integer projectId) {
+        Long userId = SecurityUtils.getUserId();
+        PmProject pmProject = pmProjectMapper.selectById(projectId);
+        if (pmProject == null) {
+            throw new BusinessException("加入项目不存在!请刷新后重试");
+        }
+        pmProject.setProjectMember(pmProject.getProjectMember() + "," + userId);
+        try {
+            pmProjectMapper.updateById(pmProject);
+        } catch (Exception e) {
+            throw new BusinessException("加入项目失败!请联系管理员");
+        }
+    }
+
+}

+ 74 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/service/impl/PmReceiveServiceImpl.java

@@ -0,0 +1,74 @@
+package com.usky.pm.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.ruoyi.common.datascope.annotation.DataScope;
+import com.ruoyi.common.datascope.context.DataScopeContextHolder;
+import com.usky.common.security.utils.SecurityUtils;
+import com.usky.pm.domain.PmReceive;
+import com.usky.pm.mapper.PmReceiveMapper;
+import com.usky.pm.mapper.SysUserMapper;
+import com.usky.pm.service.PmReceiveService;
+import com.usky.common.mybatis.core.AbstractCrudService;
+import com.usky.system.domain.SysUser;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * <p>
+ * 报告接收表 服务实现类
+ * </p>
+ *
+ * @author han
+ * @since 2024-07-09
+ */
+@Slf4j
+@Service
+public class PmReceiveServiceImpl extends AbstractCrudService<PmReceiveMapper, PmReceive> implements PmReceiveService {
+
+    @Autowired
+    private PmReceiveMapper pmReceiveMapper;
+
+    @Autowired
+    private SysUserMapper sysUserMapper;
+
+    @Override
+    public void add(PmReceive receive) {
+
+    }
+
+    @Override
+    public void updateReadFlag(Integer reportId) {
+        Long userId = SecurityUtils.getUserId();
+        LambdaUpdateWrapper<PmReceive> updateWrapper = Wrappers.lambdaUpdate();
+        updateWrapper
+                .set(PmReceive::getReadFlag, 1)
+                .set(PmReceive::getUpdateBy, SecurityUtils.getUsername())
+                .set(PmReceive::getUpdateTime, LocalDateTime.now())
+                .eq(PmReceive::getReportId, reportId)
+                .eq(PmReceive::getReceiverId, userId);
+        pmReceiveMapper.update(null, updateWrapper);
+    }
+
+
+
+    @DataScope
+    @Override
+    public List<SysUser> test(Integer tenantId) {
+        String dataScopeSql = DataScopeContextHolder.getDataScopeSql();
+        log.info("DataScopeContextHolder: "+ dataScopeSql);
+        LambdaQueryWrapper<SysUser> usersQuery = Wrappers.lambdaQuery();
+        usersQuery.select(SysUser::getUserId, SysUser::getUserName)
+                .eq(SysUser::getDelFlag, 0).eq(SysUser::getStatus, tenantId)
+                .apply(Objects.nonNull(dataScopeSql), dataScopeSql);
+        return sysUserMapper.selectList(usersQuery);
+    }
+
+
+}

+ 413 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/service/impl/PmTimeConfServiceImpl.java

@@ -0,0 +1,413 @@
+package com.usky.pm.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.usky.common.core.bean.CommonPage;
+import com.usky.common.core.exception.BusinessException;
+import com.usky.common.security.utils.SecurityUtils;
+import com.usky.pm.domain.PmReceive;
+import com.usky.pm.domain.PmTimeConf;
+import com.usky.pm.domain.PmWorkContent;
+import com.usky.pm.domain.PmWorkReport;
+import com.usky.pm.mapper.*;
+import com.usky.pm.service.PmTimeConfService;
+import com.usky.common.mybatis.core.AbstractCrudService;
+import com.usky.pm.service.vo.PmReportReadersVO;
+import com.usky.pm.service.vo.PmSubmitCountResponseVO;
+import com.usky.system.domain.SysUser;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * <p>
+ * 工作报告提交时间配置表 服务实现类
+ * </p>
+ *
+ * @author fu
+ * @since 2025-01-10
+ */
+@Service
+public class PmTimeConfServiceImpl extends AbstractCrudService<PmTimeConfMapper, PmTimeConf> implements PmTimeConfService {
+
+    @Autowired
+    private PmTimeConfMapper pmTimeConfMapper;
+
+    @Autowired
+    private PmWorkReportMapper pmWorkReportMapper;
+
+    @Autowired
+    private SysUserMapper sysUserMapper;
+
+    @Autowired
+    private PmWorkContentMapper pmWorkContentMapper;
+
+    @Autowired
+    private PmReceiveMapper pmReceiveMapper;
+
+    // 统计工作报提交
+    @Override
+    public PmSubmitCountResponseVO submitCount(String submitDate) {
+        Integer tenantId = SecurityUtils.getTenantId();
+        PmSubmitCountResponseVO responseVO = new PmSubmitCountResponseVO();
+        LocalDate countDate = null;
+        try {
+            countDate = LocalDate.parse(submitDate);
+        } catch (Exception e) {
+            throw new BusinessException("提交日期有误" + e.getMessage());
+        }
+
+        // 查询时间配置
+        PmTimeConf timeConf = getTimeConf(tenantId);
+        if (timeConf == null) {
+            throw new RuntimeException("未找到工作报提交统计告时间配置,请联系管理员");
+        } else if (countDate.equals(LocalDate.now()) && timeConf.getStartTime().isAfter(LocalTime.now()) || countDate.isAfter(LocalDate.now())) {
+            responseVO.setSubmitOnTime(0);
+            responseVO.setSubmitLate(0);
+            responseVO.setNotSubmitted(0);
+            responseVO.setStatisticalDate(countDate);
+            return responseVO;
+        }
+
+        LocalTime startTime = timeConf.getStartTime();
+        LocalTime onTime = timeConf.getOnTime();
+        LocalTime endTime = timeConf.getEndTime();
+        LocalDateTime startDateTime = LocalDateTime.of(countDate, startTime);
+        LocalDateTime onDateTime = LocalDateTime.of(countDate, onTime);
+        LocalDateTime endDateTime = LocalDateTime.of(countDate, endTime).plusDays(1);
+
+        // 按时提交
+        List<PmWorkReport> pmWorkReports = reportList(tenantId, 0, startDateTime, onDateTime, endDateTime);
+        Integer submitOnTime = pmWorkReports.size();
+
+        // 延迟提交
+        List<PmWorkReport> pmWorkReports2 = reportList(tenantId, 1, startDateTime, onDateTime, endDateTime);
+        Integer submitLate = pmWorkReports2.size();
+
+        Integer suerSum = users(tenantId).size();
+        // 未提交
+        Integer notSubmitted = suerSum - submitOnTime - submitLate;
+
+        responseVO.setSubmitOnTime(submitOnTime);
+        responseVO.setSubmitLate(submitLate);
+        responseVO.setNotSubmitted(notSubmitted);
+        responseVO.setStatisticalDate(countDate);
+        return responseVO;
+    }
+
+    @Override
+    public CommonPage<Object> submitPage(String submitDate, Integer queryType, Integer reportId, Integer pageNum, Integer pageSize) {
+        Integer tenantId = SecurityUtils.getTenantId();
+        Long userId2 = SecurityUtils.getUserId();
+        LocalDate countDate = null;
+        try {
+            countDate = LocalDate.parse(submitDate);
+        } catch (Exception e) {
+            throw new BusinessException("提交日期有误" + e.getMessage());
+        }
+
+        List<SysUser> userList = users(tenantId);
+
+        PmTimeConf timeConf = getTimeConf(tenantId);
+        if (timeConf == null) {
+            throw new BusinessException("未找到工作报提交统计告时间配置,请联系管理员");
+        }
+        if ((LocalDate.now().equals(countDate) && timeConf.getStartTime().isAfter(LocalTime.now())) ||
+                countDate.isAfter(LocalDate.now())) {
+            return new CommonPage<>();
+        }
+
+        LocalTime startTime = timeConf.getStartTime();
+        LocalTime onTime = timeConf.getOnTime();
+        LocalTime endTime = timeConf.getEndTime();
+        LocalDateTime startDateTime = LocalDateTime.of(countDate, startTime);
+        LocalDateTime onDateTime = LocalDateTime.of(countDate, onTime);
+        LocalDateTime endDateTime = LocalDateTime.of(countDate, endTime).plusDays(1);
+
+        LambdaQueryWrapper<PmWorkReport> reportQuery = Wrappers.lambdaQuery();
+        reportQuery.eq(PmWorkReport::getTenantId, tenantId).eq(PmWorkReport::getReportStatus, 1);
+
+        if (reportId != null && reportId != 0) {
+            reportQuery.eq(PmWorkReport::getId, reportId);
+        } else {
+            switch (queryType) {
+                // 按时提交
+                case 0:
+                    reportQuery.between(PmWorkReport::getSubmitDate, startDateTime, onDateTime);
+                    break;
+                // 延迟提交
+                case 1:
+                    reportQuery.between(PmWorkReport::getSubmitDate, onDateTime, endDateTime);
+                    break;
+                // 未提交
+                case 2:
+                    List<Long> userId = new ArrayList<>();
+                    // 按时提交
+                    List<PmWorkReport> pmWorkReports = reportList(tenantId, 0, startDateTime, onDateTime, endDateTime);
+                    pmWorkReports.forEach(report -> {
+                        userId.add(report.getSubmitterId());
+                    });
+                    // 延迟提交
+                    List<PmWorkReport> pmWorkReports2 = reportList(tenantId, 1, startDateTime, onDateTime, endDateTime);
+                    pmWorkReports2.forEach(report -> {
+                        userId.add(report.getSubmitterId());
+                    });
+                    // 未提交
+                    List<SysUser> unSubmitted = userList.stream().filter(user -> !userId.contains(user.getUserId())).collect(Collectors.toList());
+
+                    int total = unSubmitted.size();
+                    int start = (pageNum - 1) * pageSize;
+                    int end = Math.min(start + pageSize, total);
+
+                    List<SysUser> pageData;
+                    if (start < total) {
+                        pageData = unSubmitted.subList(start, end);
+                    } else {
+                        pageData = Collections.emptyList();
+                    }
+                    return new CommonPage<>(Collections.singletonList(pageData), total, pageSize, pageNum);
+
+                default:
+                    throw new BusinessException("报告统计分页参数错误!");
+            }
+            if (countDate.equals(LocalDate.now()) && timeConf.getStartTime().isAfter(LocalTime.now())) {
+                return new CommonPage<>();
+            }
+            reportQuery.eq(PmWorkReport::getReportDate, countDate);
+        }
+
+        List<PmWorkReport> pmWorkReports = pmWorkReportMapper.selectList(reportQuery);
+        if (pmWorkReports.isEmpty()) {
+            return new CommonPage<>();
+        }
+        List<Integer> reportIds = new ArrayList<>();
+        pmWorkReports.forEach(report -> {
+            reportIds.add(report.getId());
+        });
+
+        LambdaQueryWrapper<PmWorkContent> reportContents = Wrappers.lambdaQuery();
+        reportContents.in(PmWorkContent::getReportId, reportIds);
+        List<PmWorkContent> pmWorkContentList = pmWorkContentMapper.selectList(reportContents);
+
+        // 查询已读状态
+        LambdaQueryWrapper<PmReceive> statusQuery = Wrappers.lambdaQuery();
+        statusQuery.select(PmReceive::getReportId, PmReceive::getReadFlag).eq(PmReceive::getReceiverId, userId2);
+        if (reportId != null && reportId != 0) {
+            statusQuery.eq(PmReceive::getReportId, reportId);
+        } else {
+            statusQuery.in(PmReceive::getReportId, reportIds);
+        }
+        List<PmReceive> receiveList2 = pmReceiveMapper.selectList(statusQuery);
+
+        Map<Integer, Integer> reportReadFlags = new HashMap<>();
+        for (PmReceive pmReceive : receiveList2) {
+            Integer reportId2 = pmReceive.getReportId();
+            reportReadFlags.put(reportId2, pmReceive.getReadFlag());
+        }
+
+        List<Long> userIds = new ArrayList<>();
+
+        // 已读未读数量查询
+        List<PmReceive> receives = new ArrayList<>();
+        receives = receives(reportIds);
+        Map<Integer, List<PmReceive>> reportReceivesMap = new HashMap<>();
+        reportReceivesMap = receives.stream().collect(Collectors.groupingBy(PmReceive::getReportId));
+        for (PmWorkReport report : pmWorkReports) {
+            List<PmWorkContent> contents = pmWorkContentList.stream().filter(content -> content.getReportId().equals(report.getId())).collect(Collectors.toList());
+            report.setWorkContents(contents);
+            userIds.add(report.getSubmitterId());
+
+            Integer readFlagValue = reportReadFlags.get(report.getId());
+            report.setReadFlag(readFlagValue);
+
+            // 创建人名字替换
+            if (!userIds.isEmpty()) {
+                List<SysUser> nickNames = nickNames(userIds);
+                Map<Long, String> userNicknameMap = nickNames.stream().collect(Collectors.toMap(SysUser::getUserId, SysUser::getNickName));
+                report.setCreateBy(userNicknameMap.get(report.getSubmitterId()));
+            }
+
+            List<Long> readAlready = new ArrayList<>();
+            List<Long> readNotAlready = new ArrayList<>();
+            int readCount = 0;
+            int unreadCount = 0;
+            if (!reportReceivesMap.isEmpty()) {
+                List<PmReceive> reportReceives = reportReceivesMap.getOrDefault(report.getId(), Collections.emptyList());
+                for (PmReceive pmReceive : reportReceives) {
+                    if (pmReceive.getReadFlag() == 1) {
+                        readCount++;
+                        readAlready.add(pmReceive.getReceiverId());
+                    } else {
+                        unreadCount++;
+                        readNotAlready.add(pmReceive.getReceiverId());
+                    }
+                }
+            }
+
+            PmReportReadersVO readUnreadVO = new PmReportReadersVO(readAlready, readNotAlready, readCount, unreadCount);
+            report.setPmReportReaders(readUnreadVO);
+
+            // 头像
+            for (SysUser sysUser : userList) {
+                if (sysUser.getUserId().equals(report.getSubmitterId())) {
+                    report.setAvatar(sysUser.getAvatar());
+                    break;
+                }
+            }
+        }
+
+        int total = pmWorkReports.size();
+        int start = (pageNum - 1) * pageSize;
+        int end = Math.min(start + pageSize, total);
+
+        List<PmWorkReport> pageData;
+        if (start < total) {
+            pageData = pmWorkReports.subList(start, end);
+        } else {
+            pageData = Collections.emptyList();
+        }
+
+        return new CommonPage<>(Collections.singletonList(pageData), total, pageSize, pageNum);
+    }
+
+    // 查询已读未读
+    private List<PmReceive> receives(List<Integer> reports) {
+        LambdaQueryWrapper<PmReceive> queryWrapper = Wrappers.lambdaQuery();
+        queryWrapper.select(PmReceive::getReportId, PmReceive::getReadFlag, PmReceive::getReceiverId).in(PmReceive::getReportId, reports);
+        return pmReceiveMapper.selectList(queryWrapper);
+    }
+
+    // 构建查询条件,返回按时提交和延迟提交列表
+    private List<PmWorkReport> reportList(Integer tenantId, Integer queryType, LocalDateTime startDateTime, LocalDateTime onDateTime, LocalDateTime endDateTime) {
+
+        LocalDate countDate = startDateTime.toLocalDate();
+
+        LambdaQueryWrapper<PmWorkReport> reportQuery = new LambdaQueryWrapper<>();
+        reportQuery.eq(PmWorkReport::getTenantId, tenantId)
+                .eq(PmWorkReport::getReportDate, countDate)
+                .eq(PmWorkReport::getReportStatus, 1);
+        switch (queryType) {
+            // 按时提交
+            case 0:
+                reportQuery.between(PmWorkReport::getSubmitDate, startDateTime, onDateTime);
+                break;
+            // 延迟提交
+            case 1:
+                reportQuery.between(PmWorkReport::getSubmitDate, onDateTime, endDateTime);
+                break;
+        }
+        return pmWorkReportMapper.selectList(reportQuery);
+    }
+
+    // 获取报告时间配置
+    @Override
+    public PmTimeConf getTimeConf(Integer tenantId) {
+        LambdaQueryWrapper<PmTimeConf> timeConfQuery = new LambdaQueryWrapper<>();
+        timeConfQuery.eq(PmTimeConf::getTenantId, tenantId)
+                .eq(PmTimeConf::getConfType, "PM");
+        return pmTimeConfMapper.selectOne(timeConfQuery);
+    }
+
+    @Override
+    public void addOrUpdateTimeConf(PmTimeConf pmTimeConf) {
+        String username = SecurityUtils.getUsername();
+        Long deptId = SecurityUtils.getLoginUser().getSysUser().getDeptId();
+        LocalDateTime now = LocalDateTime.now();
+        if (pmTimeConf.getId() == null) {
+            Integer tenantId = SecurityUtils.getTenantId();
+            verificationTimeConf(pmTimeConf);
+            pmTimeConf.setTenantId(tenantId);
+            pmTimeConf.setCreateBy(username);
+            pmTimeConf.setCreateTime(now);
+            pmTimeConf.setDeptId(deptId);
+            pmTimeConfMapper.insert(pmTimeConf);
+        } else {
+            verificationTimeConf(pmTimeConf);
+            pmTimeConf.setUpdateBy(username);
+            pmTimeConf.setUpdateTime(now);
+            pmTimeConfMapper.updateById(pmTimeConf);
+        }
+    }
+
+    @Override
+    public void isOpen(Integer isOpen, Integer id) {
+        Long userId = SecurityUtils.getUserId();
+        String username = SecurityUtils.getUsername();
+        LocalDateTime now = LocalDateTime.now();
+        PmTimeConf pmTimeConf = pmTimeConfMapper.selectById(id);
+        if (pmTimeConf == null){
+            throw new BusinessException("配置不存在!联系管理员后重试");
+        }
+
+        if (isOpen == 1) {
+            pmTimeConf.setNotifier(pmTimeConf.getNotifier() + "," + userId);
+        } else if (isOpen == 0) {
+            pmTimeConf.setNotifier(pmTimeConf.getNotifier().replace("," + userId, ""));
+        } else {
+            throw new BusinessException("参数异常,报告提醒开关操作失败!");
+        }
+
+        pmTimeConf.setUpdateBy(username);
+        pmTimeConf.setUpdateTime(now);
+        pmTimeConfMapper.updateById(pmTimeConf);
+    }
+
+    private void verificationTimeConf(PmTimeConf pmTimeConf) {
+        if (StringUtils.isBlank(pmTimeConf.getConfName())) {
+            throw new BusinessException("配置名称不能为空!");
+        } else if (pmTimeConf.getConfName().length() > 64) {
+            throw new BusinessException("配置名称长度不能超过64个字符!");
+        }
+
+        if (StringUtils.isBlank(pmTimeConf.getConfType())) {
+            throw new BusinessException("配置类型不能为空!");
+        } else if (pmTimeConf.getConfType().length() > 64) {
+            throw new BusinessException("配置类型长度不能超过64个字符!");
+        }
+
+        if (pmTimeConf.getStartTime() == null) {
+            throw new BusinessException("开始时间不能为空!");
+        } else if (pmTimeConf.getStartTime().isAfter(pmTimeConf.getEndTime())) {
+            throw new BusinessException("开始时间不能大于结束时间!");
+        }
+
+        if (pmTimeConf.getEndTime() == null) {
+            throw new BusinessException("结束时间不能为空!");
+        } else if (pmTimeConf.getEndTime().isBefore(pmTimeConf.getStartTime())) {
+            throw new BusinessException("结束时间不能小于开始时间!");
+        }
+
+        if (pmTimeConf.getOnTime() == null) {
+            throw new BusinessException("准时时间不能为空!");
+        } else if (pmTimeConf.getOnTime().isBefore(pmTimeConf.getStartTime()) || pmTimeConf.getOnTime().isAfter(pmTimeConf.getEndTime())) {
+            throw new BusinessException("准时时间必须在开始时间和结束时间之间!");
+        }
+    }
+
+    // 查询用户信息
+    private List<SysUser> users(Integer tenantId) {
+        LambdaQueryWrapper<SysUser> userQuery = new LambdaQueryWrapper<>();
+        userQuery.select(SysUser::getUserId, SysUser::getDeptId, SysUser::getUserName, SysUser::getNickName, SysUser::getPhonenumber,
+                        SysUser::getAvatar, SysUser::getSex, SysUser::getAddress, SysUser::getEmail)
+                .eq(SysUser::getTenantId, tenantId)
+                .eq(SysUser::getStatus, 0)
+                .eq(SysUser::getDelFlag, 0)
+                .eq(SysUser::getUserType, "00");
+        return sysUserMapper.selectList(userQuery);
+    }
+
+    // 获取用户昵称
+    private List<SysUser> nickNames(List<Long> userIds) {
+        LambdaQueryWrapper<SysUser> userQuery = new LambdaQueryWrapper<>();
+        userQuery.select(SysUser::getUserId, SysUser::getNickName)
+                .in(SysUser::getUserId, userIds);
+        return sysUserMapper.selectList(userQuery);
+    }
+
+}

+ 1474 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/service/impl/PmWorkContentServiceImpl.java

@@ -0,0 +1,1474 @@
+package com.usky.pm.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.common.datascope.annotation.DataScope;
+import com.ruoyi.common.datascope.context.DataScopeContextHolder;
+import com.usky.common.core.bean.CommonPage;
+import com.usky.common.core.exception.BusinessException;
+import com.usky.common.mybatis.core.AbstractCrudService;
+import com.usky.common.security.utils.SecurityUtils;
+import com.usky.pm.domain.PmProject;
+import com.usky.pm.domain.PmReceive;
+import com.usky.pm.domain.PmWorkContent;
+import com.usky.pm.domain.PmWorkReport;
+import com.usky.pm.mapper.*;
+import com.usky.pm.service.PmWorkContentService;
+import com.usky.pm.service.vo.*;
+import com.usky.system.domain.SysUser;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.TemporalAdjusters;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * <p>
+ * 工作内容表 服务实现类
+ * </p>
+ *
+ * @author fu
+ * @since 2024-05-20
+ */
+@Slf4j
+@Service
+public class PmWorkContentServiceImpl extends AbstractCrudService<PmWorkContentMapper, PmWorkContent> implements PmWorkContentService {
+
+    @Autowired
+    private PmWorkReportMapper pmWorkReportMapper;
+
+    @Autowired
+    private SysUserMapper sysUserMapper;
+
+    @Autowired
+    private PmReceiveMapper pmReceiveMapper;
+
+    @Autowired
+    private PmWorkContentMapper pmWorkContentMapper;
+
+    @Autowired
+    private PmProjectMapper pmProjectMapper;
+
+    /**
+     * @description: 删除报告内容
+     * @author: fu
+     * @date: 2024/8/7 17:48
+     * @param: [reportId]
+     * @return: void
+     **/
+    @Override
+    public void deleteContent(Integer reportId) {
+        LambdaQueryWrapper<PmWorkContent> deleteWrapper = Wrappers.lambdaQuery();
+        deleteWrapper.eq(PmWorkContent::getReportId, reportId);
+        baseMapper.delete(deleteWrapper);
+    }
+
+    /**
+     * @description: 查询报告id时间段
+     * @author: fu
+     * @date: 2024/8/7 17:48
+     * @param: [startDate, endDate]
+     * @return: java.util.List<java.lang.Integer>
+     **/
+    private List<Integer> reportDateQuery(String startDate, String endDate) {
+        Long deptId = SecurityUtils.getLoginUser().getSysUser().getDeptId();
+        Integer tenantId = SecurityUtils.getTenantId();
+        LambdaQueryWrapper<PmWorkReport> reportQuery = Wrappers.lambdaQuery();
+        reportQuery.select(PmWorkReport::getId).eq(PmWorkReport::getTenantId, tenantId).eq(PmWorkReport::getDeptId, deptId);
+        if (StringUtils.isNotBlank(startDate) && StringUtils.isNotBlank(endDate)) {
+            LocalDate start = null;
+            LocalDate end = null;
+            try {
+                start = LocalDate.parse(startDate);
+                end = LocalDate.parse(endDate);
+            } catch (Exception e) {
+                log.error("时间格式错误:", e);
+                throw new BusinessException("时间格有误,请重新输入");
+            }
+            reportQuery.between(PmWorkReport::getReportDate, start, end);
+        }
+        List<PmWorkReport> reportIds = pmWorkReportMapper.selectList(reportQuery);
+        List<Integer> rIds = new ArrayList<>();
+        for (PmWorkReport report : reportIds) {
+            rIds.add(report.getId());
+        }
+        return rIds;
+    }
+
+    /**
+     * @description: 查询我收到的报告
+     * @author: fu
+     * @date: 2024/8/7 17:48
+     * @param: []
+     * @return: java.util.List<java.lang.Integer>
+     **/
+    private List<Integer> receive() {
+        Long userId = SecurityUtils.getUserId();
+        Integer tenantId = SecurityUtils.getTenantId();
+        LambdaQueryWrapper<PmWorkReport> reportQuery = Wrappers.lambdaQuery();
+        reportQuery.select(PmWorkReport::getId)
+                .eq(PmWorkReport::getTenantId, tenantId)
+                .apply("FIND_IN_SET(" + userId + ", cc_to) > 0")
+                .orderByDesc(PmWorkReport::getReportDate);
+        return pmWorkReportMapper.selectList(reportQuery).stream().map(PmWorkReport::getId).collect(Collectors.toList());
+    }
+
+    /**
+     * @description: 查询我发出的报告
+     * @author: fu
+     * @date: 2024/8/7 17:48
+     * @param: []
+     * @return: java.util.List<java.lang.Integer>
+     **/
+    private List<Integer> sentOut() {
+        Long userid = SecurityUtils.getUserId();
+        LambdaQueryWrapper<PmWorkReport> reportQuery = Wrappers.lambdaQuery();
+        reportQuery.select(PmWorkReport::getId).eq(PmWorkReport::getSubmitterId, userid).orderByDesc(PmWorkReport::getSubmitDate);
+        return pmWorkReportMapper.selectList(reportQuery).stream().map(PmWorkReport::getId).collect(Collectors.toList());
+    }
+
+    /**
+     * @description: 负责人项目id查询
+     * @author: fu
+     * @date: 2024/8/7 17:14
+     * @param: []
+     * @return: java.util.List<java.lang.Integer>
+     **/
+    private List<Integer> head() {
+        Long userId = SecurityUtils.getUserId();
+        Integer tenantId = SecurityUtils.getTenantId();
+        LambdaQueryWrapper<PmProject> projectQuery = Wrappers.lambdaQuery();
+        projectQuery.select(PmProject::getId).eq(PmProject::getTenantId, tenantId).eq(PmProject::getProjectHead, userId);
+        return pmProjectMapper.selectList(projectQuery).stream().map(PmProject::getId).collect(Collectors.toList());
+    }
+
+    /**
+     * @description: 查询项目id
+     * @author: fu
+     * @date: 2024/8/7 17:14
+     * @param: []
+     * @return: java.util.List<java.lang.Integer>
+     **/
+    private List<Integer> reportList(String projectName) {
+        Integer tenantId = SecurityUtils.getTenantId();
+        LambdaQueryWrapper<PmWorkContent> projectQuery = Wrappers.lambdaQuery();
+        projectQuery.select(PmWorkContent::getReportId).eq(PmWorkContent::getProjectName, projectName).eq(PmWorkContent::getTenantId, tenantId);
+        List<PmWorkContent> projectIds = pmWorkContentMapper.selectList(projectQuery);
+        List<Integer> rIds = new ArrayList<>();
+        for (PmWorkContent contenReportId : projectIds) {
+            rIds.add(contenReportId.getId());
+        }
+        return rIds;
+    }
+
+    /**
+     * @description: 查询项目
+     * @author: fu
+     * @date: 2024/8/7 17:14
+     * @param: [projectIds]
+     * @return: java.util.List<com.usky.pm.domain.PmProject>
+     **/
+    @Override
+    public List<PmWorkContent> projectQuery(String startDate, String endDate, String projectName, Integer projectAscription, Long submitterId) {
+        LocalDateTime startDateTime = null;
+        LocalDateTime endDateTime = null;
+        LocalDate start = null;
+        LocalDate end = null;
+        List<PmWorkContent> workContentList = new ArrayList<>();
+        List<Integer> reportIds = new ArrayList<>();
+
+        Set<Integer> reportIdsByDate = new HashSet<>();
+        Set<Integer> reportIdsBySubmitterId = new HashSet<>();
+        Set<Integer> finalReportIds = new HashSet<>();
+
+        if (StringUtils.isNotBlank(startDate) && StringUtils.isNotBlank(endDate)) {
+            DateTimeFormatter formatter = null;
+            try {
+                formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+            } catch (Exception e) {
+                throw new BusinessException("时间传参格式不正确请重试");
+            }
+            start = LocalDate.parse(startDate, formatter);
+            end = LocalDate.parse(endDate, formatter);
+            startDateTime = start.atStartOfDay();
+            endDateTime = end.atTime(23, 59, 59);
+            LambdaQueryWrapper<PmWorkReport> reportQuery = Wrappers.lambdaQuery();
+            reportQuery.select(PmWorkReport::getId).between(PmWorkReport::getSubmitDate, startDateTime, endDateTime);
+            reportIdsByDate = pmWorkReportMapper.selectList(reportQuery).stream().map(PmWorkReport::getId).collect(Collectors.toSet());
+        }
+
+        if (submitterId != null) {
+            LambdaQueryWrapper<PmWorkReport> reportQuery = Wrappers.lambdaQuery();
+            reportQuery.select(PmWorkReport::getId).eq(PmWorkReport::getSubmitterId, submitterId);
+            reportIdsBySubmitterId = pmWorkReportMapper.selectList(reportQuery).stream().map(PmWorkReport::getId).collect(Collectors.toSet());
+        }
+
+        // 取交集
+        if (!reportIdsByDate.isEmpty() && !reportIdsBySubmitterId.isEmpty()) {
+            finalReportIds.addAll(reportIdsByDate);
+            finalReportIds.retainAll(reportIdsBySubmitterId);
+        } else if (!reportIdsByDate.isEmpty()) {
+            finalReportIds.addAll(reportIdsByDate);
+        } else if (!reportIdsBySubmitterId.isEmpty()) {
+            finalReportIds.addAll(reportIdsBySubmitterId);
+        }
+        reportIds = new ArrayList<>(finalReportIds);
+
+
+        LambdaQueryWrapper<PmWorkContent> contentLambdaQuery = Wrappers.lambdaQuery();
+        contentLambdaQuery.select(PmWorkContent::getProjectId);
+        switch (projectAscription) {
+            case 1:
+                List<Integer> headProjects = head();
+                if (headProjects.isEmpty()) {
+                    return workContentList;
+                }
+                contentLambdaQuery.in(PmWorkContent::getProjectId, headProjects);
+                break;
+            case 2:
+                List<Integer> receiveList = receive();
+                if (receiveList.isEmpty()) {
+                    return workContentList;
+                }
+                contentLambdaQuery.in(PmWorkContent::getReportId, receiveList);
+                break;
+            case 3:
+                List<Integer> sentOutList = sentOut();
+                if (sentOutList.isEmpty()) {
+                    return workContentList;
+                }
+                contentLambdaQuery.in(PmWorkContent::getReportId, sentOutList);
+                break;
+            default:
+                throw new BusinessException("查询标识有误");
+        }
+        if (StringUtils.isNotBlank(projectName)) {
+            contentLambdaQuery.like(PmWorkContent::getProjectName, projectName);
+        }
+        if (!reportIds.isEmpty()) {
+            contentLambdaQuery.in(PmWorkContent::getReportId, reportIds);
+        }
+        contentLambdaQuery.groupBy(PmWorkContent::getProjectId);
+        workContentList = pmWorkContentMapper.selectList(contentLambdaQuery);
+
+        List<Integer> workContentIds = workContentList.stream().map(PmWorkContent::getProjectId).distinct().collect(Collectors.toList());
+        if (workContentIds.isEmpty()) {
+            return workContentList;
+        }
+        LambdaQueryWrapper<PmProject> projectQuery = Wrappers.lambdaQuery();
+        projectQuery.select(PmProject::getId, PmProject::getProjectName).in(PmProject::getId, workContentIds);
+        Map<Integer, String> projectNameMap = pmProjectMapper.selectList(projectQuery).stream().collect(Collectors.toMap(PmProject::getId, PmProject::getProjectName));
+
+        workContentList.forEach(workContent -> {
+            workContent.setProjectName(projectNameMap.get(workContent.getProjectId()));
+        });
+
+        return workContentList;
+    }
+
+    /**
+     * @description: 报告记录
+     * @author: fu
+     * @date: 2024/8/7 17:14
+     * @param: [projectIds]
+     * @return: java.util.List<com.usky.pm.domain.PmProject>
+     **/
+    @Override
+    public List<PmWorkReport> workReportQuery(String startDate, String endDate, String projectName, Integer projectAscription, Integer reportId) {
+
+        LocalDate start = null;
+        LocalDate end = null;
+        LocalDateTime startDateTime = null;
+        LocalDateTime endDateTime = null;
+        if (StringUtils.isNotBlank(startDate) && StringUtils.isNotBlank(endDate)) {
+            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+            start = LocalDate.parse(startDate, formatter);
+            end = LocalDate.parse(endDate, formatter);
+            startDateTime = start.atStartOfDay();
+            endDateTime = end.atTime(23, 59, 59);
+        }
+
+        List<PmWorkReport> reportList = new ArrayList<>();
+        List<PmWorkReport> reportList2 = new ArrayList<>();
+        List<PmWorkContent> pmWorkContentList = new ArrayList<>();
+        List<Long> userIds = new ArrayList<>();
+        if (reportId != null && reportId > 0) {
+            LambdaQueryWrapper<PmWorkReport> reportQuery = Wrappers.lambdaQuery();
+            reportQuery.eq(PmWorkReport::getId, reportId);
+            PmWorkReport report = pmWorkReportMapper.selectOne(reportQuery);
+            if (report == null) {
+                throw new BusinessException("该报告已被删除!");
+            }
+
+            LambdaQueryWrapper<PmWorkContent> contentQuery = Wrappers.lambdaQuery();
+            contentQuery.select(PmWorkContent::getWorkContent, PmWorkContent::getWorkTime, PmWorkContent::getProjectName).eq(PmWorkContent::getReportId, reportId);
+            pmWorkContentList = this.list(contentQuery);
+            report.setWorkContents(pmWorkContentList);
+            reportList.add(report);
+            for (PmWorkReport c : reportList) {
+                userIds.add(c.getSubmitterId());
+            }
+        } else {
+            if (projectAscription != 1 && projectAscription != 2 && projectAscription != 3) {
+                throw new BusinessException("查询标识有误");
+            }
+            List<Integer> receiveList2 = receive();
+            if (projectAscription == 2 && receiveList2.isEmpty()) {
+                return reportList2;
+            }
+            List<Integer> sentOutList = sentOut();
+            if (projectAscription == 3 && sentOutList.isEmpty()) {
+                return reportList2;
+            }
+            List<Integer> headProjects = head();
+            if (projectAscription == 1 && headProjects.isEmpty()) {
+                return reportList2;
+            }
+            LambdaQueryWrapper<PmWorkContent> contentLambdaQuery = Wrappers.lambdaQuery();
+            contentLambdaQuery.select(PmWorkContent::getReportId, PmWorkContent::getWorkContent, PmWorkContent::getWorkTime, PmWorkContent::getProjectName);
+
+            if (startDateTime != null) {
+                contentLambdaQuery.between(PmWorkContent::getCreateTime, startDateTime, endDateTime);
+            }
+            if (projectAscription == 1) {
+                contentLambdaQuery.in(PmWorkContent::getProjectId, headProjects);
+            } else if (projectAscription == 2) {
+                contentLambdaQuery.in(PmWorkContent::getReportId, receiveList2);
+            } else {
+                contentLambdaQuery.in(PmWorkContent::getReportId, sentOutList);
+            }
+            if (StringUtils.isNotBlank(projectName)) {
+                List<Integer> list = reportList(projectName);
+                contentLambdaQuery.in(PmWorkContent::getReportId, list);
+            }
+            pmWorkContentList = this.list(contentLambdaQuery);
+            if (pmWorkContentList.isEmpty()) {
+                return reportList;
+            }
+            List<Integer> reportIds = new ArrayList<>();
+            for (PmWorkContent a : pmWorkContentList) {
+                reportIds.add(a.getReportId());
+            }
+            LambdaQueryWrapper<PmWorkReport> reportQuery = Wrappers.lambdaQuery();
+            reportQuery.select(PmWorkReport::getId, PmWorkReport::getCoordinateWork, PmWorkReport::getTomorrowPlan, PmWorkReport::getCcTo, PmWorkReport::getSubmitterId, PmWorkReport::getCreateTime, PmWorkReport::getReportDate).in(PmWorkReport::getId, reportIds).orderByDesc(PmWorkReport::getCreateTime);
+            reportList = pmWorkReportMapper.selectList(reportQuery);
+            LambdaQueryWrapper<PmReceive> statusQuery = Wrappers.lambdaQuery();
+            statusQuery.select(PmReceive::getReportId, PmReceive::getReadFlag).eq(PmReceive::getReceiverId, SecurityUtils.getUserId()).eq(PmReceive::getTenantId, SecurityUtils.getTenantId()).in(PmReceive::getReportId, reportIds);
+            List<PmReceive> receiveList = pmReceiveMapper.selectList(statusQuery);
+            for (PmWorkReport c : reportList) {
+                List<PmWorkContent> contents = new ArrayList<>();
+                for (PmWorkContent b : pmWorkContentList) {
+                    if (b.getReportId().equals(c.getId())) {
+                        contents.add(b);
+                    }
+                }
+                c.setWorkContents(contents);
+                if (!receiveList.isEmpty()) {
+                    for (PmReceive receive : receiveList) {
+                        if (receive.getReportId().equals(c.getId())) {
+                            c.setReadFlag(receive.getReadFlag());
+                        }
+                    }
+                }
+                userIds.add(c.getSubmitterId());
+            }
+        }
+        List<SysUser> nickNames = nickNames(userIds);
+        for (PmWorkReport d : reportList) {
+            for (SysUser e : nickNames) {
+                if (e.getUserId().equals(d.getSubmitterId())) {
+                    d.setCreateBy(e.getNickName());
+                }
+            }
+            List<Long> ids = null;
+            if (!d.getCcTo().isEmpty()) {
+                ids = Optional.ofNullable(d.getCcTo()).map(ccTo -> Arrays.stream(ccTo.split(",")).map(Long::parseLong).collect(Collectors.toList())).orElse(Collections.emptyList());
+            }
+            List<SysUser> nickNameList = null;
+            if (CollectionUtils.isNotEmpty(ids)) {
+                nickNameList = nickNames(ids);
+                StringBuilder ccToBuilder = new StringBuilder();
+                Set<Long> addedUserIds = new HashSet<>();
+                for (Long id : ids) {
+                    for (SysUser user : nickNameList) {
+                        if (user.getUserId().equals(id) && !addedUserIds.contains(id)) {
+                            if (ccToBuilder.length() > 0) {
+                                ccToBuilder.append(", ");
+                            }
+                            ccToBuilder.append(user.getNickName());
+                            addedUserIds.add(id);
+                            break;
+                        }
+                    }
+                }
+                String ccTo = ccToBuilder.toString();
+                d.setCcTo(ccTo);
+            }
+        }
+        return reportList;
+    }
+
+    /**
+     * @description: 用户名、账户名查询
+     * @author: fu
+     * @date: 2024/8/7 17:28
+     * @param: [userIds]
+     * @return: java.util.List<com.usky.system.domain.SysUser>
+     **/
+    @Override
+    public List<SysUser> nickNames(List<Long> userIds) {
+        LambdaQueryWrapper<SysUser> usersQuery = Wrappers.lambdaQuery();
+        usersQuery.select(SysUser::getUserId, SysUser::getNickName, SysUser::getUserName, SysUser::getPhonenumber, SysUser::getAvatar, SysUser::getSex, SysUser::getDeptId)
+                .eq(SysUser::getTenantId, SecurityUtils.getTenantId())
+                .eq(SysUser::getDelFlag, 0);
+        if (!userIds.isEmpty()) {
+            usersQuery.in(SysUser::getUserId, userIds);
+        }
+        return sysUserMapper.selectList(usersQuery);
+    }
+
+    /**
+     * @description: 报告记录分页
+     * @author: fu
+     * @date: 2024/8/7 17:28
+     * @param: [startDate, endDate, projectName, projectAscription]
+     * @return: java.util.List<com.usky.system.domain.PmWorkContent>
+     **/
+    @Override
+    public CommonPage<PmWorkReport> reportPage(Integer projectAscription, Integer pageNum, Integer pageSize, Integer reportId, String startDate,
+                                               String endDate, Integer projectId, Long userId, Integer upOrDown, Integer slideSum, String submitDate) {
+        if (pageNum < 1 || pageSize < 1) {
+            throw new BusinessException("页码和每页数量不能小于1");
+        }
+
+        List<PmWorkReport> reportList = new ArrayList<>();
+        IPage<PmWorkReport> reportPage = new Page<>(pageNum, pageSize);
+        LocalDateTime startDateTime = null;
+        LocalDateTime endDateTime = null;
+        LocalDateTime submitDateTime = null;
+        if (StringUtils.isNotBlank(submitDate)) {
+            DateTimeFormatter submitDateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+            try {
+                submitDateTime = LocalDateTime.parse(submitDate, submitDateFormatter);
+            } catch (Exception e) {
+                throw new BusinessException("滑动时间传参格式不正确,请重试" + e);
+            }
+        }
+        Integer tenantId = SecurityUtils.getTenantId();
+        Long nowUserId = SecurityUtils.getUserId();
+        LocalDate start = null;
+        LocalDate end = null;
+        List<Integer> reportIds = new ArrayList<>();
+        CommonPage<PmWorkReport> returnPage = new CommonPage<>(reportList, reportPage.getTotal(), pageSize, pageNum);
+
+        Set<Integer> reportIdsByUser = new HashSet<>();
+        Set<Integer> reportIdsByDate = new HashSet<>();
+        Set<Integer> reportIdsByProject = new HashSet<>();
+        Set<Integer> reportIdsSet = new HashSet<>();
+
+        LambdaQueryWrapper<PmWorkReport> reportQuery = Wrappers.lambdaQuery();
+
+        if (StringUtils.isNotBlank(startDate) && StringUtils.isNotBlank(endDate)) {
+            DateTimeFormatter formatter = null;
+            try {
+                formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+            } catch (Exception e) {
+                throw new BusinessException("时间传参格式不正确请重试");
+            }
+            start = LocalDate.parse(startDate, formatter);
+            end = LocalDate.parse(endDate, formatter);
+            startDateTime = start.atStartOfDay();
+            endDateTime = end.atTime(23, 59, 59);
+            reportQuery.select(PmWorkReport::getId).eq(PmWorkReport::getReportStatus, 1).between(PmWorkReport::getSubmitDate, startDateTime, endDateTime);
+            reportIdsByDate = pmWorkReportMapper.selectList(reportQuery).stream().map(PmWorkReport::getId).collect(Collectors.toSet());
+        }
+
+        if (projectId != null) {
+            LambdaQueryWrapper<PmWorkContent> contentLambdaQuery = Wrappers.lambdaQuery();
+            contentLambdaQuery.select(PmWorkContent::getReportId).eq(PmWorkContent::getProjectId, projectId);
+            reportIdsByProject = pmWorkContentMapper.selectList(contentLambdaQuery).stream().map(PmWorkContent::getReportId).collect(Collectors.toSet());
+        }
+
+        if (userId != null) {
+            reportQuery.eq(PmWorkReport::getSubmitterId, userId)
+                    .and(wrappers -> wrappers.apply("FIND_IN_SET(" + nowUserId + ", cc_to) > 0"));
+            reportIdsByUser = pmWorkReportMapper.selectList(reportQuery).stream().map(PmWorkReport::getId).collect(Collectors.toSet());
+            if (reportIdsByProject.isEmpty() && reportIdsByUser.isEmpty()) {
+                return returnPage;
+            }
+        }
+
+        if (!reportIdsByUser.isEmpty() && !reportIdsByProject.isEmpty() && !reportIdsByDate.isEmpty()) {
+            reportIdsSet.addAll(reportIdsByUser);
+            reportIdsSet.retainAll(reportIdsByProject);
+            reportIdsSet.retainAll(reportIdsByDate);
+            if (reportIdsSet.isEmpty()) {
+                return returnPage;
+            }
+        } else if (!reportIdsByUser.isEmpty() && !reportIdsByProject.isEmpty()) {
+            reportIdsSet.addAll(reportIdsByUser);
+            reportIdsSet.retainAll(reportIdsByProject);
+            if (reportIdsSet.isEmpty()) {
+                return returnPage;
+            }
+        } else if (!reportIdsByUser.isEmpty() && !reportIdsByDate.isEmpty()) {
+            reportIdsSet.addAll(reportIdsByUser);
+            reportIdsSet.retainAll(reportIdsByDate);
+            if (reportIdsSet.isEmpty()) {
+                return returnPage;
+            }
+        } else if (!reportIdsByProject.isEmpty() && !reportIdsByDate.isEmpty()) {
+            reportIdsSet.addAll(reportIdsByProject);
+            reportIdsSet.retainAll(reportIdsByDate);
+            if (reportIdsSet.isEmpty()) {
+                return returnPage;
+            }
+        } else if (!reportIdsByUser.isEmpty()) {
+            reportIdsSet.addAll(reportIdsByUser);
+        } else if (!reportIdsByProject.isEmpty()) {
+            reportIdsSet.addAll(reportIdsByProject);
+        } else if (!reportIdsByDate.isEmpty()) {
+            reportIdsSet.addAll(reportIdsByDate);
+        }
+
+        reportIds = new ArrayList<>(reportIdsSet);
+
+        if (projectAscription == 1) {
+            LambdaQueryWrapper<PmWorkReport> headerQuery = Wrappers.lambdaQuery();
+            headerQuery.in(!reportIds.isEmpty(), PmWorkReport::getId, reportIds)
+                    .and(wrappers -> wrappers.apply("FIND_IN_SET(" + nowUserId + ", cc_to) > 0"));
+            reportIds = pmWorkReportMapper.selectList(headerQuery).stream().map(PmWorkReport::getId).collect(Collectors.toList());
+        }
+
+        List<PmWorkContent> pmWorkContentList = new ArrayList<>();
+        Long userId2 = SecurityUtils.getUserId();
+        List<Long> userIds = new ArrayList<>();
+        List<Integer> receiveList = receive();
+        if (receiveList.isEmpty() && projectAscription == 2) {
+            return returnPage;
+        }
+
+        LambdaQueryWrapper<PmWorkContent> contentLambdaQuery = Wrappers.lambdaQuery();
+        if (reportId != null && reportId > 0) {
+            contentLambdaQuery.eq(PmWorkContent::getReportId, reportId);
+            pmWorkContentList = pmWorkContentMapper.selectList(contentLambdaQuery);
+            if (pmWorkContentList.isEmpty()) {
+                throw new BusinessException("该报告已被删除!");
+            }
+        } else {
+            switch (projectAscription) {
+                case 1:
+                    List<Integer> headProjects = head();
+                    if (headProjects.isEmpty()) {
+                        return returnPage;
+                    }
+                    contentLambdaQuery.in(PmWorkContent::getProjectId, headProjects);
+                    break;
+                case 2:
+                    contentLambdaQuery.in(PmWorkContent::getReportId, receiveList);
+                    break;
+                case 3:
+                    List<Integer> sentOutList = sentOut();
+                    if (sentOutList.isEmpty()) {
+                        return returnPage;
+                    }
+                    contentLambdaQuery.in(PmWorkContent::getReportId, sentOutList);
+                    break;
+                default:
+                    throw new BusinessException("查询标识有误!");
+            }
+            if (!reportIds.isEmpty()) {
+                contentLambdaQuery.in(PmWorkContent::getReportId, reportIds);
+            }
+            pmWorkContentList = pmWorkContentMapper.selectList(contentLambdaQuery);
+            if (pmWorkContentList.isEmpty()) {
+                return returnPage;
+            }
+        }
+
+        List<Integer> reportIds2 = pmWorkContentList.stream().distinct().map(PmWorkContent::getReportId).collect(Collectors.toList());
+
+        LambdaQueryWrapper<PmWorkReport> reportQuery1 = Wrappers.lambdaQuery();
+        reportQuery1.eq(PmWorkReport::getTenantId, tenantId).eq(PmWorkReport::getReportStatus, 1);
+        if (reportId != null && reportId != 0) {
+            reportQuery1.eq(PmWorkReport::getId, reportId);
+        } else {
+            reportQuery1.in(PmWorkReport::getId, reportIds2);
+            // 判断上下滑动以及数量
+            if (upOrDown != null && slideSum != null && submitDateTime != null) {
+                if (slideSum > 0) {
+                    // 向下滑动
+                    if (upOrDown == 0) {
+                        reportQuery1.lt(PmWorkReport::getSubmitDate, submitDateTime).orderByDesc(PmWorkReport::getSubmitDate);
+                    }
+                    // 向上滑动
+                    else if (upOrDown == 1) {
+                        reportQuery1.gt(PmWorkReport::getSubmitDate, submitDateTime).orderByAsc(PmWorkReport::getSubmitDate);
+                    } else {
+                        throw new BusinessException("上下滑动参数异常");
+                    }
+                    // 设置pageSize为slideSum,确保返回的数据量为slideSum
+                    pageNum = 1;
+                    pageSize = slideSum;
+                } else {
+                    throw new BusinessException("滑动数量参数必须是大于0正整数");
+                }
+            } else {
+                reportQuery1.orderByDesc(PmWorkReport::getSubmitDate);
+            }
+        }
+
+        reportPage = pmWorkReportMapper.selectPage(new Page<>(pageNum, pageSize), reportQuery1);
+        reportList = reportPage.getRecords();
+
+        // 查询已读状态
+        LambdaQueryWrapper<PmReceive> statusQuery = Wrappers.lambdaQuery();
+        statusQuery.select(PmReceive::getReportId, PmReceive::getReadFlag).eq(PmReceive::getReceiverId, userId2);
+        if (reportId != null && reportId != 0) {
+            statusQuery.eq(PmReceive::getReportId, reportId);
+        } else if (!receiveList.isEmpty()) {
+            statusQuery.in(PmReceive::getReportId, receiveList);
+        }
+        List<PmReceive> receiveList2 = pmReceiveMapper.selectList(statusQuery);
+
+        Map<Integer, Integer> reportReadFlags = new HashMap<>();
+        for (PmReceive pmReceive : receiveList2) {
+            Integer reportId2 = pmReceive.getReportId();
+            reportReadFlags.put(reportId2, pmReceive.getReadFlag());
+        }
+
+        for (PmWorkReport report : reportList) {
+            List<PmWorkContent> contents = pmWorkContentList.stream().filter(content -> content.getReportId().equals(report.getId())).collect(Collectors.toList());
+            report.setWorkContents(contents);
+            userIds.add(report.getSubmitterId());
+
+            Integer readFlagValue = reportReadFlags.get(report.getId());
+            report.setReadFlag(readFlagValue);
+
+            // 创建人名字替换
+            if (!userIds.isEmpty()) {
+                List<SysUser> nickNames = nickNames(userIds);
+                Map<Long, String> userNicknameMap = nickNames.stream().collect(Collectors.toMap(SysUser::getUserId, SysUser::getNickName));
+                report.setCreateBy(userNicknameMap.get(report.getSubmitterId()));
+            }
+        }
+
+        // 已读未读数量查询
+        List<PmReceive> receives = new ArrayList<>();
+        if (!receiveList.isEmpty()) {
+            receives = receives(reportIds2);
+        }
+
+        Map<Integer, List<PmReceive>> reportReceivesMap = new HashMap<>();
+        if (!receives.isEmpty()) {
+            reportReceivesMap = receives.stream().collect(Collectors.groupingBy(PmReceive::getReportId));
+        }
+
+        Set<Long> userIdSet = new HashSet<>();
+        for (PmWorkReport report : reportList) {
+            Long submitterId = report.getSubmitterId();
+            userIdSet.add(submitterId);
+        }
+        List<Long> userIdList = new ArrayList<>(userIdSet);
+
+        List<SysUser> sysUsers = nickNames(userIdList);
+
+        if (sysUsers != null) {
+            for (PmWorkReport report : reportList) {
+                List<Long> readAlready = new ArrayList<>();
+                List<Long> readNotAlready = new ArrayList<>();
+                int readCount = 0;
+                int unreadCount = 0;
+                if (!reportReceivesMap.isEmpty()) {
+                    List<PmReceive> reportReceives = reportReceivesMap.getOrDefault(report.getId(), Collections.emptyList());
+                    for (PmReceive pmReceive : reportReceives) {
+                        if (pmReceive.getReadFlag() == 1) {
+                            readCount++;
+                            readAlready.add(pmReceive.getReceiverId());
+                        } else {
+                            unreadCount++;
+                            readNotAlready.add(pmReceive.getReceiverId());
+                        }
+                    }
+                }
+
+                PmReportReadersVO readUnreadVO = new PmReportReadersVO(readAlready, readNotAlready, readCount, unreadCount);
+                report.setPmReportReaders(readUnreadVO);
+
+                // 头像
+                for (SysUser sysUser : sysUsers) {
+                    if (sysUser.getUserId().equals(report.getSubmitterId())) {
+                        report.setAvatar(sysUser.getAvatar());
+                        break;
+                    }
+                }
+            }
+        }
+
+        return new CommonPage<PmWorkReport>(reportList, reportPage.getTotal(), pageSize, pageNum);
+    }
+
+    /**
+     * @description: 日报上下滑动
+     * @author: fu
+     * @date: 2024/8/8 19:22
+     * @param: [upOrDown, slideSum, submitDate]
+     * @return: java.util.List<com.usky.pm.vo.PmWorkReportSlideVO>
+     **/
+    @Override
+    public PmWorkReportSlideVO slide(Integer upOrDown, Integer slideSum, LocalDateTime submitDate) {
+        PmWorkReportSlideVO reportSlide = new PmWorkReportSlideVO();
+        if (slideSum <= 0) {
+            throw new BusinessException("slideSum 必须为正整数!");
+        }
+        if (submitDate == null) {
+            return reportSlide;
+        }
+
+        List<PmWorkReport> reportUp = new ArrayList<>();
+        List<PmWorkReport> reportDown = new ArrayList<>();
+        LambdaQueryWrapper<PmWorkReport> queryWrapper = Wrappers.lambdaQuery();
+        queryWrapper.last("LIMIT " + slideSum);
+        if (upOrDown == 1) {
+            queryWrapper.gt(PmWorkReport::getSubmitDate, submitDate)
+                    .orderByAsc(PmWorkReport::getSubmitDate);
+            reportUp = pmWorkReportMapper.selectList(queryWrapper);
+            if (reportUp.isEmpty()) {
+                throw new BusinessException("已经是第一篇报告了!");
+            }
+            reportSlide.setSlideUp(reportUp);
+        } else if (upOrDown == 2) {
+            queryWrapper.lt(PmWorkReport::getSubmitDate, submitDate)
+                    .orderByDesc(PmWorkReport::getSubmitDate);
+            reportDown = pmWorkReportMapper.selectList(queryWrapper);
+            if (reportDown.isEmpty()) {
+                throw new BusinessException("已经是最后一篇报告了!");
+            }
+            reportSlide.setSlideDown(reportDown);
+        } else {
+            throw new BusinessException("upOrDown 参数无效!");
+        }
+
+        return reportSlide;
+    }
+
+
+    /**
+     * @description: 获取报告接收人-工具
+     * @author: fu
+     * @date: 2024/8/8 19:22
+     * @param: [report]
+     * @return: java.util.List<com.usky.pm.domain.PmReceive>
+     **/
+    private List<PmReceive> receives(List<Integer> reports) {
+        LambdaQueryWrapper<PmReceive> queryWrapper = Wrappers.lambdaQuery();
+        queryWrapper.select(PmReceive::getReportId, PmReceive::getReadFlag, PmReceive::getReceiverId).in(PmReceive::getReportId, reports);
+        return pmReceiveMapper.selectList(queryWrapper);
+    }
+
+    /**
+     * @description: 获取时间段内报告列表(按报告时间查询)-工具
+     * @author: fu
+     * @date: 2024/8/8 19:22
+     * @param: [startDate, endDate]
+     * @return: java.util.List<java.lang.Integer>
+     **/
+    private List<Integer> reports(LocalDate startDate, LocalDate endDate) {
+        LambdaQueryWrapper<PmWorkReport> reportsQuery = Wrappers.lambdaQuery();
+        reportsQuery.select(PmWorkReport::getId).between(PmWorkReport::getReportDate, startDate, endDate);
+        List<PmWorkReport> pmWorkReportList = pmWorkReportMapper.selectList(reportsQuery);
+        return pmWorkReportList.stream().map(PmWorkReport::getId).collect(Collectors.toList());
+    }
+
+    /**
+     * @description: 项目查询-工具
+     * @author: fu
+     * @date: 2024/8/8 19:22
+     * @return: java.util.List<java.lang.Integer>
+     **/
+    private List<PmProject> projects(Integer projectId) {
+        Long userId = SecurityUtils.getUserId();
+        LambdaQueryWrapper<PmProject> projectsQuery = Wrappers.lambdaQuery();
+        projectsQuery.select(PmProject::getId, PmProject::getProjectName);
+        if (projectId != null && projectId >= 0) {
+            projectsQuery.eq(PmProject::getId, projectId);
+            return pmProjectMapper.selectList(projectsQuery);
+        } else {
+            projectsQuery.and(qw -> qw
+                    .or().apply(Objects.nonNull(DataScopeContextHolder.getDataScopeSql()), DataScopeContextHolder.getDataScopeSql())
+                    .or().eq(PmProject::getProjectHead, userId)
+                    .or().eq(PmProject::getCreateBy, SecurityUtils.getUsername())
+                    .or().apply("FIND_IN_SET('" + SecurityUtils.getUserId() + "', project_member) > 0")
+            );
+            // projectsQuery.apply(Objects.nonNull(DataScopeContextHolder.getDataScopeSql()), DataScopeContextHolder.getDataScopeSql());
+        }
+        projectsQuery.eq(PmProject::getDelFlag, 0);
+        return pmProjectMapper.selectList(projectsQuery);
+    }
+
+    /**
+     * @description: 获取用户列表-工具
+     * @author: fu
+     * @date: 2024/8/8 19:22
+     * @param: [userId]
+     * @return: java.util.List<com.usky.pm.domain.SysUser>
+     **/
+    private List<SysUser> userNameList(List<Long> userId, Integer workerOrProject) {
+        LambdaQueryWrapper<SysUser> userNameQuery = Wrappers.lambdaQuery();
+        userNameQuery.select(SysUser::getUserId, SysUser::getNickName, SysUser::getUserName, SysUser::getPhonenumber, SysUser::getAvatar,
+                SysUser::getSex, SysUser::getDeptId, SysUser::getAddress);
+        if (!userId.isEmpty()) {
+            userNameQuery.eq(SysUser::getDelFlag, 0).eq(SysUser::getStatus, 0).in(SysUser::getUserId, userId);
+            return sysUserMapper.selectList(userNameQuery);
+        } else {
+            userNameQuery.eq(SysUser::getUserId, SecurityUtils.getUserId());
+        }
+        return sysUserMapper.selectList(userNameQuery);
+    }
+
+    /**
+     * @description: 工时统计-工具
+     * @author: fu
+     * @date: 2024/8/8 19:22
+     * @param: [startDate, endDate, projectName, projectAscription]
+     * @return: java.util.List<com.usky.pm.domain.PmWorkReport>
+     **/
+    private List<PmWorkContent> workTimeCount(List<Long> users, List<Integer> project, String startDate, String endDate) {
+        List<PmWorkContent> pmWorkContentList = new ArrayList<>();
+        Integer tenantId = SecurityUtils.getTenantId();
+        Long deptId = SecurityUtils.getLoginUser().getSysUser().getDeptId();
+        LambdaQueryWrapper<PmWorkContent> workTimeQuery = Wrappers.lambdaQuery();
+        workTimeQuery.select(PmWorkContent::getSubmitterId, PmWorkContent::getReportId, PmWorkContent::getProjectId, PmWorkContent::getProjectName,
+                        PmWorkContent::getWorkTime, PmWorkContent::getDeptId, PmWorkContent::getTenantId)
+                .eq(PmWorkContent::getTenantId, tenantId);
+        if (!users.isEmpty()) {
+            workTimeQuery.in(PmWorkContent::getSubmitterId, users);
+        }
+        if (!project.isEmpty()) {
+            workTimeQuery.in(PmWorkContent::getProjectId, project);
+        }
+        if (StringUtils.isNotBlank(startDate) && StringUtils.isNotBlank(endDate)) {
+            LocalDate start = LocalDate.parse(startDate);
+            LocalDate end = LocalDate.parse(endDate);
+            List<Integer> reports = reports(start, end);
+            if (!reports.isEmpty()) {
+                workTimeQuery.in(PmWorkContent::getReportId, reports);
+            } else {
+                return pmWorkContentList;
+            }
+        }
+        pmWorkContentList = pmWorkContentMapper.selectList(workTimeQuery);
+
+        Map<String, PmWorkContent> workTimeMap = new HashMap<>();
+        for (PmWorkContent content : pmWorkContentList) {
+            String key = content.getSubmitterId() + "_" + content.getProjectId();
+            PmWorkContent existingContent = workTimeMap.get(key);
+            if (existingContent == null) {
+                workTimeMap.put(key, content);
+            } else {
+                existingContent.setWorkTime(existingContent.getWorkTime().add(content.getWorkTime()));
+            }
+        }
+
+        return new ArrayList<>(workTimeMap.values());
+    }
+
+    /**
+     * @description: 工时统计
+     * @author: fu
+     * @date: 2024/8/8 19:22
+     * @param: [userId, projectId, startDate, endDate]
+     *      * @param userId          用户id
+     *      * @param filter          是否过滤0工时(默认1:不过滤;2:过滤)
+     *      * @param startDate       开始时间
+     *      * @param endDate         结束时间
+     *      * @param workerOrProject 打工仔还是项目(默认1:打工仔;2:项目)
+     * @return: java.util.List<com.usky.pm.domain.WorkTimeExportVO>
+     **/
+    @DataScope
+    @Override
+    public List<Object> workHourStatisticNew(PmWorkHourStatisticRequestVO requestVO) {
+        List<Long> userId = new ArrayList<>();
+        if (requestVO.getUserIds() != null) {
+            userId = requestVO.getUserIds();
+        }
+        Integer projectId = requestVO.getProjectId();
+        int filter = 1;
+        if (requestVO.getFilter() != null) {
+            filter = requestVO.getFilter();
+        }
+        String startDate = requestVO.getStartDate();
+        String endDate = requestVO.getEndDate();
+        int workerOrProject = 1;
+        if (requestVO.getWorkerOrProject() != null) {
+            workerOrProject = requestVO.getWorkerOrProject();
+        }
+        if (StringUtils.isBlank(startDate) || StringUtils.isBlank(endDate)) {
+            startDate = LocalDate.now().with(TemporalAdjusters.firstDayOfYear()).toString();
+            endDate = LocalDate.now().with(TemporalAdjusters.lastDayOfYear()).toString();
+        }
+
+        List<ProjectWorkTimeVO> projectWorkTime = new ArrayList<>();
+        List<UserWorkTimeVO> userWorkTimeVO = new ArrayList<>();
+        List<Object> returnList = new ArrayList<>();
+
+        if (StringUtils.isBlank(startDate) || StringUtils.isBlank(endDate)) {
+            throw new BusinessException("请设置查询时间范围!");
+        }
+
+        List<SysUser> users = userNameList(userId, workerOrProject);
+        List<Long> userIds = new ArrayList<>();
+        List<String> nickName = new ArrayList<>();
+        for (SysUser user : users) {
+            userIds.add(user.getUserId());
+            nickName.add(user.getNickName());
+        }
+
+        List<PmProject> pmProjects = projects(projectId);
+        List<String> pmProjectName = new ArrayList<>();
+        List<Integer> pmProjectId = new ArrayList<>();
+        for (PmProject project : pmProjects) {
+            pmProjectName.add(project.getProjectName());
+            pmProjectId.add(project.getId());
+        }
+
+        // 统计userId每个项目所需工时
+        List<WorkHoursStatisticsVO> userAndProject = new ArrayList<>();
+        List<PmWorkContent> pmWorkContentList = workTimeCount(userIds, pmProjectId, startDate, endDate);
+        if (!pmWorkContentList.isEmpty()) {
+            for (PmWorkContent content : pmWorkContentList) {
+                WorkHoursStatisticsVO workHoursStatisticsVO = new WorkHoursStatisticsVO();
+                Long submitterId = content.getSubmitterId();
+                Integer projectId1 = content.getProjectId();
+                BigDecimal workTime = content.getWorkTime();
+                workHoursStatisticsVO.setWorkTime(workTime);
+                for (SysUser user : users) {
+                    Long userId1 = user.getUserId();
+                    String nickName1 = user.getNickName();
+                    if (userId1.equals(submitterId)) {
+                        workHoursStatisticsVO.setNickName(nickName1);
+                    }
+                }
+                for (PmProject project : pmProjects) {
+                    Integer projectId2 = project.getId();
+                    String projectName = project.getProjectName();
+                    if (Objects.equals(projectId2, projectId1)) {
+                        workHoursStatisticsVO.setProjectName(projectName);
+                    }
+                }
+                userAndProject.add(workHoursStatisticsVO);
+            }
+        }
+        List<WorkHoursStatisticsVO> userAndProjectD = userAndProject;
+
+        // 判断是否为项目维度
+        if (workerOrProject != 1) {
+            returnList.add(nickName);
+            // 重组返回数据结构
+            for (String name : pmProjectName) {
+                ProjectWorkTimeVO projectWorkTimeVO = new ProjectWorkTimeVO();
+                projectWorkTimeVO.setProjectOrPerson(name);
+                List<BigDecimal> workTime = new ArrayList<>();
+                boolean hasNonZeroWorkTime = false;
+                if (userAndProject.isEmpty()) {
+                    for (String currentNickName : nickName) {
+                        workTime.add(BigDecimal.ZERO);
+                    }
+                } else {
+                    for (String currentNickName : nickName) {
+                        boolean foundWorkTime = false;
+                        for (WorkHoursStatisticsVO workHoursStatisticsVO : userAndProject) {
+                            String userNickName = workHoursStatisticsVO.getNickName();
+                            String projectName = workHoursStatisticsVO.getProjectName();
+                            BigDecimal userProjectWorkTime = workHoursStatisticsVO.getWorkTime();
+                            if (projectName == name && userNickName == currentNickName) {
+                                workTime.add(userProjectWorkTime);
+                                if (userProjectWorkTime.compareTo(BigDecimal.ZERO) != 0) {
+                                    hasNonZeroWorkTime = true;
+                                }
+                                foundWorkTime = true;
+                                break;
+                            }
+                        }
+                        if (!foundWorkTime) {
+                            workTime.add(BigDecimal.ZERO);
+                        }
+                    }
+                }
+                // 过滤0工时
+                if (filter != 2 || hasNonZeroWorkTime) {
+                    projectWorkTimeVO.setWorkTime(workTime);
+                    projectWorkTime.add(projectWorkTimeVO);
+                }
+            }
+            returnList.add(projectWorkTime);
+
+
+            // 先过滤掉总工时为0的项目
+            Set<Integer> nonZeroProjectIndices = new HashSet<>();
+            for (int i = 0; i < pmProjectName.size(); i++) {
+                BigDecimal totalWorkTime = BigDecimal.ZERO;
+                for (WorkHoursStatisticsVO workHoursStatisticsVO : userAndProjectD) {
+                    if (workHoursStatisticsVO.getProjectName().equals(pmProjectName.get(i))) {
+                        totalWorkTime = totalWorkTime.add(workHoursStatisticsVO.getWorkTime());
+                    }
+                }
+                if (totalWorkTime.compareTo(BigDecimal.ZERO) != 0) {
+                    nonZeroProjectIndices.add(i);
+                }
+            }
+
+            // 重组返回数据结构
+            for (String uName : nickName) {
+                String name1 = uName;
+                UserWorkTimeVO userWorkTimeVO2 = new UserWorkTimeVO();
+                userWorkTimeVO2.setProjectOrPerson(name1);
+                List<BigDecimal> workTime1 = new ArrayList<>();
+
+                for (int i = 0; i < pmProjectName.size(); i++) {
+                    BigDecimal userProjectWorkTime = BigDecimal.ZERO;
+                    boolean foundWorkTime = false;
+                    for (WorkHoursStatisticsVO workHoursStatisticsVO : userAndProjectD) {
+                        String userNickName = workHoursStatisticsVO.getNickName();
+                        String projectName = workHoursStatisticsVO.getProjectName();
+                        if (userNickName.equals(name1) && projectName.equals(pmProjectName.get(i))) {
+                            userProjectWorkTime = workHoursStatisticsVO.getWorkTime();
+                            foundWorkTime = true;
+                            break;
+                        }
+                    }
+                    if (foundWorkTime && nonZeroProjectIndices.contains(i)) {
+                        workTime1.add(userProjectWorkTime);
+                    } else {
+                        workTime1.add(BigDecimal.ZERO);
+                    }
+                }
+
+                // 过滤0工时
+                boolean hasNonZeroWorkTime1 = false;
+                for (BigDecimal time : workTime1) {
+                    if (time.compareTo(BigDecimal.ZERO) != 0) {
+                        hasNonZeroWorkTime1 = true;
+                        break;
+                    }
+                }
+
+                if (filter != 2 || hasNonZeroWorkTime1) {
+                    userWorkTimeVO2.setWorkTime(workTime1);
+                    userWorkTimeVO.add(userWorkTimeVO2);
+                }
+            }
+
+            if (filter == 2) {
+                for (int i = pmProjectName.size() - 1; i >= 0; i--) {
+                    if (!nonZeroProjectIndices.contains(i)) {
+                        pmProjectName.remove(i);
+                    }
+                }
+                for (int i = userWorkTimeVO.size() - 1; i >= 0; i--) {
+                    List<BigDecimal> workTime = userWorkTimeVO.get(i).getWorkTime();
+                    for (int j = workTime.size() - 1; j >= 0; j--) {
+                        if (!nonZeroProjectIndices.contains(j)) {
+                            workTime.remove(j);
+                        }
+                    }
+                    if (workTime.isEmpty()) {
+                        userWorkTimeVO.remove(i);
+                    }
+                }
+            }
+
+            returnList.add(pmProjectName);
+            returnList.add(userWorkTimeVO);
+
+        } else {
+            returnList.add(pmProjectName);
+            // 重组返回数据结构
+            for (String uName : nickName) {
+                String name = uName;
+                UserWorkTimeVO userWorkTimeVO2 = new UserWorkTimeVO();
+                userWorkTimeVO2.setProjectOrPerson(name);
+                List<BigDecimal> workTime = new ArrayList<>();
+                boolean hasNonZeroWorkTime = false;
+                if (userAndProjectD.isEmpty()) {
+                    for (String currentNickName : pmProjectName) {
+                        workTime.add(BigDecimal.ZERO);
+                    }
+                } else {
+                    for (String currentNickName : pmProjectName) {
+                        boolean foundWorkTime = false;
+                        for (WorkHoursStatisticsVO workHoursStatisticsVO : userAndProjectD) {
+                            String userNickName = workHoursStatisticsVO.getNickName();
+                            String projectName = workHoursStatisticsVO.getProjectName();
+                            BigDecimal userProjectWorkTime = workHoursStatisticsVO.getWorkTime();
+                            if (Objects.equals(userNickName, name) && Objects.equals(projectName, currentNickName)) {
+                                workTime.add(userProjectWorkTime);
+                                if (userProjectWorkTime.compareTo(BigDecimal.ZERO) != 0) {
+                                    hasNonZeroWorkTime = true;
+                                }
+                                foundWorkTime = true;
+                                break;
+                            }
+                        }
+                        if (!foundWorkTime) {
+                            workTime.add(BigDecimal.ZERO);
+                        }
+                    }
+                }
+                // 过滤0工时
+                if (filter != 2 || hasNonZeroWorkTime) {
+                    userWorkTimeVO2.setWorkTime(workTime);
+                    userWorkTimeVO.add(userWorkTimeVO2);
+                }
+            }
+            returnList.add(userWorkTimeVO);
+
+
+            List<String> filteredNickName = new ArrayList<>();
+            List<ProjectWorkTimeVO> filteredProjectWorkTime = new ArrayList<>();
+
+            for (String name : pmProjectName) {
+                ProjectWorkTimeVO projectWorkTimeVO = new ProjectWorkTimeVO();
+                projectWorkTimeVO.setProjectOrPerson(name);
+                List<BigDecimal> workTime = new ArrayList<>();
+                boolean hasNonZeroWorkTime = false;
+
+                if (userAndProject.isEmpty()) {
+                    for (String currentNickName : nickName) {
+                        workTime.add(BigDecimal.ZERO);
+                    }
+                } else {
+                    for (String currentNickName : nickName) {
+                        boolean foundWorkTime = false;
+                        for (WorkHoursStatisticsVO workHoursStatisticsVO : userAndProject) {
+                            String userNickName = workHoursStatisticsVO.getNickName();
+                            String projectName = workHoursStatisticsVO.getProjectName();
+                            BigDecimal userProjectWorkTime = workHoursStatisticsVO.getWorkTime();
+                            if (projectName.equals(name) && userNickName.equals(currentNickName)) {
+                                workTime.add(userProjectWorkTime);
+                                if (userProjectWorkTime.compareTo(BigDecimal.ZERO) != 0) {
+                                    hasNonZeroWorkTime = true;
+                                }
+                                foundWorkTime = true;
+                                break;
+                            }
+                        }
+                        if (!foundWorkTime) {
+                            workTime.add(BigDecimal.ZERO);
+                        }
+                    }
+                }
+
+                // 过滤0工时
+                if (filter != 2 || hasNonZeroWorkTime) {
+                    projectWorkTimeVO.setWorkTime(workTime);
+                    filteredProjectWorkTime.add(projectWorkTimeVO);
+                }
+            }
+
+            // 过滤 nickName 和 projectWorkTime
+            if (filter == 2) {
+                Set<Integer> nonZeroIndices = new HashSet<>();
+                for (int i = 0; i < nickName.size(); i++) {
+                    BigDecimal totalWorkTime = BigDecimal.ZERO;
+                    for (int j = 0; j < filteredProjectWorkTime.size(); j++) {
+                        totalWorkTime = totalWorkTime.add(filteredProjectWorkTime.get(j).getWorkTime().get(i));
+                    }
+                    if (totalWorkTime.compareTo(BigDecimal.ZERO) != 0) {
+                        filteredNickName.add(nickName.get(i));
+                        nonZeroIndices.add(i);
+                    }
+                }
+                for (int i = filteredProjectWorkTime.size() - 1; i >= 0; i--) {
+                    List<BigDecimal> workTime = filteredProjectWorkTime.get(i).getWorkTime();
+                    for (int j = workTime.size() - 1; j >= 0; j--) {
+                        if (!nonZeroIndices.contains(j)) {
+                            workTime.remove(j);
+                        }
+                    }
+                    if (workTime.isEmpty()) {
+                        filteredProjectWorkTime.remove(i);
+                    }
+                }
+            } else {
+                filteredNickName.addAll(nickName);
+            }
+
+            returnList.add(filteredNickName);
+            returnList.add(filteredProjectWorkTime);
+        }
+        return returnList;
+    }
+
+    // 回退代码
+    @DataScope
+    @Override
+    public List<Object> workHourStatistic(Long userId, Integer projectId, Integer filter, String startDate, String endDate, Integer workerOrProject) {
+        List<ProjectWorkTimeVO> projectWorkTime = new ArrayList<>();
+        List<UserWorkTimeVO> userWorkTimeVO = new ArrayList<>();
+        List<ProjectWorkTimeVO> projectWorkTime1 = new ArrayList<>();
+        List<UserWorkTimeVO> userWorkTimeVO1 = new ArrayList<>();
+        List<Object> returnList = new ArrayList<>();
+        if (StringUtils.isBlank(startDate) || StringUtils.isBlank(endDate)) {
+            throw new BusinessException("请设置查询时间范围!");
+        }
+
+        List<PmProject> pmProjects = projects(projectId);
+        List<String> pmProjectName = new ArrayList<>();
+        List<Integer> pmProjectId = new ArrayList<>();
+        for (PmProject project : pmProjects) {
+            pmProjectName.add(project.getProjectName());
+            pmProjectId.add(project.getId());
+        }
+
+        List<Long> userIdS = new ArrayList<>();
+        if (userId != null) {
+            userIdS.add(userId);
+        }
+        List<SysUser> users = userNameList(userIdS, workerOrProject);
+        List<Long> userIds = new ArrayList<>();
+        List<String> nickName = new ArrayList<>();
+        for (SysUser user : users) {
+            userIds.add(user.getUserId());
+            nickName.add(user.getNickName());
+        }
+
+        // 统计userId每个项目所需工时
+        List<WorkHoursStatisticsVO> userAndProject = new ArrayList<>();
+        List<PmWorkContent> pmWorkContentList = workTimeCount(userIds, pmProjectId, startDate, endDate);
+        if (!pmWorkContentList.isEmpty()) {
+            for (PmWorkContent content : pmWorkContentList) {
+                WorkHoursStatisticsVO workHoursStatisticsVO = new WorkHoursStatisticsVO();
+                Long submitterId = content.getSubmitterId();
+                Integer projectId1 = content.getProjectId();
+                BigDecimal workTime = content.getWorkTime();
+                workHoursStatisticsVO.setWorkTime(workTime);
+                for (SysUser user : users) {
+                    Long userId1 = user.getUserId();
+                    String nickName1 = user.getNickName();
+                    if (userId1.equals(submitterId)) {
+                        workHoursStatisticsVO.setNickName(nickName1);
+                    }
+                }
+                for (PmProject project : pmProjects) {
+                    Integer projectId2 = project.getId();
+                    String projectName = project.getProjectName();
+                    if (projectId2.equals(projectId1)) {
+                        workHoursStatisticsVO.setProjectName(projectName);
+                    }
+                }
+                userAndProject.add(workHoursStatisticsVO);
+            }
+        }
+        List<WorkHoursStatisticsVO> userAndProjectD = userAndProject;
+
+        if (workerOrProject != 1) {
+            returnList.add(nickName);
+            // 重组返回数据结构
+            for (String name : pmProjectName) {
+                ProjectWorkTimeVO projectWorkTimeVO = new ProjectWorkTimeVO();
+                projectWorkTimeVO.setProjectOrPerson(name);
+                List<BigDecimal> workTime = new ArrayList<>();
+                boolean hasNonZeroWorkTime = false;
+                if (userAndProject.isEmpty()) {
+                    for (String currentNickName : nickName) {
+                        workTime.add(BigDecimal.ZERO);
+                    }
+                } else {
+                    for (String currentNickName : nickName) {
+                        boolean foundWorkTime = false;
+                        for (WorkHoursStatisticsVO workHoursStatisticsVO : userAndProject) {
+                            String userNickName = workHoursStatisticsVO.getNickName();
+                            String projectName = workHoursStatisticsVO.getProjectName();
+                            BigDecimal userProjectWorkTime = workHoursStatisticsVO.getWorkTime();
+                            if (projectName == name && userNickName == currentNickName) {
+                                workTime.add(userProjectWorkTime);
+                                if (userProjectWorkTime.compareTo(BigDecimal.ZERO) != 0) {
+                                    hasNonZeroWorkTime = true;
+                                }
+                                foundWorkTime = true;
+                                break;
+                            }
+                        }
+                        if (!foundWorkTime) {
+                            workTime.add(BigDecimal.ZERO);
+                        }
+                    }
+                }
+                // 过滤0工时
+                if (filter != 2 || hasNonZeroWorkTime) {
+                    projectWorkTimeVO.setWorkTime(workTime);
+                    projectWorkTime.add(projectWorkTimeVO);
+                }
+            }
+            returnList.add(projectWorkTime);
+
+
+            returnList.add(pmProjectName);
+            // 重组返回数据结构
+            for (String uName : nickName) {
+                String name1 = uName;
+                UserWorkTimeVO userWorkTimeVO2 = new UserWorkTimeVO();
+                userWorkTimeVO2.setProjectOrPerson(name1);
+                List<BigDecimal> workTime1 = new ArrayList<>();
+                boolean hasNonZeroWorkTime1 = false;
+                if (userAndProjectD.isEmpty()) {
+                    for (String currentNickName : pmProjectName) {
+                        workTime1.add(BigDecimal.ZERO);
+                    }
+                } else {
+                    for (String currentNickName : pmProjectName) {
+                        boolean foundWorkTime = false;
+                        for (WorkHoursStatisticsVO workHoursStatisticsVO : userAndProjectD) {
+                            String userNickName = workHoursStatisticsVO.getNickName();
+                            String projectName = workHoursStatisticsVO.getProjectName();
+                            BigDecimal userProjectWorkTime = workHoursStatisticsVO.getWorkTime();
+                            if (userNickName == name1 && projectName == currentNickName) {
+                                workTime1.add(userProjectWorkTime);
+                                if (userProjectWorkTime.compareTo(BigDecimal.ZERO) != 0) {
+                                    hasNonZeroWorkTime1 = true;
+                                }
+                                foundWorkTime = true;
+                                break;
+                            }
+                        }
+                        if (!foundWorkTime) {
+                            workTime1.add(BigDecimal.ZERO);
+                        }
+                    }
+                }
+                // 过滤0工时
+                if (filter != 2 || hasNonZeroWorkTime1) {
+                    userWorkTimeVO2.setWorkTime(workTime1);
+                    userWorkTimeVO.add(userWorkTimeVO2);
+                }
+            }
+            returnList.add(userWorkTimeVO);
+
+        } else {
+            returnList.add(pmProjectName);
+            // 重组返回数据结构
+            for (String uName : nickName) {
+                String name = uName;
+                UserWorkTimeVO userWorkTimeVO2 = new UserWorkTimeVO();
+                userWorkTimeVO2.setProjectOrPerson(name);
+                List<BigDecimal> workTime = new ArrayList<>();
+                boolean hasNonZeroWorkTime = false;
+                if (userAndProjectD.isEmpty()) {
+                    for (String currentNickName : pmProjectName) {
+                        workTime.add(BigDecimal.ZERO);
+                    }
+                } else {
+                    for (String currentNickName : pmProjectName) {
+                        boolean foundWorkTime = false;
+                        for (WorkHoursStatisticsVO workHoursStatisticsVO : userAndProjectD) {
+                            String userNickName = workHoursStatisticsVO.getNickName();
+                            String projectName = workHoursStatisticsVO.getProjectName();
+                            BigDecimal userProjectWorkTime = workHoursStatisticsVO.getWorkTime();
+                            if (userNickName == name && projectName == currentNickName) {
+                                workTime.add(userProjectWorkTime);
+                                if (userProjectWorkTime.compareTo(BigDecimal.ZERO) != 0) {
+                                    hasNonZeroWorkTime = true;
+                                }
+                                foundWorkTime = true;
+                                break;
+                            }
+                        }
+                        if (!foundWorkTime) {
+                            workTime.add(BigDecimal.ZERO);
+                        }
+                    }
+                }
+                // 过滤0工时
+                if (filter != 2 || hasNonZeroWorkTime) {
+                    userWorkTimeVO2.setWorkTime(workTime);
+                    userWorkTimeVO.add(userWorkTimeVO2);
+                }
+            }
+            returnList.add(userWorkTimeVO);
+
+
+            returnList.add(nickName);
+            // 重组返回数据结构
+            for (String name : pmProjectName) {
+                ProjectWorkTimeVO projectWorkTimeVO = new ProjectWorkTimeVO();
+                projectWorkTimeVO.setProjectOrPerson(name);
+                List<BigDecimal> workTime = new ArrayList<>();
+                boolean hasNonZeroWorkTime = false;
+                if (userAndProject.isEmpty()) {
+                    for (String currentNickName : nickName) {
+                        workTime.add(BigDecimal.ZERO);
+                    }
+                } else {
+                    for (String currentNickName : nickName) {
+                        boolean foundWorkTime = false;
+                        for (WorkHoursStatisticsVO workHoursStatisticsVO : userAndProject) {
+                            String userNickName = workHoursStatisticsVO.getNickName();
+                            String projectName = workHoursStatisticsVO.getProjectName();
+                            BigDecimal userProjectWorkTime = workHoursStatisticsVO.getWorkTime();
+                            if (projectName == name && userNickName == currentNickName) {
+                                workTime.add(userProjectWorkTime);
+                                if (userProjectWorkTime.compareTo(BigDecimal.ZERO) != 0) {
+                                    hasNonZeroWorkTime = true;
+                                }
+                                foundWorkTime = true;
+                                break;
+                            }
+                        }
+                        if (!foundWorkTime) {
+                            workTime.add(BigDecimal.ZERO);
+                        }
+                    }
+                }
+                // 过滤0工时
+                if (filter != 2 || hasNonZeroWorkTime) {
+                    projectWorkTimeVO.setWorkTime(workTime);
+                    projectWorkTime.add(projectWorkTimeVO);
+                }
+            }
+            returnList.add(projectWorkTime);
+        }
+        return returnList;
+    }
+
+    /**
+     * 工时-明细-导出
+     * @return
+     */
+    @DataScope(deptAlias = "u")
+    @Override
+    public List<WorkTimeExportVO> workHourStatisticExport(PmWorkHourStatisticRequestVO requestVO) {
+        List<Long> userIds = new ArrayList<>();
+        if (Objects.isNull(requestVO.getUserIds()) || requestVO.getUserIds().isEmpty()) {
+            userIds.add(SecurityUtils.getUserId());
+            requestVO.setUserIds(userIds);
+        }
+        return pmWorkContentMapper.workHourStatisticExport(requestVO);
+    }
+
+    /**
+     * 工时-统计-导出
+     * @param userId
+     * @param projectId
+     * @param startDate
+     * @param endDate
+     * @return
+     */
+    @DataScope
+    @Override
+    public List<WorkTimeExportTwoVO> workHourExport(Long userId, Integer projectId, Integer filter, String
+            startDate, String endDate, Integer workerOrProject) {
+        return Collections.emptyList();
+    }
+
+}

+ 783 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/service/impl/PmWorkReportServiceImpl.java

@@ -0,0 +1,783 @@
+package com.usky.pm.service.impl;
+
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.usky.common.core.exception.BusinessException;
+import com.usky.common.mybatis.core.AbstractCrudService;
+import com.usky.common.security.utils.SecurityUtils;
+import com.usky.pm.domain.PmProject;
+import com.usky.pm.domain.PmReceive;
+import com.usky.pm.domain.PmWorkContent;
+import com.usky.pm.domain.PmWorkReport;
+import com.usky.pm.mapper.PmReceiveMapper;
+import com.usky.pm.mapper.PmWorkContentMapper;
+import com.usky.pm.mapper.PmWorkReportMapper;
+import com.usky.pm.mapper.SysUserMapper;
+import com.usky.pm.service.PmProjectService;
+import com.usky.pm.service.PmWorkContentService;
+import com.usky.pm.service.PmWorkReportService;
+import com.usky.pm.service.config.DingTalkAndMessage;
+import com.usky.pm.service.vo.PmProjectTotalWorkTimeVo;
+import com.usky.pm.service.vo.PmProjectWorkTimeVo;
+import com.usky.system.RemoteMceService;
+import com.usky.system.domain.SysUser;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.DayOfWeek;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.Year;
+import java.time.temporal.TemporalAdjusters;
+import java.util.*;
+import java.util.stream.Collectors;
+
+
+/**
+ * <p>
+ * 工作报告表 服务实现类
+ * </p>
+ *
+ * @author fu
+ * @since 2024-05-20
+ */
+@Slf4j
+@Service
+public class PmWorkReportServiceImpl extends AbstractCrudService<PmWorkReportMapper, PmWorkReport> implements PmWorkReportService {
+
+
+    @Autowired
+    private PmWorkContentMapper pmWorkContentMapper;
+
+    @Autowired
+    private PmWorkReportMapper pmWorkReportMapper;
+
+    @Autowired
+    private PmProjectService pmProjectService;
+
+    @Autowired
+    private PmWorkContentService pmWorkContentService;
+
+    @Autowired
+    private DingTalkAndMessage dingTalkAndMessage;
+
+    @Autowired
+    private PmReceiveMapper pmReceiveMapper;
+
+    @Autowired
+    private SysUserMapper sysUserMapper;
+
+    @Autowired
+    private RemoteMceService remoteMceService;
+
+    private static final String INFO_TITLE = "日报未提交提醒";
+    private static final String INFO_CONTENT = "您今天的日报还未提交,别忘记哦~";
+    private static final int INFO_TYPE = 5;
+
+    /**
+     * 获取时间内工作报告
+     *
+     * @param startDate 开始时间
+     * @param endDate   结束时间
+     * @param reportId  报告id
+     * @return
+     */
+    @Override
+    public List<Map<String, List<PmWorkReport>>> weekWork(String startDate, String endDate, Integer reportId) {
+        Long userId = SecurityUtils.getUserId();
+        Integer tenantId = SecurityUtils.getTenantId();
+
+        List<Map<String, List<PmWorkReport>>> returnList = new ArrayList<>();
+        List<PmWorkReport> report = new ArrayList<>();
+        Map<String, List<PmWorkReport>> weekData = new HashMap<>();
+        Map<String, List<PmWorkReport>> newData = new HashMap<>();
+        LocalDate startDate1 = null;
+        LocalDate endDate1 = null;
+        if (startDate == null && endDate == null) {
+            startDate1 = LocalDate.now().with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
+            endDate1 = LocalDate.now().with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY));
+        } else {
+            startDate1 = LocalDate.parse(startDate);
+            endDate1 = LocalDate.parse(endDate);
+        }
+        List<LocalDate> dates = new ArrayList<>();
+        LocalDate tempDate = startDate1;
+        while (!tempDate.isAfter(endDate1)) {
+            dates.add(tempDate);
+            tempDate = tempDate.plusDays(1);
+        }
+
+        // 返回最新的一条数据给前端填充
+        LambdaQueryWrapper<PmWorkReport> wrapper2 = Wrappers.lambdaQuery();
+        wrapper2.eq(PmWorkReport::getSubmitterId, userId)
+                .orderByDesc(PmWorkReport::getSubmitDate)
+                .last("LIMIT 1");
+        report = baseMapper.selectList(wrapper2);
+        if (!report.isEmpty()) {
+            Integer pId = report.get(0).getId();
+            LambdaQueryWrapper<PmWorkContent> wrapper = Wrappers.lambdaQuery();
+            wrapper.eq(PmWorkContent::getReportId, pId);
+            List<PmWorkContent> contents1 = pmWorkContentMapper.selectList(wrapper);
+            report.get(0).setWorkContents(contents1);
+        }
+
+        // 固定返回七条数据,没有内容也要设置时间给前端渲染
+        LambdaQueryWrapper<PmWorkReport> queryWrapperR = Wrappers.lambdaQuery();
+        queryWrapperR.eq(PmWorkReport::getTenantId, tenantId)
+    .eq(PmWorkReport::getSubmitterId, userId)
+                .between(reportId == 0, PmWorkReport::getReportDate, startDate1, endDate1)
+                .eq(reportId != 0, PmWorkReport::getId, reportId)
+                .orderByAsc(PmWorkReport::getReportDate);
+        List<PmWorkReport> reports = baseMapper.selectList(queryWrapperR);
+        if (reports.isEmpty()) {
+            List<PmWorkReport> reports1 = new ArrayList<>();
+            for (int f = 0; f < dates.size(); f++) {
+                List<PmWorkContent> content = new ArrayList<>();
+                PmWorkReport report1 = new PmWorkReport();
+                report1.setReportDate(dates.get(f));
+                report1.setWorkContents(content);
+                reports1.add(report1);
+            }
+            weekData.put("weekData", reports1);
+            newData.put("newData", report);
+            returnList.add(0, newData);
+            returnList.add(1, weekData);
+            return returnList;
+        }
+        List<Integer> ids = new ArrayList<>();
+        for (PmWorkReport pwr : reports) {
+            ids.add(pwr.getId());
+        }
+        LambdaQueryWrapper<PmWorkContent> queryWrapperC = Wrappers.lambdaQuery();
+        queryWrapperC.select(PmWorkContent::getId, PmWorkContent::getWorkContent, PmWorkContent::getWorkTime, PmWorkContent::getProjectId,
+                        PmWorkContent::getProjectName, PmWorkContent::getReportId, PmWorkContent::getCreateTime, PmWorkContent::getUpdateTime)
+                .in(PmWorkContent::getReportId, ids)
+                .orderByDesc(PmWorkContent::getWorkTime);
+        List<PmWorkContent> contents = pmWorkContentMapper.selectList(queryWrapperC);
+        int a, b;
+        for (int i = 0; i < reports.size(); i++) {
+            a = reports.get(i).getId();
+            List<PmWorkContent> contentList = new ArrayList<>();
+            for (int j = 0; j < contents.size(); j++) {
+                b = contents.get(j).getReportId();
+                if (b == a) {
+                    contentList.add(contents.get(j));
+                }
+            }
+            reports.get(i).setWorkContents(contentList);
+        }
+        if (reports.size() == 7 || reportId != 0) {
+            weekData.put("weekData", reports);
+            LambdaQueryWrapper<PmWorkReport> wrapper1 = Wrappers.lambdaQuery();
+            wrapper1.eq(PmWorkReport::getSubmitterId, userId)
+                    .orderByDesc(PmWorkReport::getReportDate)
+                    .last("LIMIT 1");
+            report = this.list(wrapper1);
+            Integer rId = report.get(0).getId();
+            LambdaQueryWrapper<PmWorkContent> wrapperC = Wrappers.lambdaQuery();
+            wrapperC.eq(PmWorkContent::getProjectId, rId);
+            List<PmWorkContent> pmWorkContents = pmWorkContentMapper.selectList(wrapperC);
+            report.get(0).setWorkContents(pmWorkContents);
+            newData.put("newData", report);
+            returnList.add(0, newData);
+            returnList.add(1, weekData);
+            return returnList;
+        }
+        List<PmWorkReport> reportList = new ArrayList<>();
+        for (int d = 0; d < dates.size(); d++) {
+            boolean matchFound = false;
+            for (int c = 0; c < reports.size(); c++) {
+                if (dates.get(d).isEqual(reports.get(c).getReportDate())) {
+                    reportList.add(reports.get(c));
+                    matchFound = true;
+                    break;
+                }
+            }
+            if (!matchFound) {
+                PmWorkReport newReport = new PmWorkReport();
+                List<PmWorkContent> newContent = new ArrayList<>();
+                newReport.setReportDate(dates.get(d));
+                newReport.setWorkContents(newContent);
+                reportList.add(newReport);
+            }
+        }
+        weekData.put("weekData", reportList);
+        newData.put("newData", report);
+        returnList.add(0, newData);
+        returnList.add(1, weekData);
+        return returnList;
+    }
+
+    /**
+     * 新增、编辑工作报告
+     */
+    @Transactional
+    @Override
+    public void addReport(PmWorkReport pmWorkReport) {
+        Long deptId = SecurityUtils.getLoginUser().getSysUser().getDeptId();
+        String userName = SecurityUtils.getUsername();
+        Long userId = SecurityUtils.getUserId();
+        Integer tenantId = SecurityUtils.getTenantId();
+        BigDecimal totalWorkTime = BigDecimal.ZERO;
+        LocalDateTime dateTime = LocalDateTime.now();
+        LocalDateTime timingTime = pmWorkReport.getTimingTime();
+        LocalDateTime currentTimePlusMinutes = dateTime.plusMinutes(5);
+        String ccTo = pmWorkReport.getCcTo();
+        Integer rid = pmWorkReport.getId();
+        List<PmWorkContent> contents = pmWorkReport.getWorkContents();
+
+        List<PmWorkContent> workContents = pmWorkReport.getWorkContents();
+        if (workContents == null) {
+            throw new BusinessException("报告内容不能为空,请检查后提交!");
+        } else {
+            for (PmWorkContent workContent : workContents) {
+                BigDecimal workTime = workContent.getWorkTime();
+                // 单个工时大于零
+                if (workTime == null) {
+                    throw new BusinessException("工时不能为空,请检查后提交!");
+                } else if (workTime.compareTo(BigDecimal.ZERO) <= 0) {
+                    throw new BusinessException("工时必须大于零!");
+                } else if (workTime.scale() > 1) {
+                    throw new BusinessException("工时小数位超出长度,请重新输入!");
+                } else if (workTime.precision() - workTime.scale() > 3) {
+                    throw new BusinessException("工时整数位超出长度,请重新输入!");
+                }
+
+                String workContent1 = workContent.getWorkContent();
+                // 工作内容不允许为空或空字符串
+                if (StringUtils.isBlank(workContent1)) {
+                    throw new BusinessException("工作内容不能为空,请检查后提交!");
+                }
+            }
+        }
+        Set<Integer> projectIds2 = new HashSet<>();
+        for (PmWorkContent workContent : pmWorkReport.getWorkContents()) {
+            if (!projectIds2.add(workContent.getProjectId())) {
+                throw new BusinessException("存在重复项目,请检查后提交!");
+            }
+        }
+        for (PmWorkContent a : pmWorkReport.getWorkContents()) {
+            totalWorkTime = totalWorkTime.add(a.getWorkTime());
+        }
+        // 判断总工时是否超过24h
+        BigDecimal maxWorkTimePerDay = BigDecimal.valueOf(24);
+        if (totalWorkTime.compareTo(maxWorkTimePerDay) >= 0) {
+            throw new BusinessException("总工时超过24小时,请检查后提交!");
+        }
+
+        // 判断是否携带reportId,不写带则为新增报告,携带则为编辑(更新)报告
+        if (rid == null) {
+            // 判断是否今天已提交过
+            LambdaQueryWrapper<PmWorkReport> wrapper = Wrappers.lambdaQuery();
+            wrapper.eq(PmWorkReport::getReportDate, pmWorkReport.getReportDate())
+                    .eq(PmWorkReport::getSubmitterId, userId)
+                    .eq(PmWorkReport::getTenantId, tenantId);
+            if (!pmWorkReportMapper.selectList(wrapper).isEmpty()) {
+                throw new BusinessException("今天已提交工作报告,请勿重复提交!");
+            }
+
+            PmWorkReport newReport = new PmWorkReport();
+            newReport.setReportDate(pmWorkReport.getReportDate());
+            newReport.setSubmitterId(userId);
+            newReport.setSubmitDate(LocalDateTime.now());
+            newReport.setCcTo(ccTo);
+            newReport.setCoordinateWork(pmWorkReport.getCoordinateWork());
+            newReport.setTomorrowPlan(pmWorkReport.getTomorrowPlan());
+            newReport.setCreateBy(userName);
+            newReport.setCreateTime(dateTime);
+            newReport.setDeptId(deptId);
+            newReport.setTenantId(tenantId);
+            newReport.setTotalHours(totalWorkTime);
+            newReport.setSendDingTalk(pmWorkReport.getSendDingTalk());
+            newReport.setReportFile(pmWorkReport.getReportFile());
+            newReport.setReportImage(pmWorkReport.getReportImage());
+            newReport.setReportStatus(1);
+            if (pmWorkReport.getIsRegularlySend() == 1 && timingTime != null) {
+                if (timingTime.isAfter(currentTimePlusMinutes)) {
+                    newReport.setIsRegularlySend(pmWorkReport.getIsRegularlySend());
+                    newReport.setTimingTime(timingTime);
+                    newReport.setReportStatus(0);
+                } else {
+                    throw new BusinessException("定时发送支持选中5分钟后的时间,请重新选择时间");
+                }
+            }
+            try {
+                pmWorkReportMapper.insert(newReport);
+
+            } catch (Exception e) {
+                throw new BusinessException("新增报告异常!请联系管理员" + e);
+            }
+
+            // 获取报告中所有项目id
+            List<Integer> projectIds = new ArrayList<>();
+            for (PmWorkContent b1 : pmWorkReport.getWorkContents()) {
+                projectIds.add(b1.getProjectId());
+            }
+            // 查出所有项目id对应项目名
+            List<PmProject> project = pmProjectService.projectName(projectIds);
+            // 将项目名重新赋值
+            List<PmWorkContent> pmWorkContents = new ArrayList<>();
+            for (PmWorkContent b : pmWorkReport.getWorkContents()) {
+                for (int c = 0; c < project.size(); c++) {
+                    if (b.getProjectId().equals(project.get(c).getId())) {
+                        b.setProjectName(project.get(c).getProjectName());
+                    }
+                }
+                PmWorkContent newContent = new PmWorkContent();
+                newContent.setReportId(newReport.getId());
+                newContent.setProjectId(b.getProjectId());
+                projectIds.add(b.getReportId());
+                newContent.setProjectName(b.getProjectName());
+                newContent.setSubmitterId(userId);
+                newContent.setWorkContent(b.getWorkContent());
+                newContent.setWorkTime(b.getWorkTime());
+                newContent.setCreateBy(userName);
+                newContent.setCreateTime(dateTime);
+                newContent.setDeptId(deptId);
+                newContent.setTenantId(tenantId);
+                pmWorkContentMapper.insert(newContent);
+                pmWorkContents.add(newContent);
+            }
+
+            // 抄送人不为空、非定时发送
+            if (timingTime == null) {
+                if (StringUtils.isNotEmpty(ccTo)) {
+                    // 推送消息中心
+                    dingTalkAndMessage.sendAsyncMessage(newReport);
+                    // 存入报告消息表
+                    receiveMessages(ccTo, newReport.getId());
+                }
+                // 是否同步钉钉
+                if (pmWorkReport.getSendDingTalk().equals(1)) {
+/*                    String dingTalkUserId = dingTalkAndMessage.getDingTalkUserId(userId);
+                    if (StringUtils.isBlank(dingTalkUserId)){
+                        throw new BusinessException("注册手机号与钉钉手机号不一致,无法抄送钉钉");
+                    }*/
+                    dingTalkAndMessage.sendDingTalkDailyReport(newReport, pmWorkContents);
+                }
+            }
+        } else {
+            PmWorkReport rp = new PmWorkReport();
+            LambdaQueryWrapper<PmWorkContent> queryWrapper = Wrappers.lambdaQuery();
+            queryWrapper.select(PmWorkContent::getReportId, PmWorkContent::getSubmitterId, PmWorkContent::getCreateBy, PmWorkContent::getCreateTime, PmWorkContent::getDeptId, PmWorkContent::getTenantId)
+                    .eq(PmWorkContent::getReportId, rid)
+                    .last("LIMIT 1");
+            PmWorkContent f = pmWorkContentMapper.selectOne(queryWrapper);
+
+            if (pmWorkReport.getReportStatus().equals(0)) {
+                if (timingTime == null) {
+                    rp.setTimingTime(timingTime);
+                } else if (timingTime.isAfter(currentTimePlusMinutes)) {
+                    rp.setTimingTime(timingTime);
+                } else {
+                    throw new BusinessException("定时发送支持选中5分钟后的时间,请重新选择时间!");
+                }
+                rp.setId(rid);
+                rp.setSubmitterId(pmWorkReport.getSubmitterId());
+                rp.setCcTo(ccTo);
+                rp.setSubmitDate(LocalDateTime.now());
+                rp.setCoordinateWork(pmWorkReport.getCoordinateWork());
+                rp.setTomorrowPlan(pmWorkReport.getTomorrowPlan());
+                rp.setCreateBy(pmWorkReport.getCreateBy());
+                rp.setUpdateBy(userName);
+                rp.setUpdateTime(dateTime);
+                rp.setTotalHours(totalWorkTime);
+                rp.setIsRegularlySend(pmWorkReport.getIsRegularlySend());
+                rp.setReportFile(pmWorkReport.getReportFile());
+                rp.setReportImage(pmWorkReport.getReportImage());
+                rp.setSendDingTalk(pmWorkReport.getSendDingTalk());
+                pmWorkReportMapper.updateById(rp);
+
+                pmWorkContentService.deleteContent(rid);
+                List<PmWorkContent> contentsList = new ArrayList<>();
+                for (PmWorkContent e : contents) {
+                    e.setReportId(f.getReportId());
+                    e.setUpdateBy(userName);
+                    e.setUpdateTime(dateTime);
+                    e.setSubmitterId(f.getSubmitterId());
+                    e.setCreateTime(f.getCreateTime());
+                    e.setCreateBy(f.getCreateBy());
+                    e.setDeptId(f.getDeptId());
+                    e.setTenantId(f.getTenantId());
+                    pmWorkContentMapper.insert(e);
+                    contentsList.add(e);
+                }
+
+                if (pmWorkReport.getIsRegularlySend().equals(0)) {
+                    if (pmWorkReport.getSendDingTalk().equals(1)) {
+                        dingTalkAndMessage.sendDingTalkDailyReport(rp, contents);
+                    }
+                    rp.setReportStatus(1);
+                    pmWorkReportMapper.updateById(rp);
+                    if (StringUtils.isNotBlank(ccTo)) {
+                        dingTalkAndMessage.sendAsyncMessage(rp);
+                        receiveMessages(ccTo, rid);
+                    }
+                }
+            } else {
+                rp.setId(rid);
+                rp.setCoordinateWork(pmWorkReport.getCoordinateWork());
+                rp.setTomorrowPlan(pmWorkReport.getTomorrowPlan());
+                rp.setUpdateBy(userName);
+                rp.setUpdateTime(dateTime);
+                rp.setTotalHours(totalWorkTime);
+                rp.setIsRegularlySend(pmWorkReport.getIsRegularlySend());
+                rp.setReportFile(pmWorkReport.getReportFile());
+                rp.setReportImage(pmWorkReport.getReportImage());
+                pmWorkReportMapper.updateById(rp);
+
+                pmWorkContentService.deleteContent(rid);
+                List<PmWorkContent> contentsList = new ArrayList<>();
+                for (PmWorkContent e : contents) {
+                    e.setReportId(f.getReportId());
+                    e.setUpdateBy(userName);
+                    e.setUpdateTime(dateTime);
+                    e.setSubmitterId(f.getSubmitterId());
+                    e.setCreateTime(f.getCreateTime());
+                    e.setCreateBy(f.getCreateBy());
+                    e.setDeptId(f.getDeptId());
+                    e.setTenantId(f.getTenantId());
+                    pmWorkContentMapper.insert(e);
+                    contentsList.add(e);
+                }
+            }
+        }
+    }
+
+    private void receiveMessages(String ids, Integer reportId) {
+        if (StringUtils.isBlank(ids)) {
+            return;
+        }
+        List<Long> longList = Arrays.stream(ids.split(","))
+                .map(Long::parseLong)
+                .collect(Collectors.toList());
+        List<SysUser> users = pmWorkContentService.nickNames(longList);
+
+        LambdaQueryWrapper<PmWorkReport> queryWrapper = Wrappers.lambdaQuery();
+        queryWrapper.select(PmWorkReport::getCreateBy)
+                .eq(PmWorkReport::getId, reportId);
+        PmWorkReport pmWorkReport = pmWorkReportMapper.selectOne(queryWrapper);
+        String createBy = pmWorkReport.getCreateBy();
+
+        if (longList.size() > 0) {
+            for (Long id : longList) {
+                PmReceive pmReceive = new PmReceive();
+                pmReceive.setReceiverId(id);
+                pmReceive.setReportId(reportId);
+                pmReceive.setCreateBy(createBy);
+                pmReceive.setCreateTime(LocalDateTime.now());
+                for (SysUser user : users) {
+                    if (user.getUserId().equals(id)) {
+                        pmReceive.setReceiverName(user.getUserName());
+                        pmReceive.setDeptId(user.getDeptId());
+                        pmReceive.setTenantId(user.getTenantId());
+                    }
+                }
+                pmReceive.setReadFlag(0);
+                try {
+                    pmReceiveMapper.insert(pmReceive);
+                } catch (Exception e) {
+                    log.error("报告接收人:" + id + ",报告发送人:" + createBy + ",存入报告消息表失败!");
+                    throw new BusinessException(createBy + " 的工作报告存入报告消息表失败!");
+                }
+            }
+        }
+    }
+
+    @Override
+    public PmProjectTotalWorkTimeVo countTime(Integer nowDate, Integer dateType, Integer dateNum) {
+        int scale = 4;
+        RoundingMode roundingMode = RoundingMode.HALF_UP;
+        PmProjectTotalWorkTimeVo projectTotalWorkTimeVo = new PmProjectTotalWorkTimeVo();
+        Map<String, LocalDateTime> stringLocalDateTimeMap = countWeekOrMonth(nowDate, dateType, dateNum);
+        LocalDateTime startTime = stringLocalDateTimeMap.get("start");
+        LocalDateTime endTime = stringLocalDateTimeMap.get("end");
+        BigDecimal totalWorkTime = BigDecimal.ZERO;
+
+        List<PmProjectWorkTimeVo> week = pmWorkContentMapper.workTimeCount(startTime, endTime, SecurityUtils.getUserId(), SecurityUtils.getTenantId());
+        if (week.isEmpty()) {
+            projectTotalWorkTimeVo.setTotalWorkTime(totalWorkTime);
+            projectTotalWorkTimeVo.setProjectWorkTime(week);
+            return projectTotalWorkTimeVo;
+        }
+        totalWorkTime = week.stream()
+                .map(PmProjectWorkTimeVo::getWorkTime)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+
+        List<PmProjectWorkTimeVo> projectWorkTimeWithPercentage = calculatePercentage(week, totalWorkTime, scale, roundingMode);
+        projectTotalWorkTimeVo.setTotalWorkTime(totalWorkTime);
+        projectTotalWorkTimeVo.setProjectWorkTime(projectWorkTimeWithPercentage);
+
+        return projectTotalWorkTimeVo;
+    }
+
+    // 计算百分比
+    private List<PmProjectWorkTimeVo> calculatePercentage(List<PmProjectWorkTimeVo> workTimeList, BigDecimal totalWorkTime, int scale, RoundingMode roundingMode) {
+        BigDecimal totalPercentage = BigDecimal.ZERO;
+        List<PmProjectWorkTimeVo> projectWorkTimeWithPercentage = new ArrayList<>();
+
+        for (int i = 0; i < workTimeList.size(); i++) {
+            PmProjectWorkTimeVo workTimeVo = workTimeList.get(i);
+            BigDecimal percentage = workTimeVo.getWorkTime().divide(totalWorkTime, scale, roundingMode);
+            totalPercentage = totalPercentage.add(percentage);
+
+            if (totalPercentage.compareTo(BigDecimal.ONE) > 0) {
+                percentage = BigDecimal.ONE.subtract(totalPercentage.subtract(percentage));
+            }
+
+            workTimeVo.setPercentage(percentage);
+            projectWorkTimeWithPercentage.add(workTimeVo);
+        }
+
+        return projectWorkTimeWithPercentage;
+    }
+
+    /**
+     * @description: 计算周和月
+     * @author: fu
+     * @date: 2024/8/7 20:17
+     * @param: [dateType, dateNum]
+     * @return: java.util.Map<java.lang.String, java.time.LocalDateTime>
+     **/
+    public Map<String, LocalDateTime> countWeekOrMonth(Integer nowYear, Integer dateType, Integer dateNum) {
+        Map<String, LocalDateTime> timePeriod = new HashMap<>();
+        Year currentYear = Year.now();
+        if (nowYear != null && nowYear > 0) {
+            currentYear = Year.of(nowYear);
+        }
+
+        if (dateType == 1) {
+            LocalDate lastDayOfYear = currentYear.atDay(currentYear.length());
+            LocalDate lastMondayOfYear = lastDayOfYear.with(TemporalAdjusters.previousOrSame(java.time.DayOfWeek.MONDAY));
+            long totalWeeks = lastMondayOfYear.getDayOfYear() / 7 + 1;
+            if (dateNum > totalWeeks) {
+                throw new BusinessException("周数错误!请重新选择");
+            }
+
+            int yearValue = currentYear.getValue();
+            LocalDate firstDayOfYear = LocalDate.of(yearValue, 1, 1);
+            LocalDate firstMondayOfYear = firstDayOfYear.with(TemporalAdjusters.nextOrSame(DayOfWeek.MONDAY));
+
+            DayOfWeek dayOfWeek = firstDayOfYear.getDayOfWeek();
+            if (dayOfWeek != DayOfWeek.MONDAY) {
+                dateNum -= 1;
+            }
+
+            LocalDate startOfWeek = firstMondayOfYear.plusWeeks(dateNum - 1);
+            LocalDate endOfWeek = startOfWeek.plusWeeks(1).minusDays(1);
+            LocalDateTime startTimeOfWeek = startOfWeek.atStartOfDay();
+            LocalDateTime endTimeOfWeek = endOfWeek.atTime(23, 59, 59);
+            timePeriod.put("start", startTimeOfWeek);
+            timePeriod.put("end", endTimeOfWeek);
+
+        } else if (dateType == 2) {
+            if (dateNum < 1 || dateNum > 12) {
+                throw new BusinessException("月数错误!请重新选择");
+            }
+            LocalDate startOfMonth = currentYear.atMonth(dateNum).atDay(1);
+            LocalDate endOfMonth = startOfMonth.with(TemporalAdjusters.lastDayOfMonth());
+            LocalDateTime startTimeOfMonth = startOfMonth.atStartOfDay();
+            LocalDateTime endTimeOfMonth = endOfMonth.atTime(23, 59, 59);
+            timePeriod.put("start", startTimeOfMonth);
+            timePeriod.put("end", endTimeOfMonth);
+
+        } else {
+            throw new BusinessException("类型选择错误!请选择周或月");
+        }
+
+        return timePeriod;
+    }
+
+
+    /**
+     * @description: 工作报告定时发送
+     * @author: fu
+     * @date: 2024/8/7 20:17
+     * @param: [time] 开始时间
+     * @return: void
+     **/
+    @Async("asyncServiceExecutor")
+    @Override
+    public void timedSending(LocalDateTime time) {
+        log.info("定时报告任务开始---------------------------");
+        LambdaQueryWrapper<PmWorkReport> reports = Wrappers.lambdaQuery();
+        reports.eq(PmWorkReport::getReportStatus, 0)
+                .eq(PmWorkReport::getIsRegularlySend, 1)
+                .le(PmWorkReport::getTimingTime, time)
+                .orderByAsc(PmWorkReport::getTimingTime);
+        List<PmWorkReport> reportList = pmWorkReportMapper.selectList(reports);
+        if (!reportList.isEmpty()) {
+            List<Integer> reportIds = new ArrayList<>();
+            for (PmWorkReport report : reportList) {
+                reportIds.add(report.getId());
+            }
+            LambdaQueryWrapper<PmWorkContent> reportContents = Wrappers.lambdaQuery();
+            reportContents.in(PmWorkContent::getReportId, reportIds);
+            List<PmWorkContent> pmWorkContentList = pmWorkContentMapper.selectList(reportContents);
+            for (PmWorkReport report : reportList) {
+                Integer id = report.getId();
+                List<PmWorkContent> workContents = new ArrayList<>();
+                for (PmWorkContent content : pmWorkContentList) {
+                    Integer reportId = content.getReportId();
+                    if (reportId.equals(id)) {
+                        workContents.add(content);
+                    }
+                }
+                try {
+                    dingTalkAndMessage.sendAsyncMessage(report);
+                } catch (Exception e) {
+                    log.error("定时报告" + report.getId() + "调用消息中心异常" + e);
+                }
+                if (report.getSendDingTalk() == 1) {
+                    try {
+                        dingTalkAndMessage.sendDingTalkDailyReport(report, workContents);
+                    } catch (Exception e) {
+                        log.error("定时报告" + report.getId() + "发送钉钉异常" + e);
+                    }
+                }
+
+                try {
+                    report.setReportStatus(1);
+                    report.setSubmitDate(time);
+                    pmWorkReportMapper.updateById(report);
+                    receiveMessages(report.getCcTo(), report.getId());
+                } catch (Exception e) {
+                    log.error("定时报告" + report.getId() + "更新状态异常" + e);
+                } finally {
+                    report.setReportStatus(1);
+                    report.setSubmitDate(time);
+                    pmWorkReportMapper.updateById(report);
+                }
+            }
+        } else {
+            log.info("当前没有定时工作报告可发送");
+        }
+        log.info("定时报告任务结束---------------------------");
+    }
+
+    /**
+     * @description: 删除报告
+     * @author: fu
+     * @date: 2024/8/7 20:17
+     * @param: [reportId]
+     * @return: void
+     **/
+    @Transactional
+    @Override
+    public void deleteContent(Integer reportId) {
+        if (reportId == null || reportId < 0) {
+            throw new BusinessException("报告id不正确,删除报告失败!");
+        }
+
+        Long userId = SecurityUtils.getUserId();
+        LambdaQueryWrapper<PmWorkReport> report = Wrappers.lambdaQuery();
+        report.select(PmWorkReport::getId)
+                .eq(PmWorkReport::getSubmitterId, userId)
+                .eq(PmWorkReport::getId, reportId);
+        PmWorkReport pmWorkReport = pmWorkReportMapper.selectOne(report);
+        if (pmWorkReport == null) {
+            throw new BusinessException("您没有删除该报告权限!");
+        }
+
+        LambdaQueryWrapper<PmWorkContent> reportContents = Wrappers.lambdaQuery();
+        reportContents.eq(PmWorkContent::getReportId, reportId);
+        pmWorkContentMapper.delete(reportContents);
+
+        try {
+            pmWorkReportMapper.deleteById(reportId);
+        } catch (Exception e) {
+            log.error("删除报告失败!" + e);
+            throw new BusinessException("删除报告失败请联系管理员");
+        }
+    }
+
+    /**
+     * @description: 查询定时报告
+     * @author: fu
+     * @date: 2024/8/7 20:17
+     * @param: []
+     * @return: java.util.List<com.usky.pm.domain.PmWorkReport>
+     **/
+    @Override
+    public List<PmWorkReport> timedReportQuery() {
+        Long userId = SecurityUtils.getUserId();
+        LambdaQueryWrapper<PmWorkReport> reports = Wrappers.lambdaQuery();
+        reports.eq(PmWorkReport::getSubmitterId, userId)
+                .eq(PmWorkReport::getReportStatus, 0)
+                .orderByAsc(PmWorkReport::getReportDate);
+        List<PmWorkReport> timedReports = pmWorkReportMapper.selectList(reports);
+
+        if (timedReports.isEmpty()) {
+            return timedReports;
+        }
+
+        List<Integer> reportIds = timedReports.stream().map(PmWorkReport::getId).collect(Collectors.toList());
+
+        List<Long> userIds = timedReports.stream().map(PmWorkReport::getSubmitterId).collect(Collectors.toList());
+        List<SysUser> sysUsers = pmWorkContentService.nickNames(userIds);
+        Map<Long, String> userNicknameMap = sysUsers.stream().collect(Collectors.toMap(SysUser::getUserId, SysUser::getNickName));
+
+        LambdaQueryWrapper<PmWorkContent> reportContents = Wrappers.lambdaQuery();
+        reportContents.in(PmWorkContent::getReportId, reportIds);
+        List<PmWorkContent> pmWorkContentList = pmWorkContentMapper.selectList(reportContents);
+        for (PmWorkReport report : timedReports) {
+            Integer id = report.getId();
+            List<PmWorkContent> workContents = new ArrayList<>();
+            for (PmWorkContent content : pmWorkContentList) {
+                Integer reportId = content.getReportId();
+                if (reportId.equals(id)) {
+                    workContents.add(content);
+                }
+            }
+            report.setWorkContents(workContents);
+            report.setCreateBy(userNicknameMap.get(report.getSubmitterId()));
+        }
+        return timedReports;
+    }
+
+    @Async
+    @Override
+    public void reportSubmissionReminder() {
+        Integer tenantId = SecurityUtils.getTenantId();
+        List<Long> userIds = new ArrayList<>();
+        LambdaQueryWrapper<PmWorkReport> todayReports = Wrappers.lambdaQuery();
+        todayReports.eq(PmWorkReport::getReportStatus, 1)
+                .eq(PmWorkReport::getReportDate, LocalDate.now())
+                .eq(PmWorkReport::getTenantId, tenantId);
+        List<PmWorkReport> todayReportsList = pmWorkReportMapper.selectList(todayReports);
+        if (!todayReportsList.isEmpty()) {
+            userIds = todayReportsList.stream().map(PmWorkReport::getSubmitterId).collect(Collectors.toList());
+        }
+
+        LambdaQueryWrapper<SysUser> userQueryWrapper = Wrappers.lambdaQuery();
+        userQueryWrapper.select(SysUser::getUserId, SysUser::getNickName)
+                .eq(SysUser::getTenantId, tenantId)
+                .eq(SysUser::getStatus, 0)
+                .eq(SysUser::getDelFlag, 0)
+                .notIn(!userIds.isEmpty(), SysUser::getUserId, userIds);
+        userIds = sysUserMapper.selectList(userQueryWrapper).stream().map(SysUser::getUserId).collect(Collectors.toList());
+
+        JSONObject jsonObject = new JSONObject();
+        jsonObject.put("infoTitle", INFO_TITLE);
+        jsonObject.put("infoContent", INFO_CONTENT);
+        jsonObject.put("infoType", INFO_TYPE);
+        jsonObject.put("id", 0);
+        jsonObject.put("infoTypeName", INFO_TITLE);
+        jsonObject.put("userName", "报告提醒通知");
+        jsonObject.put("userIds", userIds);
+
+        // 推送消息中心
+        try {
+            remoteMceService.addMce(jsonObject.toString());
+        } catch (Exception e) {
+            log.error("报告提醒通知推送消息中心异常" + e);
+        }
+    }
+}

+ 25 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/service/vo/PmProjectTotalWorkTimeVo.java

@@ -0,0 +1,25 @@
+package com.usky.pm.service.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * @description:TODO
+ * @author: fu
+ * @create: 2024-06-04 16:32
+ */
+@Data
+public class PmProjectTotalWorkTimeVo {
+    /**
+     * 总工时
+     */
+    private BigDecimal totalWorkTime;
+
+    /**
+     * 各项目工时及占比
+     */
+    private List<PmProjectWorkTimeVo> projectWorkTime;
+}
+

+ 19 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/service/vo/PmProjectWorkTimeTwoVo.java

@@ -0,0 +1,19 @@
+package com.usky.pm.service.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.List;
+
+/**
+ * @author fu
+ * @date 2024/8/16
+ */
+@Data
+public class PmProjectWorkTimeTwoVo {
+    //日期
+    private List<LocalDate> dateList;
+    //工时
+    private List<BigDecimal> workTimeList;
+}

+ 29 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/service/vo/PmProjectWorkTimeVo.java

@@ -0,0 +1,29 @@
+package com.usky.pm.service.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * @description:TODO
+ * @author: fu
+ * @create: 2024-05-30 16:40
+ */
+@Data
+public class PmProjectWorkTimeVo {
+    /**
+     * 项目名
+     */
+    private String projectName;
+
+    /**
+     * 项目工时
+     */
+    private BigDecimal workTime;
+
+    /**
+     * 项目工时占比
+     */
+    private BigDecimal percentage;
+}
+

+ 27 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/service/vo/PmReportReadersVO.java

@@ -0,0 +1,27 @@
+package com.usky.pm.service.vo;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @author fu
+ * @date 2024/8/8
+ */
+@Data
+public class PmReportReadersVO {
+    //已读
+    private List<Long> readAlready;
+    //未读
+    private List<Long> readNotAlready;
+    //已读数量
+    private Integer read;
+    //未读数量
+    private Integer unRead;
+
+    public PmReportReadersVO(List<Long> readAlready, List<Long> readNotAlready, Integer read, Integer unRead) {
+        this.readAlready = readAlready;
+        this.readNotAlready = readNotAlready;
+        this.read = read;
+        this.unRead = unRead;
+    }
+}

+ 65 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/service/vo/PmSubmitCountResponseVO.java

@@ -0,0 +1,65 @@
+package com.usky.pm.service.vo;
+
+import java.time.LocalDate;
+
+/**
+ *
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2025/1/10
+ */
+public class PmSubmitCountResponseVO {
+
+    /**
+     * 按时提交
+     **/
+    private Integer submitOnTime;
+
+    /**
+     * 迟交
+     **/
+    private Integer submitLate;
+
+    /**
+     * 未提交
+     **/
+    private Integer notSubmitted;
+
+    /**
+     * 统计日期
+     **/
+    private LocalDate statisticalDate;
+
+    public Integer getSubmitOnTime() {
+        return submitOnTime;
+    }
+
+    public void setSubmitOnTime(Integer submitOnTime) {
+        this.submitOnTime = submitOnTime;
+    }
+
+    public Integer getSubmitLate() {
+        return submitLate;
+    }
+
+    public void setSubmitLate(Integer submitLate) {
+        this.submitLate = submitLate;
+    }
+
+    public Integer getNotSubmitted() {
+        return notSubmitted;
+    }
+
+    public void setNotSubmitted(Integer notSubmitted) {
+        this.notSubmitted = notSubmitted;
+    }
+
+    public LocalDate getStatisticalDate() {
+        return statisticalDate;
+    }
+
+    public void setStatisticalDate(LocalDate statisticalDate) {
+        this.statisticalDate = statisticalDate;
+    }
+
+}

+ 19 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/service/vo/PmUsersProjectWorkTimeVO.java

@@ -0,0 +1,19 @@
+package com.usky.pm.service.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * @author fu
+ * @date 2024/8/12
+ */
+@Data
+public class PmUsersProjectWorkTimeVO {
+    // 用户
+    private List<String> users;
+    // 工时
+    private List<BigDecimal> workTime;
+
+}

+ 44 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/service/vo/PmWorkHourStatisticRequestVO.java

@@ -0,0 +1,44 @@
+package com.usky.pm.service.vo;
+
+import com.usky.common.core.bean.BaseEntity;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ *
+ * @author fu
+ * @date 2024/9/20
+ */
+@Data
+public class PmWorkHourStatisticRequestVO extends BaseEntity {
+    /**
+     * 用户id集合
+     */
+    private List<Long> userIds;
+
+    /**
+     * 项目id
+     */
+    private Integer projectId;
+
+    /**
+     * 过滤标识 是否过滤0工时(默认1:不过滤;2:过滤)
+     **/
+    private Integer filter;
+
+    /**
+     * 开始时间
+     */
+    private String startDate;
+
+    /**
+     * 结束时间
+     */
+    private String endDate;
+
+    /**
+     * 人员 or 项目 打工仔还是项目(默认1:打工仔;2:项目)
+     */
+    private Integer workerOrProject;
+}

+ 26 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/service/vo/PmWorkReportSlideVO.java

@@ -0,0 +1,26 @@
+package com.usky.pm.service.vo;
+
+import com.usky.pm.domain.PmWorkReport;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ *
+ * @author fu
+ * @date 2024/12/9
+ */
+@Data
+public class PmWorkReportSlideVO {
+
+    /**
+     * 上滑动
+     **/
+    List<PmWorkReport> slideUp;
+
+    /**
+     * 下滑动
+     **/
+    List<PmWorkReport> slideDown;
+
+}

+ 18 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/service/vo/ProjectWorkTimeVO.java

@@ -0,0 +1,18 @@
+package com.usky.pm.service.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * @description:TODO
+ * @author: fu
+ * @create: 2024-06-27 14:15
+ */
+@Data
+public class ProjectWorkTimeVO {
+    private String ProjectOrPerson;
+    private List<BigDecimal> workTime;
+}
+

+ 31 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/service/vo/SendWeChatMessageRequestVO.java

@@ -0,0 +1,31 @@
+package com.usky.pm.service.vo;
+
+import lombok.Data;
+
+@Data
+public class SendWeChatMessageRequestVO {
+    /**
+     * 消息类型
+     */
+    String infoType;
+
+    /**
+     * 消息标题
+     */
+    String infoTitle;
+
+    /**
+     * 消息内容
+     */
+    String infoContent;
+
+    /**
+     * 消息Id
+     */
+    Integer infoId;
+
+    /**
+     * openId
+     */
+    String openId;
+}

+ 18 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/service/vo/UserWorkTimeVO.java

@@ -0,0 +1,18 @@
+package com.usky.pm.service.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * @description:TODO
+ * @author: fu
+ * @create: 2024-06-27 14:15
+ */
+@Data
+public class UserWorkTimeVO {
+    private String ProjectOrPerson;
+    private List<BigDecimal> workTime;
+}
+

+ 31 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/service/vo/WorkHoursStatisticsVO.java

@@ -0,0 +1,31 @@
+package com.usky.pm.service.vo;
+
+import lombok.Data;
+import java.math.BigDecimal;
+
+
+/**
+ * @description:TODO
+ * @author: fu
+ * @create: 2024-06-25 11:32
+ */
+@Data
+public class WorkHoursStatisticsVO {
+
+    /**
+     * 员工昵称
+     */
+    private String nickName;
+
+    /**
+     * 项目名
+     */
+    private String projectName;
+
+    /**
+     * 工时
+     */
+    private BigDecimal workTime;
+
+}
+

+ 55 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/service/vo/WorkTimeExportTwoVO.java

@@ -0,0 +1,55 @@
+package com.usky.pm.service.vo;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.ruoyi.common.core.annotation.Excel;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+/**
+ * @description:TODO
+ * @author: fu
+ * @create: 2024-07-16 11:03
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class WorkTimeExportTwoVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 日期
+     */
+    @Excel(name = "日期")
+    private LocalDate reportDate;
+
+    /**
+     *
+     */
+    @Excel(name = "账号名", align = Excel.Align.LEFT)
+    private String userName;
+
+    /**
+     *
+     */
+    @Excel(name = "员工名", align = Excel.Align.LEFT)
+    private String fullName;
+
+    /**
+     * 工时
+     */
+    @Excel(name = "耗时", align = Excel.Align.LEFT)
+    private BigDecimal workTime;
+
+    /**
+     * 工作内容
+     */
+    @Excel(name = "工作内容", width = 48, align = Excel.Align.LEFT)
+    private String workContent;
+
+}
+

+ 61 - 0
service-pm/service-pm-biz/src/main/java/com/usky/pm/service/vo/WorkTimeExportVO.java

@@ -0,0 +1,61 @@
+package com.usky.pm.service.vo;
+
+import com.ruoyi.common.core.annotation.Excel;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+/**
+ * @description:TODO
+ * @author: fu
+ * @create: 2024-07-16 11:03
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class WorkTimeExportVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 日期
+     */
+    @Excel(name = "日期")
+    private LocalDate reportDate;
+
+    /**
+     *
+     */
+    @Excel(name = "账号名", align = Excel.Align.LEFT)
+    private String userName;
+
+    /**
+     *
+     */
+    @Excel(name = "员工名", align = Excel.Align.LEFT)
+    private String fullName;
+
+    /**
+     * 项目名
+     */
+    @Excel(name = "项目名", align = Excel.Align.LEFT)
+    private String projectName;
+
+    /**
+     * 工时
+     */
+    @Excel(name = "耗时", align = Excel.Align.LEFT)
+    private BigDecimal workTime;
+
+    /**
+     * 工作内容
+     */
+    @Excel(name = "工作内容", width = 48, align = Excel.Align.LEFT)
+    private String workContent;
+
+}
+

+ 25 - 0
service-pm/service-pm-biz/src/main/resources/bootstrap.yml

@@ -0,0 +1,25 @@
+# Tomcat
+server:
+  port: 9900
+
+# Spring
+spring: 
+  application:
+    # 应用名称
+    name: service-pm
+  profiles:
+    # 环境配置
+    active: dev
+  cloud:
+    nacos:
+      discovery:
+        # 服务注册地址
+        server-addr: usky-cloud-nacos:8848
+      config:
+        # 配置中心地址
+        server-addr: usky-cloud-nacos:8848
+        # 配置文件格式
+        file-extension: yml
+        # 共享配置
+        shared-configs:
+          - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}

+ 108 - 0
service-pm/service-pm-biz/src/main/resources/doc/index.adoc

@@ -0,0 +1,108 @@
+= 安防项目
+
+[width="100%",options="header"]
+[stripes=even]
+|====================
+|Version |  Update Time  | Status | Author |  Description
+|v2022-04-21 16:57:08|2022-04-21 16:57:08|auto|@yq|Created by smart-doc
+|====================
+
+
+== &lt;p&gt;参数配置表 前端控制器&lt;/p&gt;
+== &lt;p&gt;部门信息&lt;/p&gt;
+=== 查看部门信息
+*URL:* http:10.23.39.1:8082/sysDept/list
+
+*Type:* POST
+
+*Author:* ya
+
+*Content-Type:* application/json; charset=utf-8
+
+
+
+
+*Body-parameters:*
+
+[width="100%",options="header"]
+[stripes=even]
+|====================
+|Parameter | Type|Description|Required|Since
+|deptId|int64|部门id|false|-
+|parentId|int64|父部门id|false|-
+|ancestors|string|祖级列表|false|-
+|deptName|string|部门名称|false|-
+|orderNum|int32|显示顺序|false|-
+|leader|string|负责人|false|-
+|phone|string|联系电话|false|-
+|email|string|邮箱|false|-
+|status|string|部门状态(0正常 1停用)|false|-
+|delFlag|string|删除标志(0代表存在 2代表删除)|false|-
+|createBy|string|创建者|false|-
+|createTime|string|创建时间|false|-
+|updateBy|string|更新者|false|-
+|updateTime|string|更新时间|false|-
+|bId|int64|建筑id|false|-
+|====================
+
+*Response-fields:*
+
+[width="100%",options="header"]
+[stripes=even]
+|====================
+|Field | Type|Description|Since
+|status|object|No comments found.|-
+|code|string|No comments found.|-
+|msg|string|No comments found.|-
+|data|object|No comments found.|-
+|└─deptId|int64|部门id|-
+|└─parentId|int64|父部门id|-
+|└─ancestors|string|祖级列表|-
+|└─deptName|string|部门名称|-
+|└─orderNum|int32|显示顺序|-
+|└─leader|string|负责人|-
+|└─phone|string|联系电话|-
+|└─email|string|邮箱|-
+|└─status|string|部门状态(0正常 1停用)|-
+|└─delFlag|string|删除标志(0代表存在 2代表删除)|-
+|└─createBy|string|创建者|-
+|└─createTime|string|创建时间|-
+|└─updateBy|string|更新者|-
+|└─updateTime|string|更新时间|-
+|└─bId|int64|建筑id|-
+|exception|string|No comments found.|-
+|====================
+
+*Response-example:*
+----
+{
+	"status": {
+		
+	},
+	"code": "97564",
+	"msg": "wnr5qt",
+	"data": [
+		{
+			"deptId": 540,
+			"parentId": 858,
+			"ancestors": "o5lg60",
+			"deptName": "文.沈",
+			"orderNum": 260,
+			"leader": "ufz93p",
+			"phone": "17852835049",
+			"email": "智渊.徐@yahoo.com",
+			"status": "nu6cnp",
+			"delFlag": "72oiji",
+			"createBy": "5fxr6j",
+			"createTime": "2022-04-21 16:57:10",
+			"updateBy": "4kcs4e",
+			"updateTime": "2022-04-21 16:57:10",
+			"bId": 977
+		}
+	],
+	"exception": "53u6bg"
+}
+----
+
+== &lt;p&gt;用户信息表 前端控制器&lt;/p&gt;
+

+ 94 - 0
service-pm/service-pm-biz/src/main/resources/logback.xml

@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration scan="true" scanPeriod="60 seconds" debug="false">
+    <!-- 日志存放路径 -->
+    <property name="log.path" value="/var/log/uskycloud/service-pm" />
+    <!-- 日志输出格式 -->
+    <property name="log.pattern" value="%d{MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{26}:%line: %msg%n" />
+    <!--    	<property name="log.pattern" value="%gray(%d{MM-dd HH:mm:ss.SSS}) %highlight(%-5level) &#45;&#45; [%gray(%thread)] %cyan(%logger{26}:%line): %msg%n" />-->
+
+
+    <property name="SQL_PACKAGE" value="com.usky.pm.mapper"/>
+
+    <!-- 控制台输出 -->
+    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+    </appender>
+
+    <appender name="file_sql" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${log.path}/sql.log</file>
+        <!-- 循环政策:基于时间创建日志文件 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+            <fileNamePattern>${log.path}/sql.%d{yyyy-MM-dd}.log</fileNamePattern>
+            <!-- 日志最大的历史 60天 -->
+            <maxHistory>3</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+    </appender>
+
+    <!-- 系统日志输出 -->
+    <appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${log.path}/info.log</file>
+        <!-- 循环政策:基于时间创建日志文件 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+            <fileNamePattern>${log.path}/info.%d{yyyy-MM-dd}.log</fileNamePattern>
+            <!-- 日志最大的历史 60天 -->
+            <maxHistory>3</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 过滤的级别 -->
+            <level>INFO</level>
+            <!-- 匹配时的操作:接收(记录) -->
+            <onMatch>ACCEPT</onMatch>
+            <!-- 不匹配时的操作:拒绝(不记录) -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+    <appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${log.path}/error.log</file>
+        <!-- 循环政策:基于时间创建日志文件 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+            <fileNamePattern>${log.path}/error.%d{yyyy-MM-dd}.log</fileNamePattern>
+            <!-- 日志最大的历史 60天 -->
+            <maxHistory>60</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 过滤的级别 -->
+            <level>ERROR</level>
+            <!-- 匹配时的操作:接收(记录) -->
+            <onMatch>ACCEPT</onMatch>
+            <!-- 不匹配时的操作:拒绝(不记录) -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+    <!-- 系统模块日志级别控制  -->
+    <!--	<logger name="com.usky" level="info" />-->
+    <!-- Spring日志级别控制  -->
+    <!--	<logger name="org.springframework" level="warn" />-->
+
+    <logger name="${SQL_PACKAGE}" additivity="false" level="debug">
+        <appender-ref ref="console"/>
+        <appender-ref ref="file_sql"/>
+    </logger>
+
+    <!--系统操作日志-->
+    <root level="info">
+        <appender-ref ref="file_info" />
+        <appender-ref ref="file_error" />
+        <appender-ref ref="console" />
+    </root>
+</configuration>

+ 27 - 0
service-pm/service-pm-biz/src/main/resources/mapper/pm/PmProjectMapper.xml

@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.usky.pm.mapper.PmProjectMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.usky.pm.domain.PmProject">
+        <id column="id" property="id" />
+        <result column="project_name" property="projectName" />
+        <result column="start_time" property="startTime" />
+        <result column="end_time" property="endTime" />
+        <result column="project_describe" property="projectDescribe" />
+        <result column="project_type" property="projectType" />
+        <result column="project_status" property="projectStatus" />
+        <result column="project_head" property="projectHead" />
+        <result column="project_member" property="projectMember" />
+        <result column="project_workload" property="projectWorkload" />
+        <result column="create_by" property="createBy" />
+        <result column="create_time" property="createTime" />
+        <result column="update_by" property="updateBy" />
+        <result column="update_time" property="updateTime" />
+        <result column="dept_id" property="deptId" />
+        <result column="tenant_id" property="tenantId" />
+        <result column="del_flag" property="delFlag" />
+        <result column="visible_range" property="visibleRange" />
+    </resultMap>
+
+</mapper>

+ 20 - 0
service-pm/service-pm-biz/src/main/resources/mapper/pm/PmReceiveMapper.xml

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.usky.pm.mapper.PmReceiveMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.usky.pm.domain.PmReceive">
+        <id column="id" property="id" />
+        <result column="receiver_id" property="receiverId" />
+        <result column="receiver_name" property="receiverName" />
+        <result column="report_id" property="reportId" />
+        <result column="tenant_id" property="tenantId" />
+        <result column="dept_id" property="deptId" />
+        <result column="create_by" property="createBy" />
+        <result column="create_time" property="createTime" />
+        <result column="update_by" property="updateBy" />
+        <result column="update_time" property="updateTime" />
+        <result column="read_flag" property="readFlag" />
+    </resultMap>
+
+</mapper>

+ 22 - 0
service-pm/service-pm-biz/src/main/resources/mapper/pm/PmTimeConfMapper.xml

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.usky.pm.mapper.PmTimeConfMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.usky.pm.domain.PmTimeConf">
+        <id column="id" property="id" />
+        <result column="start_time" property="startTime" />
+        <result column="on_time" property="onTime" />
+        <result column="end_time" property="endTime" />
+        <result column="create_by" property="createBy" />
+        <result column="create_time" property="createTime" />
+        <result column="update_by" property="updateBy" />
+        <result column="update_time" property="updateTime" />
+        <result column="dept_id" property="deptId" />
+        <result column="tenant_id" property="tenantId" />
+        <result column="conf_name" property="confName" />
+        <result column="conf_type" property="confType" />
+        <result column="notifier" property="notifier" />
+    </resultMap>
+
+</mapper>

+ 57 - 0
service-pm/service-pm-biz/src/main/resources/mapper/pm/PmWorkContentMapper.xml

@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.usky.pm.mapper.PmWorkContentMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.usky.pm.domain.PmWorkContent">
+        <id column="id" property="id"/>
+        <result column="report_id" property="reportId"/>
+        <result column="project_id" property="projectId"/>
+        <result column="project_name" property="projectName"/>
+        <result column="submitter_id" property="submitterId"/>
+        <result column="work_content" property="workContent"/>
+        <result column="work_time" property="workTime"/>
+        <result column="create_by" property="createBy"/>
+        <result column="create_time" property="createTime"/>
+        <result column="update_by" property="updateBy"/>
+        <result column="update_time" property="updateTime"/>
+        <result column="dept_id" property="deptId"/>
+        <result column="tenant_id" property="tenantId"/>
+    </resultMap>
+    <select id="workTimeCount" resultType="com.usky.pm.service.vo.PmProjectWorkTimeVo">
+        SELECT pc.project_name, SUM(pc.work_time) as workTime
+        FROM pm_work_content pc
+                 JOIN pm_work_report pr ON pc.report_id = pr.id
+        WHERE pr.submitter_id = #{userId}
+          AND pr.report_date BETWEEN #{startTime} AND #{endTime}
+          AND pr.tenant_id = #{tenantId}
+        GROUP BY pc.project_name
+    </select>
+
+    <select id="workHourStatisticExport" resultType="com.usky.pm.service.vo.WorkTimeExportVO">
+        SELECT pr.report_date AS reportDate, pr.create_by AS userName, pc.project_name AS projectName, pc.work_time AS
+        workTime, pc.work_content AS workContent, u.nick_name AS fullName
+        FROM pm_work_content pc
+        JOIN sys_user u ON pc.submitter_id = u.user_id
+        JOIN pm_work_report pr ON pc.report_id = pr.id
+        <if test="projectId != null and projectId != 0">
+            AND pr.id = #{projectId}
+        </if>
+        <if test="userIds != null">
+            AND pr.submitter_id IN
+            <foreach collection="userIds" item="userId" open="(" separator="," close=")">
+                #{userId}
+            </foreach>
+        </if>
+        <if test="startDate != null and startDate != ''"><!-- 开始时间检索 -->
+            and date_format(pr.report_date,'%y%m%d') >= date_format(#{startDate},'%y%m%d')
+        </if>
+        <if test="endDate != null and endDate != ''"><!-- 结束时间检索 -->
+            and date_format(pr.report_date,'%y%m%d') &lt;= date_format(#{endDate},'%y%m%d')
+        </if>
+        <!-- 数据范围过滤 -->
+        ${params.dataScope}
+        ORDER BY pr.report_date DESC
+    </select>
+
+</mapper>

+ 29 - 0
service-pm/service-pm-biz/src/main/resources/mapper/pm/PmWorkReportMapper.xml

@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.usky.pm.mapper.PmWorkReportMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.usky.pm.domain.PmWorkReport">
+        <id column="id" property="id" />
+        <result column="submitter_id" property="submitterId" />
+        <result column="report_date" property="reportDate" />
+        <result column="submit_date" property="submitDate" />
+        <result column="total_hours" property="totalHours" />
+        <result column="cc_to" property="ccTo" />
+        <result column="coordinate_work" property="coordinateWork" />
+        <result column="tomorrow_plan" property="tomorrowPlan" />
+        <result column="create_by" property="createBy" />
+        <result column="create_time" property="createTime" />
+        <result column="update_by" property="updateBy" />
+        <result column="update_time" property="updateTime" />
+        <result column="dept_id" property="deptId" />
+        <result column="tenant_id" property="tenantId" />
+        <result column="send_ding_talk" property="sendDingTalk" />
+        <result column="timing_time" property="timingTime" />
+        <result column="report_status" property="reportStatus" />
+        <result column="is_regularly_send" property="isRegularlySend" />
+        <result column="report_file" property="reportFile" />
+        <result column="report_image" property="reportImage" />
+    </resultMap>
+
+</mapper>

+ 15 - 0
service-pm/service-pm-biz/src/main/resources/smart-doc.json

@@ -0,0 +1,15 @@
+{
+  "outPath":"./src/main/resources/doc",
+  "serverUrl": "http:10.23.39.1:9887/",
+  "isStrict": false,
+  "coverOld": true,
+  "allInOne": true,
+  "packageFilters": "com.usky.pm.controller.web",
+  "requestExample":"false",
+  "responseExample":"true",
+  "projectName": "pm项目",
+  "appKey": "20211216921084883495813120",
+  "appToken":"967031b0cc6f474aaf73616cbf2b25c2",
+  "secret": "N@Pd,KXAHki*BW3=zK.XPNykf!=CM79J",
+  "openUrl": "http://101.133.214.75:7700/api"
+}