Commit 09cf3017e19fcf8dd17ee0993f2cc989f27a9fd9

Authored by YunaiV
1 parent 8e43004c

feat(oa): add office automation

Showing 501 changed files with 53138 additions and 17 deletions

Too many changes.

To preserve performance only 100 of 501 files are displayed.

yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java
... ... @@ -10,6 +10,8 @@ import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
10 10 import lombok.AllArgsConstructor;
11 11 import lombok.Getter;
12 12  
  13 +import java.math.BigDecimal;
  14 +import java.math.RoundingMode;
13 15 import java.sql.Timestamp;
14 16 import java.time.*;
15 17 import java.time.format.DateTimeFormatter;
... ... @@ -33,6 +35,54 @@ import static cn.hutool.core.date.DatePattern.*;
33 35 public class LocalDateTimeUtils {
34 36  
35 37 /**
  38 + * 按经过的时长计算天数,以 24 小时为一天,按指定精度四舍五入
  39 + *
  40 + * @param beginTime 开始时间
  41 + * @param endTime 结束时间
  42 + * @param scale 保留的小数位数
  43 + * @return 按毫秒精度计算的天数,时间缺失时返回 null
  44 + */
  45 + public static BigDecimal getDaysBetween(LocalDateTime beginTime, LocalDateTime endTime, int scale) {
  46 + if (beginTime == null || endTime == null) {
  47 + return null;
  48 + }
  49 + return BigDecimal.valueOf(Duration.between(beginTime, endTime).toMillis())
  50 + .divide(BigDecimal.valueOf(Duration.ofDays(1).toMillis()), scale, RoundingMode.HALF_UP);
  51 + }
  52 +
  53 + /**
  54 + * 按经过的时长计算天数,以 24 小时为一天,结果向上取整
  55 + *
  56 + * 与 {@link #getDaysBetweenInclusive(LocalDateTime, LocalDateTime)} 不同,本方法不按自然日期计数。
  57 + * 例如跨午夜的 2 小时为 1 天,恰好 24 小时为 1 天,相同时刻为 0 天。
  58 + *
  59 + * @param beginTime 开始时间
  60 + * @param endTime 结束时间
  61 + * @return 按毫秒精度计算的天数,时间缺失时返回 null;反向区间按相同规则向上取整
  62 + */
  63 + public static Integer getDaysBetweenCeiling(LocalDateTime beginTime, LocalDateTime endTime) {
  64 + if (beginTime == null || endTime == null) {
  65 + return null;
  66 + }
  67 + return BigDecimal.valueOf(Duration.between(beginTime, endTime).toMillis())
  68 + .divide(BigDecimal.valueOf(Duration.ofDays(1).toMillis()), 0, RoundingMode.CEILING).intValueExact();
  69 + }
  70 +
  71 + /**
  72 + * 计算包含首尾日期的自然日数,不足一天按对应自然日期计算
  73 + *
  74 + * @param beginTime 开始时间
  75 + * @param endTime 结束时间
  76 + * @return 自然日数,时间缺失时返回 null;结束日期早于开始日期时返回非正数
  77 + */
  78 + public static Integer getDaysBetweenInclusive(LocalDateTime beginTime, LocalDateTime endTime) {
  79 + if (beginTime == null || endTime == null) {
  80 + return null;
  81 + }
  82 + return Math.toIntExact(ChronoUnit.DAYS.between(beginTime.toLocalDate(), endTime.toLocalDate()) + 1);
  83 + }
  84 +
  85 + /**
36 86 * 空的 LocalDateTime 对象,主要用于 DB 唯一索引的默认值
37 87 */
38 88 public static LocalDateTime EMPTY = buildTime(1970, 1, 1);
... ... @@ -544,6 +594,16 @@ public class LocalDateTimeUtils {
544 594 }
545 595  
546 596 /**
  597 + * 获得指定时间所在日期的结束时间
  598 + *
  599 + * @param time 时间
  600 + * @return 当日结束时间;时间为空时返回 {@code null}
  601 + */
  602 + public static LocalDateTime getDayEndTime(LocalDateTime time) {
  603 + return time == null ? null : getDayEndTime(time.toLocalDate());
  604 + }
  605 +
  606 + /**
547 607 * 获得自然日期范围对应的日期时间闭区间
548 608 *
549 609 * @param beginDate 开始日期
... ...
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/object/PageUtils.java
... ... @@ -28,14 +28,14 @@ public class PageUtils {
28 28 }
29 29  
30 30 /**
31   - * 对内存列表进行分页
  31 + * 对内存列表分页,不改变列表顺序
32 32 *
33   - * @param list 数据列表
34   - * @param pageParam 分页参数
  33 + * @param pageParam 分页参数,支持 {@link PageParam#PAGE_SIZE_NONE} 返回全部数据
  34 + * @param list 已排序的完整列表
35 35 * @param <T> 数据类型
36   - * @return 分页结果
  36 + * @return 分页结果,超出末页时保留总数并返回空列表
37 37 */
38   - public static <T> PageResult<T> buildPageResult(List<T> list, PageParam pageParam) {
  38 + public static <T> PageResult<T> buildPageResult(PageParam pageParam, List<T> list) {
39 39 if (CollUtil.isEmpty(list)) {
40 40 return PageResult.empty();
41 41 }
... ... @@ -43,8 +43,9 @@ public class PageUtils {
43 43 if (PageParam.PAGE_SIZE_NONE.equals(pageParam.getPageSize())) {
44 44 return new PageResult<>(list, total);
45 45 }
46   - int fromIndex = Math.toIntExact(Math.min(getStart(pageParam), total));
47   - int toIndex = Math.toIntExact(Math.min((long) fromIndex + pageParam.getPageSize(), total));
  46 + long offset = (long) (pageParam.getPageNo() - 1) * pageParam.getPageSize();
  47 + int fromIndex = (int) Math.min(offset, total);
  48 + int toIndex = (int) Math.min((long) fromIndex + pageParam.getPageSize(), total);
48 49 return new PageResult<>(list.subList(fromIndex, toIndex), total);
49 50 }
50 51  
... ...
yudao-framework/yudao-common/src/test/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtilsTest.java
... ... @@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil;
4 4 import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.TimeRange;
5 5 import org.junit.jupiter.api.Test;
6 6  
  7 +import java.math.BigDecimal;
7 8 import java.time.LocalDate;
8 9 import java.time.LocalDateTime;
9 10 import java.time.LocalTime;
... ... @@ -26,6 +27,56 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
26 27 public class LocalDateTimeUtilsTest {
27 28  
28 29 @Test
  30 + public void testGetDaysBetween() {
  31 + // 准备参数
  32 + LocalDateTime beginTime = LocalDateTime.of(2026, 12, 31, 23, 0);
  33 +
  34 + // 调用,并断言:按时长四舍五入,跨日不改变计算规则
  35 + assertEquals(new BigDecimal("0.0"), LocalDateTimeUtils.getDaysBetween(beginTime, beginTime, 1));
  36 + assertEquals(new BigDecimal("0.5"), LocalDateTimeUtils.getDaysBetween(beginTime, beginTime.plusHours(12), 1));
  37 + assertEquals(new BigDecimal("0.1"), LocalDateTimeUtils.getDaysBetween(beginTime, beginTime.plusMinutes(72), 1));
  38 + assertEquals(new BigDecimal("0.0"), LocalDateTimeUtils.getDaysBetween(beginTime, beginTime.plusMinutes(71), 1));
  39 + assertEquals(new BigDecimal("1.1"), LocalDateTimeUtils.getDaysBetween(beginTime, beginTime.plusHours(26), 1));
  40 + assertEquals(new BigDecimal("-0.5"), LocalDateTimeUtils.getDaysBetween(beginTime, beginTime.minusHours(12), 1));
  41 + assertEquals(new BigDecimal("0.50"), LocalDateTimeUtils.getDaysBetween(beginTime, beginTime.plusHours(12), 2));
  42 + assertNull(LocalDateTimeUtils.getDaysBetween(null, beginTime, 1));
  43 + assertNull(LocalDateTimeUtils.getDaysBetween(beginTime, null, 1));
  44 + }
  45 +
  46 + @Test
  47 + public void testGetDaysBetweenCeiling() {
  48 + // 准备参数
  49 + LocalDateTime beginTime = LocalDateTime.of(2026, 12, 31, 23, 0);
  50 +
  51 + // 调用,并断言:按时长取整,不因跨午夜额外增加一天
  52 + assertEquals(0, LocalDateTimeUtils.getDaysBetweenCeiling(beginTime, beginTime));
  53 + assertEquals(1, LocalDateTimeUtils.getDaysBetweenCeiling(beginTime, beginTime.plusHours(2)));
  54 + assertEquals(1, LocalDateTimeUtils.getDaysBetweenCeiling(beginTime, beginTime.plusDays(1)));
  55 + assertEquals(2, LocalDateTimeUtils.getDaysBetweenCeiling(beginTime, beginTime.plusDays(1).plusNanos(1_000_000)));
  56 + assertEquals(2, LocalDateTimeUtils.getDaysBetweenCeiling(beginTime, beginTime.plusHours(26)));
  57 + assertEquals(0, LocalDateTimeUtils.getDaysBetweenCeiling(beginTime, beginTime.minusHours(2)));
  58 + assertEquals(-1, LocalDateTimeUtils.getDaysBetweenCeiling(beginTime, beginTime.minusHours(26)));
  59 + assertNull(LocalDateTimeUtils.getDaysBetweenCeiling(null, beginTime));
  60 + assertNull(LocalDateTimeUtils.getDaysBetweenCeiling(beginTime, null));
  61 + }
  62 +
  63 + @Test
  64 + public void testGetDaysBetweenInclusive() {
  65 + // 准备参数
  66 + LocalDateTime beginTime = LocalDateTime.of(2026, 12, 31, 23, 0);
  67 + LocalDateTime endTime = LocalDateTime.of(2027, 1, 2, 1, 0);
  68 +
  69 + // 调用,并断言:按自然日计算,包含首尾日期
  70 + assertEquals(3, LocalDateTimeUtils.getDaysBetweenInclusive(beginTime, endTime));
  71 + assertEquals(1, LocalDateTimeUtils.getDaysBetweenInclusive(beginTime, beginTime.minusHours(1)));
  72 + assertEquals(-1, LocalDateTimeUtils.getDaysBetweenInclusive(endTime, beginTime));
  73 + assertNull(LocalDateTimeUtils.getDaysBetweenInclusive(null, endTime));
  74 + assertNull(LocalDateTimeUtils.getDaysBetweenInclusive(beginTime, null));
  75 + assertEquals(3, LocalDateTimeUtils.getDaysBetweenInclusive(
  76 + LocalDateTime.of(2024, 2, 28, 0, 0), LocalDateTime.of(2024, 3, 1, 0, 0)));
  77 + }
  78 +
  79 + @Test
29 80 public void testDateCompare() {
30 81 // 准备参数
31 82 LocalDate firstDate = LocalDate.of(2026, 7, 23);
... ... @@ -99,6 +150,10 @@ public class LocalDateTimeUtilsTest {
99 150 assertNull(LocalDateTimeUtils.getDayBeginTime((LocalDateTime) null));
100 151 assertEquals(LocalDateTime.of(date, LocalTime.MAX),
101 152 LocalDateTimeUtils.getDayEndTime(date));
  153 + assertEquals(LocalDateTime.of(date, LocalTime.MAX),
  154 + LocalDateTimeUtils.getDayEndTime(time));
  155 + assertNull(LocalDateTimeUtils.getDayEndTime((LocalDateTime) null));
  156 + assertNull(LocalDateTimeUtils.getDayEndTime((LocalDate) null));
102 157 }
103 158  
104 159 @Test
... ...
yudao-framework/yudao-common/src/test/java/cn/iocoder/yudao/framework/common/util/object/PageUtilsTest.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.common.util.object;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import org.junit.jupiter.api.Test;
  6 +
  7 +import java.util.Arrays;
  8 +import java.util.Collections;
  9 +import java.util.List;
  10 +
  11 +import static org.junit.jupiter.api.Assertions.assertEquals;
  12 +import static org.junit.jupiter.api.Assertions.assertTrue;
  13 +
  14 +/**
  15 + * {@link PageUtils} 的单元测试
  16 + */
  17 +public class PageUtilsTest {
  18 +
  19 + @Test
  20 + public void testBuildPageResult() {
  21 + // 准备参数
  22 + PageParam pageParam = new PageParam();
  23 + pageParam.setPageSize(2);
  24 + List<Integer> list = Arrays.asList(1, 2, 3);
  25 +
  26 + // 调用并断言首页和末页
  27 + PageResult<Integer> result = PageUtils.buildPageResult(pageParam, list);
  28 + assertEquals(3L, result.getTotal());
  29 + assertEquals(Arrays.asList(1, 2), result.getList());
  30 + pageParam.setPageNo(2);
  31 + result = PageUtils.buildPageResult(pageParam, list);
  32 + assertEquals(3L, result.getTotal());
  33 + assertEquals(Collections.singletonList(3), result.getList());
  34 + assertEquals(Arrays.asList(1, 2, 3), list);
  35 + }
  36 +
  37 + @Test
  38 + public void testBuildPageResult_empty() {
  39 + PageParam pageParam = new PageParam();
  40 + assertEquals(0L, PageUtils.buildPageResult(pageParam, Collections.emptyList()).getTotal());
  41 + assertTrue(PageUtils.buildPageResult(pageParam, null).getList().isEmpty());
  42 + }
  43 +
  44 + @Test
  45 + public void testBuildPageResult_pastLastPage() {
  46 + PageParam pageParam = new PageParam();
  47 + pageParam.setPageNo(Integer.MAX_VALUE);
  48 + pageParam.setPageSize(200);
  49 +
  50 + PageResult<Integer> result = PageUtils.buildPageResult(pageParam, Arrays.asList(1, 2, 3));
  51 +
  52 + assertEquals(3L, result.getTotal());
  53 + assertTrue(result.getList().isEmpty());
  54 + }
  55 +
  56 + @Test
  57 + public void testBuildPageResult_noPagination() {
  58 + PageParam pageParam = new PageParam();
  59 + pageParam.setPageNo(2);
  60 + pageParam.setPageSize(PageParam.PAGE_SIZE_NONE);
  61 +
  62 + PageResult<Integer> result = PageUtils.buildPageResult(pageParam, Arrays.asList(1, 2, 3));
  63 +
  64 + assertEquals(3L, result.getTotal());
  65 + assertEquals(Arrays.asList(1, 2, 3), result.getList());
  66 + }
  67 +
  68 +}
... ...
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java
... ... @@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam;
5 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 6 import cn.iocoder.yudao.framework.common.pojo.SortablePageParam;
7 7 import cn.iocoder.yudao.framework.common.pojo.SortingField;
  8 +import cn.iocoder.yudao.framework.common.util.object.PageUtils;
8 9 import cn.iocoder.yudao.framework.mybatis.core.util.JdbcUtils;
9 10 import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
10 11 import com.baomidou.mybatisplus.annotation.DbType;
... ... @@ -44,7 +45,7 @@ public interface BaseMapperX&lt;T&gt; extends MPJBaseMapper&lt;T&gt; {
44 45 if (PageParam.PAGE_SIZE_NONE.equals(pageParam.getPageSize())) {
45 46 MyBatisUtils.addOrder(queryWrapper, sortingFields);
46 47 List<T> list = selectList(queryWrapper);
47   - return new PageResult<>(list, (long) list.size());
  48 + return PageUtils.buildPageResult(pageParam, list);
48 49 }
49 50  
50 51 // MyBatis Plus 查询
... ... @@ -58,7 +59,7 @@ public interface BaseMapperX&lt;T&gt; extends MPJBaseMapper&lt;T&gt; {
58 59 // 特殊:不分页,直接查询全部
59 60 if (PageParam.PAGE_SIZE_NONE.equals(pageParam.getPageSize())) {
60 61 List<D> list = selectJoinList(clazz, lambdaWrapper);
61   - return new PageResult<>(list, (long) list.size());
  62 + return PageUtils.buildPageResult(pageParam, list);
62 63 }
63 64  
64 65 // MyBatis Plus Join 查询
... ... @@ -81,7 +82,7 @@ public interface BaseMapperX&lt;T&gt; extends MPJBaseMapper&lt;T&gt; {
81 82 // 特殊:不分页,直接查询全部
82 83 if (PageParam.PAGE_SIZE_NONE.equals(pageParam.getPageSize())) {
83 84 List<D> list = selectJoinList(clazz, lambdaWrapper);
84   - return new PageResult<>(list, (long) list.size());
  85 + return PageUtils.buildPageResult(pageParam, list);
85 86 }
86 87  
87 88 // MyBatis Plus Join 查询
... ...
yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/statistics/HrmAttendanceStatisticsServiceImpl.java
... ... @@ -135,7 +135,7 @@ public class HrmAttendanceStatisticsServiceImpl implements HrmAttendanceStatisti
135 135 buildMonthSummaryList(reqVO.getYear(), reqVO.getMonth(), employeeList);
136 136 List<HrmAttendanceMonthRecordRespVO> filteredMonthSummaryList = filterList(monthSummaryList,
137 137 record -> Objects.equals(record.getFullAttendance(), reqVO.getFullAttendance()));
138   - return PageUtils.buildPageResult(filteredMonthSummaryList, reqVO);
  138 + return PageUtils.buildPageResult(reqVO, filteredMonthSummaryList);
139 139 }
140 140  
141 141 @Override
... ...
yudao-module-oa/pom.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project xmlns="http://maven.apache.org/POM/4.0.0"
  3 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  5 + <parent>
  6 + <groupId>cn.iocoder.boot</groupId>
  7 + <artifactId>yudao</artifactId>
  8 + <version>${revision}</version>
  9 + </parent>
  10 + <modelVersion>4.0.0</modelVersion>
  11 + <artifactId>yudao-module-oa</artifactId>
  12 + <packaging>jar</packaging>
  13 +
  14 + <name>${project.artifactId}</name>
  15 + <description>
  16 + oa 包下,办公自动化(Office Automation),提供协同办公、行政管理和业务审批功能。
  17 + 例如说:日程计划、企业云盘、请假报销、会议室、车辆、印章、公文等等
  18 + </description>
  19 +
  20 + <dependencies>
  21 + <dependency>
  22 + <groupId>cn.iocoder.boot</groupId>
  23 + <artifactId>yudao-module-system</artifactId>
  24 + <version>${revision}</version>
  25 + </dependency>
  26 + <dependency>
  27 + <groupId>cn.iocoder.boot</groupId>
  28 + <artifactId>yudao-module-infra</artifactId>
  29 + <version>${revision}</version>
  30 + </dependency>
  31 + <dependency>
  32 + <groupId>cn.iocoder.boot</groupId>
  33 + <artifactId>yudao-module-bpm</artifactId>
  34 + <version>${revision}</version>
  35 + </dependency>
  36 +
  37 + <!-- Web 相关 -->
  38 + <dependency>
  39 + <groupId>cn.iocoder.boot</groupId>
  40 + <artifactId>yudao-spring-boot-starter-web</artifactId>
  41 + </dependency>
  42 + <dependency>
  43 + <groupId>cn.iocoder.boot</groupId>
  44 + <artifactId>yudao-spring-boot-starter-security</artifactId>
  45 + </dependency>
  46 +
  47 + <!-- DB 相关 -->
  48 + <dependency>
  49 + <groupId>cn.iocoder.boot</groupId>
  50 + <artifactId>yudao-spring-boot-starter-mybatis</artifactId>
  51 + </dependency>
  52 +
  53 + <!-- Job 定时任务相关 -->
  54 + <dependency>
  55 + <groupId>cn.iocoder.boot</groupId>
  56 + <artifactId>yudao-spring-boot-starter-job</artifactId>
  57 + </dependency>
  58 +
  59 + <!-- 工具类相关 -->
  60 + <dependency>
  61 + <groupId>org.springframework.boot</groupId>
  62 + <artifactId>spring-boot-starter-mail</artifactId>
  63 + </dependency>
  64 + <dependency>
  65 + <groupId>org.jsoup</groupId>
  66 + <artifactId>jsoup</artifactId>
  67 + </dependency>
  68 + <dependency>
  69 + <groupId>com.belerweb</groupId>
  70 + <artifactId>pinyin4j</artifactId>
  71 + </dependency>
  72 +
  73 + <!-- Test 测试相关 -->
  74 + <dependency>
  75 + <groupId>cn.iocoder.boot</groupId>
  76 + <artifactId>yudao-spring-boot-starter-test</artifactId>
  77 + <scope>test</scope>
  78 + </dependency>
  79 + </dependencies>
  80 +
  81 +</project>
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/announcement/OaAnnouncementController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.announcement;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
  5 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  6 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  7 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  8 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  9 +import cn.iocoder.yudao.module.oa.controller.admin.announcement.vo.OaAnnouncementPageReqVO;
  10 +import cn.iocoder.yudao.module.oa.controller.admin.announcement.vo.OaAnnouncementRespVO;
  11 +import cn.iocoder.yudao.module.oa.controller.admin.announcement.vo.OaAnnouncementSaveReqVO;
  12 +import cn.iocoder.yudao.module.oa.dal.dataobject.announcement.OaAnnouncementDO;
  13 +import cn.iocoder.yudao.module.oa.dal.dataobject.announcement.OaAnnouncementReceiverDO;
  14 +import cn.iocoder.yudao.module.oa.service.announcement.OaAnnouncementService;
  15 +import cn.iocoder.yudao.module.system.api.dept.DeptApi;
  16 +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
  17 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  18 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  19 +import io.swagger.v3.oas.annotations.Operation;
  20 +import io.swagger.v3.oas.annotations.Parameter;
  21 +import io.swagger.v3.oas.annotations.tags.Tag;
  22 +import jakarta.annotation.Resource;
  23 +import jakarta.validation.Valid;
  24 +import org.springframework.security.access.prepost.PreAuthorize;
  25 +import org.springframework.validation.annotation.Validated;
  26 +import org.springframework.web.bind.annotation.DeleteMapping;
  27 +import org.springframework.web.bind.annotation.GetMapping;
  28 +import org.springframework.web.bind.annotation.PostMapping;
  29 +import org.springframework.web.bind.annotation.PutMapping;
  30 +import org.springframework.web.bind.annotation.RequestBody;
  31 +import org.springframework.web.bind.annotation.RequestMapping;
  32 +import org.springframework.web.bind.annotation.RequestParam;
  33 +import org.springframework.web.bind.annotation.RestController;
  34 +
  35 +import java.util.ArrayList;
  36 +import java.util.Collection;
  37 +import java.util.Collections;
  38 +import java.util.List;
  39 +import java.util.Map;
  40 +import java.util.Set;
  41 +
  42 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  43 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  44 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  45 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  46 +
  47 +@Tag(name = "管理后台 - OA 公告")
  48 +@RestController
  49 +@RequestMapping("/oa/announcement")
  50 +@Validated
  51 +public class OaAnnouncementController {
  52 +
  53 + @Resource
  54 + private OaAnnouncementService announcementService;
  55 +
  56 + @Resource
  57 + private AdminUserApi adminUserApi;
  58 + @Resource
  59 + private DeptApi deptApi;
  60 +
  61 + // ==================== 我发布的 ====================
  62 +
  63 + @PostMapping("/create")
  64 + @Operation(summary = "创建公告")
  65 + @PreAuthorize("@ss.hasPermission('oa:announcement:create')")
  66 + public CommonResult<Long> createAnnouncement(@Valid @RequestBody OaAnnouncementSaveReqVO createReqVO) {
  67 + return success(announcementService.createAnnouncement(createReqVO, getLoginUserId()));
  68 + }
  69 +
  70 + @PutMapping("/update")
  71 + @Operation(summary = "更新公告")
  72 + @PreAuthorize("@ss.hasPermission('oa:announcement:update')")
  73 + public CommonResult<Boolean> updateAnnouncement(@Valid @RequestBody OaAnnouncementSaveReqVO updateReqVO) {
  74 + announcementService.updateAnnouncement(updateReqVO, getLoginUserId());
  75 + return success(true);
  76 + }
  77 +
  78 + @DeleteMapping("/delete")
  79 + @Operation(summary = "删除发布的公告")
  80 + @Parameter(name = "id", description = "公告编号", required = true, example = "1024")
  81 + @PreAuthorize("@ss.hasPermission('oa:announcement:delete')")
  82 + public CommonResult<Boolean> deleteAnnouncement(@RequestParam("id") Long id) {
  83 + announcementService.deleteAnnouncement(id, getLoginUserId());
  84 + return success(true);
  85 + }
  86 +
  87 + @GetMapping("/published-page")
  88 + @Operation(summary = "获得我发布的公告分页")
  89 + @PreAuthorize("@ss.hasPermission('oa:announcement:query')")
  90 + public CommonResult<PageResult<OaAnnouncementRespVO>> getPublishedAnnouncementPage(
  91 + @Valid OaAnnouncementPageReqVO pageReqVO) {
  92 + Long userId = getLoginUserId();
  93 + PageResult<OaAnnouncementDO> pageResult = announcementService
  94 + .getPublishedAnnouncementPage(pageReqVO, userId);
  95 + return success(new PageResult<>(buildAnnouncementRespVOList(pageResult.getList(), userId),
  96 + pageResult.getTotal()));
  97 + }
  98 +
  99 + // ==================== 我收到的 ====================
  100 +
  101 + @DeleteMapping("/delete-received")
  102 + @Operation(summary = "删除接收的公告")
  103 + @Parameter(name = "id", description = "公告编号", required = true, example = "1024")
  104 + public CommonResult<Boolean> deleteReceivedAnnouncement(@RequestParam("id") Long id) {
  105 + announcementService.deleteReceivedAnnouncement(id, getLoginUserId());
  106 + return success(true);
  107 + }
  108 +
  109 + @GetMapping("/received-page")
  110 + @Operation(summary = "获得接收的公告分页")
  111 + @PreAuthorize("@ss.hasPermission('oa:announcement:query')")
  112 + public CommonResult<PageResult<OaAnnouncementRespVO>> getReceivedAnnouncementPage(
  113 + @Valid OaAnnouncementPageReqVO pageReqVO) {
  114 + Long userId = getLoginUserId();
  115 + PageResult<OaAnnouncementDO> pageResult = announcementService
  116 + .getReceivedAnnouncementPage(pageReqVO, userId);
  117 + return success(new PageResult<>(buildAnnouncementRespVOList(pageResult.getList(), userId),
  118 + pageResult.getTotal()));
  119 + }
  120 +
  121 + @PutMapping("/update-read-status")
  122 + @Operation(summary = "标记公告为已读")
  123 + @Parameter(name = "id", description = "公告编号", required = true, example = "1024")
  124 + public CommonResult<Boolean> updateAnnouncementReadStatus(@RequestParam("id") Long id) {
  125 + announcementService.updateAnnouncementReadStatus(id, getLoginUserId());
  126 + return success(true);
  127 + }
  128 +
  129 + @PostMapping("/forward")
  130 + @Operation(summary = "转发公告给下属")
  131 + @Parameter(name = "id", description = "公告编号", required = true, example = "1024")
  132 + public CommonResult<Integer> forwardAnnouncement(@RequestParam("id") Long id) {
  133 + return success(announcementService.forwardAnnouncement(id, getLoginUserId()));
  134 + }
  135 +
  136 + // ==================== 公共详情 ====================
  137 +
  138 + @GetMapping("/get")
  139 + @Operation(summary = "获得公告详情")
  140 + @Parameter(name = "id", description = "公告编号", required = true, example = "1024")
  141 + @PreAuthorize("@ss.hasPermission('oa:announcement:query')")
  142 + public CommonResult<OaAnnouncementRespVO> getAnnouncement(@RequestParam("id") Long id) {
  143 + Long userId = getLoginUserId();
  144 + OaAnnouncementDO announcement = announcementService.getAnnouncement(id, userId);
  145 + return success(buildAnnouncementRespVO(announcement, userId));
  146 + }
  147 +
  148 + // ==================== 拼接 VO ====================
  149 +
  150 + /**
  151 + * 拼接公告详情
  152 + *
  153 + * @param announcement 公告
  154 + * @param loginUserId 当前登录用户编号
  155 + * @return 公告响应
  156 + */
  157 + private OaAnnouncementRespVO buildAnnouncementRespVO(OaAnnouncementDO announcement, Long loginUserId) {
  158 + if (announcement == null) {
  159 + return null;
  160 + }
  161 + return CollUtil.getFirst(buildAnnouncementRespVOList(Collections.singletonList(announcement), loginUserId));
  162 + }
  163 +
  164 + /**
  165 + * 拼接公告响应列表
  166 + *
  167 + * @param announcements 公告列表
  168 + * @param loginUserId 当前登录用户编号
  169 + * @return 公告响应列表
  170 + */
  171 + private List<OaAnnouncementRespVO> buildAnnouncementRespVOList(List<OaAnnouncementDO> announcements,
  172 + Long loginUserId) {
  173 + if (CollUtil.isEmpty(announcements)) {
  174 + return Collections.emptyList();
  175 + }
  176 + // 1. 查询公告接收关系、发布人、接收人和发布人部门信息
  177 + Map<Long, List<OaAnnouncementReceiverDO>> receiverListMap = announcementService
  178 + .getAnnouncementReceiverListMap(convertSet(announcements, OaAnnouncementDO::getId));
  179 + Collection<Long> userIds = new ArrayList<>(convertSet(announcements,
  180 + announcement -> NumberUtils.parseLong(announcement.getCreator())));
  181 + receiverListMap.values().forEach(receivers -> userIds.addAll(
  182 + convertSet(receivers, OaAnnouncementReceiverDO::getReceiverUserId)));
  183 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
  184 + Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
  185 + Set<Long> subordinateUserIds = convertSet(adminUserApi.getUserListBySubordinate(loginUserId), AdminUserRespDTO::getId);
  186 + // 2. 拼接公告、发布人、接收人和当前接收状态
  187 + return convertList(announcements, announcementDO -> {
  188 + OaAnnouncementRespVO announcement = BeanUtils.toBean(announcementDO, OaAnnouncementRespVO.class)
  189 + .setPublisherUserId(NumberUtils.parseLong(announcementDO.getCreator()));
  190 + MapUtils.findAndThen(userMap, announcement.getPublisherUserId(), publisher -> {
  191 + announcement.setPublisherUserName(publisher.getNickname());
  192 + announcement.setPublisherDeptId(publisher.getDeptId());
  193 + MapUtils.findAndThen(deptMap, publisher.getDeptId(),
  194 + dept -> announcement.setPublisherDeptName(dept.getName()));
  195 + });
  196 + List<OaAnnouncementReceiverDO> receivers = receiverListMap.getOrDefault(announcement.getId(),
  197 + Collections.emptyList());
  198 + List<Long> receiverUserIds = convertList(receivers, OaAnnouncementReceiverDO::getReceiverUserId);
  199 + announcement.setReceiverUserIds(receiverUserIds);
  200 + announcement.setReceiverUserNames(convertList(receiverUserIds, receiverUserId -> {
  201 + AdminUserRespDTO receiverUser = userMap.get(receiverUserId);
  202 + return receiverUser != null ? receiverUser.getNickname() : null;
  203 + }));
  204 + OaAnnouncementReceiverDO loginUserReceiver = CollUtil.findOne(receivers,
  205 + receiver -> receiver.getReceiverUserId().equals(loginUserId));
  206 + if (loginUserReceiver != null) {
  207 + announcement.setReadStatus(loginUserReceiver.getReadStatus());
  208 + }
  209 + announcement.setForwarded(CollUtil.containsAny(receiverUserIds, subordinateUserIds));
  210 + return announcement;
  211 + });
  212 + }
  213 +
  214 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/announcement/vo/OaAnnouncementPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.announcement.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  5 +import cn.iocoder.yudao.module.oa.enums.announcement.OaAnnouncementTypeEnum;
  6 +import cn.iocoder.yudao.module.oa.enums.schedule.OaPriorityEnum;
  7 +import io.swagger.v3.oas.annotations.media.Schema;
  8 +import lombok.Data;
  9 +import lombok.EqualsAndHashCode;
  10 +import org.springframework.format.annotation.DateTimeFormat;
  11 +
  12 +import java.time.LocalDateTime;
  13 +
  14 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  15 +
  16 +@Schema(description = "管理后台 - OA 公告分页 Request VO")
  17 +@Data
  18 +@EqualsAndHashCode(callSuper = true)
  19 +public class OaAnnouncementPageReqVO extends PageParam {
  20 +
  21 + @Schema(description = "公告标题,模糊匹配", example = "国庆节放假通知")
  22 + private String title;
  23 +
  24 + @Schema(description = "公告类型", example = "1")
  25 + @InEnum(value = OaAnnouncementTypeEnum.class, message = "公告类型必须是 {value}")
  26 + private Integer type;
  27 +
  28 + @Schema(description = "优先级", example = "1")
  29 + @InEnum(value = OaPriorityEnum.class, message = "优先级必须是 {value}")
  30 + private Integer priority;
  31 +
  32 + @Schema(description = "是否已读", example = "false")
  33 + private Boolean readStatus;
  34 +
  35 + @Schema(description = "发布时间范围", example = "[\"2026-09-14 00:00:00\", \"2026-09-14 23:59:59\"]")
  36 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  37 + private LocalDateTime[] createTime;
  38 +
  39 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/announcement/vo/OaAnnouncementRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.announcement.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +import java.util.List;
  8 +
  9 +@Schema(description = "管理后台 - OA 公告 Response VO")
  10 +@Data
  11 +public class OaAnnouncementRespVO {
  12 +
  13 + @Schema(description = "公告编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  14 + private Long id;
  15 +
  16 + @Schema(description = "发布人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  17 + private Long publisherUserId;
  18 +
  19 + @Schema(description = "发布人用户昵称", example = "芋道源码")
  20 + private String publisherUserName;
  21 +
  22 + @Schema(description = "发布人部门编号", example = "1")
  23 + private Long publisherDeptId;
  24 +
  25 + @Schema(description = "发布人部门名称", example = "研发部")
  26 + private String publisherDeptName;
  27 +
  28 + @Schema(description = "公告类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  29 + private Integer type;
  30 +
  31 + @Schema(description = "优先级", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  32 + private Integer priority;
  33 +
  34 + @Schema(description = "公告标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "国庆节放假通知")
  35 + private String title;
  36 +
  37 + @Schema(description = "公告内容", example = "请各部门提前安排好假期值班工作。")
  38 + private String content;
  39 +
  40 + @Schema(description = "相关链接", example = "https://example.com/file.pdf")
  41 + private String url;
  42 +
  43 + @Schema(description = "是否置顶", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
  44 + private Boolean top;
  45 +
  46 + @Schema(description = "接收人用户编号列表", example = "[1, 2]")
  47 + private List<Long> receiverUserIds;
  48 +
  49 + @Schema(description = "接收人用户昵称列表", example = "[\"张三\", \"李四\"]")
  50 + private List<String> receiverUserNames;
  51 +
  52 + @Schema(description = "当前接收人是否已读", example = "false")
  53 + private Boolean readStatus;
  54 +
  55 + @Schema(description = "当前接收人是否已转发给下属", example = "false")
  56 + private Boolean forwarded;
  57 +
  58 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, type = "integer", format = "int64", example = "1789347600000")
  59 + private LocalDateTime createTime;
  60 +
  61 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/announcement/vo/OaAnnouncementSaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.announcement.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  4 +import cn.iocoder.yudao.module.oa.enums.announcement.OaAnnouncementTypeEnum;
  5 +import cn.iocoder.yudao.module.oa.enums.schedule.OaPriorityEnum;
  6 +import io.swagger.v3.oas.annotations.media.Schema;
  7 +import jakarta.validation.constraints.NotBlank;
  8 +import jakarta.validation.constraints.NotNull;
  9 +import jakarta.validation.constraints.Size;
  10 +import lombok.Data;
  11 +
  12 +@Schema(description = "管理后台 - OA 公告新增/修改 Request VO")
  13 +@Data
  14 +public class OaAnnouncementSaveReqVO {
  15 +
  16 + @Schema(description = "公告编号", example = "1024")
  17 + private Long id;
  18 +
  19 + @Schema(description = "公告类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  20 + @NotNull(message = "公告类型不能为空")
  21 + @InEnum(value = OaAnnouncementTypeEnum.class, message = "公告类型必须是 {value}")
  22 + private Integer type;
  23 +
  24 + @Schema(description = "优先级", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  25 + @NotNull(message = "优先级不能为空")
  26 + @InEnum(value = OaPriorityEnum.class, message = "优先级必须是 {value}")
  27 + private Integer priority;
  28 +
  29 + @Schema(description = "公告标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "国庆节放假通知")
  30 + @NotBlank(message = "公告标题不能为空")
  31 + @Size(max = 255, message = "公告标题长度不能超过 255 个字符")
  32 + private String title;
  33 +
  34 + @Schema(description = "公告内容", example = "请各部门提前安排好假期值班工作。")
  35 + private String content;
  36 +
  37 + @Schema(description = "相关链接", example = "https://www.iocoder.cn")
  38 + @Size(max = 512, message = "相关链接长度不能超过 512 个字符")
  39 + private String url;
  40 +
  41 + @Schema(description = "是否置顶", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
  42 + @NotNull(message = "是否置顶不能为空")
  43 + private Boolean top;
  44 +
  45 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/attendance/OaAttendanceController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.attendance;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  7 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  8 +import cn.iocoder.yudao.module.oa.controller.admin.attendance.vo.OaAttendanceMonthReportReqVO;
  9 +import cn.iocoder.yudao.module.oa.controller.admin.attendance.vo.OaAttendanceMonthReportRespVO;
  10 +import cn.iocoder.yudao.module.oa.controller.admin.attendance.vo.OaAttendancePageReqVO;
  11 +import cn.iocoder.yudao.module.oa.controller.admin.attendance.vo.OaAttendanceRespVO;
  12 +import cn.iocoder.yudao.module.oa.controller.admin.attendance.vo.OaAttendanceUpdateReqVO;
  13 +import cn.iocoder.yudao.module.oa.controller.admin.attendance.vo.OaAttendanceWeekReportReqVO;
  14 +import cn.iocoder.yudao.module.oa.controller.admin.attendance.vo.OaAttendanceWeekReportRespVO;
  15 +import cn.iocoder.yudao.module.oa.dal.dataobject.attendance.OaAttendanceDO;
  16 +import cn.iocoder.yudao.module.oa.service.attendance.OaAttendanceService;
  17 +import cn.iocoder.yudao.module.system.api.dept.DeptApi;
  18 +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
  19 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  20 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  21 +import io.swagger.v3.oas.annotations.Operation;
  22 +import io.swagger.v3.oas.annotations.Parameter;
  23 +import io.swagger.v3.oas.annotations.tags.Tag;
  24 +import jakarta.annotation.Resource;
  25 +import jakarta.validation.Valid;
  26 +import org.springframework.security.access.prepost.PreAuthorize;
  27 +import org.springframework.validation.annotation.Validated;
  28 +import org.springframework.web.bind.annotation.DeleteMapping;
  29 +import org.springframework.web.bind.annotation.GetMapping;
  30 +import org.springframework.web.bind.annotation.PostMapping;
  31 +import org.springframework.web.bind.annotation.PutMapping;
  32 +import org.springframework.web.bind.annotation.RequestBody;
  33 +import org.springframework.web.bind.annotation.RequestMapping;
  34 +import org.springframework.web.bind.annotation.RequestParam;
  35 +import org.springframework.web.bind.annotation.RestController;
  36 +
  37 +import java.util.Collections;
  38 +import java.util.List;
  39 +import java.util.Map;
  40 +
  41 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  42 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  43 +import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
  44 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  45 +
  46 +@Tag(name = "管理后台 - OA 考勤")
  47 +@RestController
  48 +@RequestMapping("/oa/attendance")
  49 +@Validated
  50 +public class OaAttendanceController {
  51 +
  52 + @Resource
  53 + private OaAttendanceService attendanceService;
  54 +
  55 + @Resource
  56 + private AdminUserApi adminUserApi;
  57 + @Resource
  58 + private DeptApi deptApi;
  59 +
  60 + // ==================== 我的考勤 ====================
  61 +
  62 + @PostMapping("/clock")
  63 + @Operation(summary = "执行当前用户打卡")
  64 + public CommonResult<Long> clockAttendance() {
  65 + return success(attendanceService.clockAttendance(getLoginUserId(), getClientIP()));
  66 + }
  67 +
  68 + @GetMapping("/my-page")
  69 + @Operation(summary = "获得我的考勤分页列表")
  70 + public CommonResult<PageResult<OaAttendanceRespVO>> getMyAttendancePage(
  71 + @Valid OaAttendancePageReqVO pageReqVO) {
  72 + PageResult<OaAttendanceDO> pageResult = attendanceService.getMyAttendancePage(
  73 + pageReqVO, getLoginUserId());
  74 + return success(new PageResult<>(buildAttendanceRespVOList(pageResult.getList()), pageResult.getTotal()));
  75 + }
  76 +
  77 + @GetMapping("/my-today-list")
  78 + @Operation(summary = "获得我的今日考勤列表")
  79 + public CommonResult<List<OaAttendanceRespVO>> getMyTodayAttendanceList() {
  80 + return success(buildAttendanceRespVOList(
  81 + attendanceService.getTodayAttendanceList(getLoginUserId())));
  82 + }
  83 +
  84 + // ==================== 考勤管理 ====================
  85 +
  86 + @PutMapping("/update")
  87 + @Operation(summary = "更新考勤记录")
  88 + @PreAuthorize("@ss.hasPermission('oa:attendance:update')")
  89 + public CommonResult<Boolean> updateAttendance(@Valid @RequestBody OaAttendanceUpdateReqVO updateReqVO) {
  90 + attendanceService.updateAttendance(updateReqVO, getLoginUserId());
  91 + return success(true);
  92 + }
  93 +
  94 + @DeleteMapping("/delete")
  95 + @Operation(summary = "删除考勤记录")
  96 + @Parameter(name = "id", description = "考勤记录编号", required = true, example = "1024")
  97 + @PreAuthorize("@ss.hasPermission('oa:attendance:delete')")
  98 + public CommonResult<Boolean> deleteAttendance(@RequestParam("id") Long id) {
  99 + attendanceService.deleteAttendance(id, getLoginUserId());
  100 + return success(true);
  101 + }
  102 +
  103 + @GetMapping("/get")
  104 + @Operation(summary = "获得考勤详情")
  105 + @Parameter(name = "id", description = "考勤记录编号", required = true, example = "1024")
  106 + @PreAuthorize("@ss.hasPermission('oa:attendance:query')")
  107 + public CommonResult<OaAttendanceRespVO> getAttendance(@RequestParam("id") Long id) {
  108 + OaAttendanceDO attendance = attendanceService.getAttendance(id, getLoginUserId());
  109 + return success(buildAttendanceRespVO(attendance));
  110 + }
  111 +
  112 + @GetMapping("/page")
  113 + @Operation(summary = "获得考勤分页列表")
  114 + @PreAuthorize("@ss.hasPermission('oa:attendance:query')")
  115 + public CommonResult<PageResult<OaAttendanceRespVO>> getAttendancePage(
  116 + @Valid OaAttendancePageReqVO pageReqVO) {
  117 + PageResult<OaAttendanceDO> pageResult = attendanceService.getAttendancePage(pageReqVO, getLoginUserId());
  118 + return success(new PageResult<>(buildAttendanceRespVOList(pageResult.getList()), pageResult.getTotal()));
  119 + }
  120 +
  121 + // ==================== 考勤报表 ====================
  122 +
  123 + @GetMapping("/week-report")
  124 + @Operation(summary = "获得考勤周报")
  125 + @PreAuthorize("@ss.hasPermission('oa:attendance:query')")
  126 + public CommonResult<List<OaAttendanceWeekReportRespVO>> getAttendanceWeekReport(
  127 + @Valid OaAttendanceWeekReportReqVO reqVO) {
  128 + return success(buildAttendanceWeekReportRespVOList(
  129 + attendanceService.getAttendanceWeekReport(reqVO, getLoginUserId())));
  130 + }
  131 +
  132 + @GetMapping("/month-report")
  133 + @Operation(summary = "获得考勤月报")
  134 + @PreAuthorize("@ss.hasPermission('oa:attendance:query')")
  135 + public CommonResult<List<OaAttendanceMonthReportRespVO>> getAttendanceMonthReport(
  136 + @Valid OaAttendanceMonthReportReqVO reqVO) {
  137 + return success(buildAttendanceMonthReportRespVOList(
  138 + attendanceService.getAttendanceMonthReport(reqVO, getLoginUserId())));
  139 + }
  140 +
  141 + // ==================== 拼接 VO ====================
  142 +
  143 + /**
  144 + * 拼接考勤详情
  145 + *
  146 + * @param attendance 考勤记录
  147 + * @return 考勤响应
  148 + */
  149 + private OaAttendanceRespVO buildAttendanceRespVO(OaAttendanceDO attendance) {
  150 + if (attendance == null) {
  151 + return null;
  152 + }
  153 + return CollUtil.getFirst(buildAttendanceRespVOList(Collections.singletonList(attendance)));
  154 + }
  155 +
  156 + /**
  157 + * 拼接考勤响应列表
  158 + *
  159 + * @param attendances 考勤记录列表
  160 + * @return 考勤响应列表
  161 + */
  162 + private List<OaAttendanceRespVO> buildAttendanceRespVOList(List<OaAttendanceDO> attendances) {
  163 + if (CollUtil.isEmpty(attendances)) {
  164 + return Collections.emptyList();
  165 + }
  166 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
  167 + convertSet(attendances, OaAttendanceDO::getUserId));
  168 + Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(
  169 + convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
  170 + return BeanUtils.toBean(attendances, OaAttendanceRespVO.class, attendance ->
  171 + fillAttendanceUserInfo(attendance, userMap, deptMap));
  172 + }
  173 +
  174 + /**
  175 + * 拼接考勤周报响应列表
  176 + *
  177 + * @param reports 考勤周报列表
  178 + * @return 包含用户和部门信息的考勤周报列表
  179 + */
  180 + private List<OaAttendanceWeekReportRespVO> buildAttendanceWeekReportRespVOList(
  181 + List<OaAttendanceWeekReportRespVO> reports) {
  182 + if (CollUtil.isEmpty(reports)) {
  183 + return Collections.emptyList();
  184 + }
  185 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
  186 + convertSet(reports, OaAttendanceWeekReportRespVO::getUserId));
  187 + Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(
  188 + convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
  189 + reports.forEach(report -> fillAttendanceUserInfo(report, userMap, deptMap));
  190 + return reports;
  191 + }
  192 +
  193 + /**
  194 + * 拼接考勤月报响应列表
  195 + *
  196 + * @param reports 考勤月报列表
  197 + * @return 包含用户和部门信息的考勤月报列表
  198 + */
  199 + private List<OaAttendanceMonthReportRespVO> buildAttendanceMonthReportRespVOList(
  200 + List<OaAttendanceMonthReportRespVO> reports) {
  201 + if (CollUtil.isEmpty(reports)) {
  202 + return Collections.emptyList();
  203 + }
  204 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
  205 + convertSet(reports, OaAttendanceMonthReportRespVO::getUserId));
  206 + Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(
  207 + convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
  208 + reports.forEach(report -> fillAttendanceUserInfo(report, userMap, deptMap));
  209 + return reports;
  210 + }
  211 +
  212 + /**
  213 + * 拼接考勤响应的用户和部门信息
  214 + *
  215 + * @param attendance 考勤响应
  216 + * @param userMap 用户 Map
  217 + * @param deptMap 部门 Map
  218 + */
  219 + private void fillAttendanceUserInfo(OaAttendanceRespVO attendance,
  220 + Map<Long, AdminUserRespDTO> userMap,
  221 + Map<Long, DeptRespDTO> deptMap) {
  222 + MapUtils.findAndThen(userMap, attendance.getUserId(), user -> {
  223 + attendance.setUserName(user.getNickname()).setDeptId(user.getDeptId());
  224 + MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> attendance.setDeptName(dept.getName()));
  225 + });
  226 + }
  227 +
  228 + /**
  229 + * 拼接考勤周报响应的用户和部门信息
  230 + *
  231 + * @param report 考勤周报响应
  232 + * @param userMap 用户 Map
  233 + * @param deptMap 部门 Map
  234 + */
  235 + private void fillAttendanceUserInfo(OaAttendanceWeekReportRespVO report,
  236 + Map<Long, AdminUserRespDTO> userMap,
  237 + Map<Long, DeptRespDTO> deptMap) {
  238 + MapUtils.findAndThen(userMap, report.getUserId(), user -> {
  239 + report.setUserName(user.getNickname()).setDeptId(user.getDeptId());
  240 + MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> report.setDeptName(dept.getName()));
  241 + });
  242 + }
  243 +
  244 + /**
  245 + * 拼接考勤月报响应的用户和部门信息
  246 + *
  247 + * @param report 考勤月报响应
  248 + * @param userMap 用户 Map
  249 + * @param deptMap 部门 Map
  250 + */
  251 + private void fillAttendanceUserInfo(OaAttendanceMonthReportRespVO report,
  252 + Map<Long, AdminUserRespDTO> userMap,
  253 + Map<Long, DeptRespDTO> deptMap) {
  254 + MapUtils.findAndThen(userMap, report.getUserId(), user -> {
  255 + report.setUserName(user.getNickname()).setDeptId(user.getDeptId());
  256 + MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> report.setDeptName(dept.getName()));
  257 + });
  258 + }
  259 +
  260 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/attendance/vo/OaAttendanceMonthReportReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.attendance.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.Max;
  5 +import jakarta.validation.constraints.Min;
  6 +import jakarta.validation.constraints.NotNull;
  7 +import lombok.Data;
  8 +
  9 +@Schema(description = "管理后台 - OA 考勤月报 Request VO")
  10 +@Data
  11 +public class OaAttendanceMonthReportReqVO {
  12 +
  13 + @Schema(description = "年份", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026")
  14 + @NotNull(message = "年份不能为空")
  15 + @Min(value = 2000, message = "年份不能小于 2000")
  16 + private Integer year;
  17 +
  18 + @Schema(description = "月份", requiredMode = Schema.RequiredMode.REQUIRED, example = "9")
  19 + @NotNull(message = "月份不能为空")
  20 + @Min(value = 1, message = "月份不能小于 1")
  21 + @Max(value = 12, message = "月份不能大于 12")
  22 + private Integer month;
  23 +
  24 + @Schema(description = "用户编号", example = "1")
  25 + private Long userId;
  26 +
  27 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/attendance/vo/OaAttendanceMonthReportRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.attendance.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import lombok.experimental.Accessors;
  6 +
  7 +@Schema(description = "管理后台 - OA 考勤月报 Response VO")
  8 +@Data
  9 +@Accessors(chain = true)
  10 +public class OaAttendanceMonthReportRespVO {
  11 +
  12 + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  13 + private Long userId;
  14 +
  15 + @Schema(description = "用户昵称", example = "芋道")
  16 + private String userName;
  17 +
  18 + @Schema(description = "部门编号", example = "100")
  19 + private Long deptId;
  20 +
  21 + @Schema(description = "部门名称", example = "研发部")
  22 + private String deptName;
  23 +
  24 + @Schema(description = "上班打卡次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
  25 + private Integer clockInCount;
  26 +
  27 + @Schema(description = "下班打卡次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "19")
  28 + private Integer clockOutCount;
  29 +
  30 + @Schema(description = "正常次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "36")
  31 + private Integer normalCount;
  32 +
  33 + @Schema(description = "迟到次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  34 + private Integer lateCount;
  35 +
  36 + @Schema(description = "早退次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  37 + private Integer earlyCount;
  38 +
  39 + @Schema(description = "请假天数,审批通过且开始时间在当月的申请天数合计", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  40 + private Integer leaveDays;
  41 +
  42 + @Schema(description = "出差天数,审批通过且开始时间在当月的申请天数合计", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
  43 + private Integer travelDays;
  44 +
  45 + @Schema(description = "旷工天数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  46 + private Integer absentDays;
  47 +
  48 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/attendance/vo/OaAttendancePageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.attendance.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  5 +import cn.iocoder.yudao.module.oa.enums.attendance.OaAttendanceStatusEnum;
  6 +import cn.iocoder.yudao.module.oa.enums.attendance.OaAttendanceTypeEnum;
  7 +import io.swagger.v3.oas.annotations.media.Schema;
  8 +import lombok.Data;
  9 +import lombok.EqualsAndHashCode;
  10 +import org.springframework.format.annotation.DateTimeFormat;
  11 +
  12 +import java.time.LocalDateTime;
  13 +
  14 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  15 +
  16 +@Schema(description = "管理后台 - OA 考勤分页 Request VO")
  17 +@Data
  18 +@EqualsAndHashCode(callSuper = true)
  19 +public class OaAttendancePageReqVO extends PageParam {
  20 +
  21 + @Schema(description = "用户编号", example = "1")
  22 + private Long userId;
  23 +
  24 + @Schema(description = "考勤类型", example = "1")
  25 + @InEnum(value = OaAttendanceTypeEnum.class, message = "考勤类型必须是 {value}")
  26 + private Integer type;
  27 +
  28 + @Schema(description = "考勤状态", example = "1")
  29 + @InEnum(value = OaAttendanceStatusEnum.class, message = "考勤状态必须是 {value}")
  30 + private Integer status;
  31 +
  32 + @Schema(description = "考勤时间范围", example = "[\"2026-09-14 00:00:00\", \"2026-09-14 23:59:59\"]")
  33 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  34 + private LocalDateTime[] attendanceTime;
  35 +
  36 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/attendance/vo/OaAttendanceRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.attendance.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import lombok.experimental.Accessors;
  6 +
  7 +import java.time.LocalDateTime;
  8 +
  9 +@Schema(description = "管理后台 - OA 考勤 Response VO")
  10 +@Data
  11 +@Accessors(chain = true)
  12 +public class OaAttendanceRespVO {
  13 +
  14 + @Schema(description = "考勤记录编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  15 + private Long id;
  16 +
  17 + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  18 + private Long userId;
  19 +
  20 + @Schema(description = "用户昵称", example = "芋道")
  21 + private String userName;
  22 +
  23 + @Schema(description = "部门编号", example = "100")
  24 + private Long deptId;
  25 +
  26 + @Schema(description = "部门名称", example = "研发部")
  27 + private String deptName;
  28 +
  29 + @Schema(description = "考勤类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  30 + private Integer type;
  31 +
  32 + @Schema(description = "考勤状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  33 + private Integer status;
  34 +
  35 + @Schema(description = "考勤时间", requiredMode = Schema.RequiredMode.REQUIRED, type = "integer", format = "int64", example = "1789380000000")
  36 + private LocalDateTime attendanceTime;
  37 +
  38 + @Schema(description = "考勤 IP", example = "127.0.0.1")
  39 + private String attendanceIp;
  40 +
  41 + @Schema(description = "考勤备注", example = "正常打卡")
  42 + private String remark;
  43 +
  44 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, type = "integer", format = "int64", example = "1789347600000")
  45 + private LocalDateTime createTime;
  46 +
  47 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/attendance/vo/OaAttendanceUpdateReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.attendance.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  4 +import cn.iocoder.yudao.module.oa.enums.attendance.OaAttendanceStatusEnum;
  5 +import io.swagger.v3.oas.annotations.media.Schema;
  6 +import jakarta.validation.constraints.NotNull;
  7 +import jakarta.validation.constraints.Size;
  8 +import lombok.Data;
  9 +
  10 +@Schema(description = "管理后台 - OA 考勤修改 Request VO")
  11 +@Data
  12 +public class OaAttendanceUpdateReqVO {
  13 +
  14 + @Schema(description = "考勤记录编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  15 + @NotNull(message = "考勤记录编号不能为空")
  16 + private Long id;
  17 +
  18 + @Schema(description = "考勤状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  19 + @NotNull(message = "考勤状态不能为空")
  20 + @InEnum(value = OaAttendanceStatusEnum.class, message = "考勤状态必须是 {value}")
  21 + private Integer status;
  22 +
  23 + @Schema(description = "考勤备注", example = "因交通拥堵迟到")
  24 + @Size(max = 500, message = "考勤备注不能超过 500 个字符")
  25 + private String remark;
  26 +
  27 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/attendance/vo/OaAttendanceWeekReportReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.attendance.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotNull;
  5 +import lombok.Data;
  6 +import org.springframework.format.annotation.DateTimeFormat;
  7 +
  8 +import java.time.LocalDate;
  9 +
  10 +@Schema(description = "管理后台 - OA 考勤周报 Request VO")
  11 +@Data
  12 +public class OaAttendanceWeekReportReqVO {
  13 +
  14 + @Schema(description = "周开始日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-09-07")
  15 + @NotNull(message = "周开始日期不能为空")
  16 + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
  17 + private LocalDate startDate;
  18 +
  19 + @Schema(description = "用户编号", example = "1")
  20 + private Long userId;
  21 +
  22 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/attendance/vo/OaAttendanceWeekReportRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.attendance.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import lombok.experimental.Accessors;
  6 +
  7 +import java.time.LocalDate;
  8 +import java.time.LocalDateTime;
  9 +import java.util.List;
  10 +
  11 +@Schema(description = "管理后台 - OA 考勤周报 Response VO")
  12 +@Data
  13 +@Accessors(chain = true)
  14 +public class OaAttendanceWeekReportRespVO {
  15 +
  16 + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  17 + private Long userId;
  18 +
  19 + @Schema(description = "用户昵称", example = "芋道")
  20 + private String userName;
  21 +
  22 + @Schema(description = "部门编号", example = "100")
  23 + private Long deptId;
  24 +
  25 + @Schema(description = "部门名称", example = "研发部")
  26 + private String deptName;
  27 +
  28 + @Schema(description = "每日考勤列表", requiredMode = Schema.RequiredMode.REQUIRED)
  29 + private List<DailyAttendance> dailyAttendances;
  30 +
  31 + @Schema(description = "管理后台 - OA 每日考勤 Response VO")
  32 + @Data
  33 + @Accessors(chain = true)
  34 + public static class DailyAttendance {
  35 +
  36 + @Schema(description = "日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-09-07")
  37 + private LocalDate date;
  38 +
  39 + @Schema(description = "上班考勤记录编号", example = "1024")
  40 + private Long clockInId;
  41 +
  42 + @Schema(description = "上班打卡时间", type = "integer", format = "int64", example = "1789347600000")
  43 + private LocalDateTime clockInTime;
  44 +
  45 + @Schema(description = "上班打卡状态", example = "1")
  46 + private Integer clockInStatus;
  47 +
  48 + @Schema(description = "下班考勤记录编号", example = "1025")
  49 + private Long clockOutId;
  50 +
  51 + @Schema(description = "下班打卡时间", type = "integer", format = "int64", example = "1789380000000")
  52 + private LocalDateTime clockOutTime;
  53 +
  54 + @Schema(description = "下班打卡状态", example = "1")
  55 + private Integer clockOutStatus;
  56 +
  57 + }
  58 +
  59 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/contact/OaContactCategoryController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.contact;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  5 +import cn.iocoder.yudao.module.oa.controller.admin.contact.vo.category.OaContactCategoryRespVO;
  6 +import cn.iocoder.yudao.module.oa.controller.admin.contact.vo.category.OaContactCategorySaveReqVO;
  7 +import cn.iocoder.yudao.module.oa.dal.dataobject.contact.OaContactCategoryDO;
  8 +import cn.iocoder.yudao.module.oa.service.contact.OaContactCategoryService;
  9 +import io.swagger.v3.oas.annotations.Operation;
  10 +import io.swagger.v3.oas.annotations.Parameter;
  11 +import io.swagger.v3.oas.annotations.tags.Tag;
  12 +import jakarta.annotation.Resource;
  13 +import jakarta.validation.Valid;
  14 +import org.springframework.security.access.prepost.PreAuthorize;
  15 +import org.springframework.validation.annotation.Validated;
  16 +import org.springframework.web.bind.annotation.DeleteMapping;
  17 +import org.springframework.web.bind.annotation.GetMapping;
  18 +import org.springframework.web.bind.annotation.PostMapping;
  19 +import org.springframework.web.bind.annotation.PutMapping;
  20 +import org.springframework.web.bind.annotation.RequestBody;
  21 +import org.springframework.web.bind.annotation.RequestMapping;
  22 +import org.springframework.web.bind.annotation.RequestParam;
  23 +import org.springframework.web.bind.annotation.RestController;
  24 +
  25 +import java.util.List;
  26 +
  27 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  28 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  29 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  30 +
  31 +@Tag(name = "管理后台 - OA 联系人分类")
  32 +@RestController
  33 +@RequestMapping("/oa/contact-category")
  34 +@Validated
  35 +public class OaContactCategoryController {
  36 +
  37 + @Resource
  38 + private OaContactCategoryService contactCategoryService;
  39 +
  40 + @PostMapping("/create")
  41 + @Operation(summary = "创建联系人分类")
  42 + public CommonResult<Long> createContactCategory(
  43 + @Valid @RequestBody OaContactCategorySaveReqVO createReqVO) {
  44 + return success(contactCategoryService.createContactCategory(createReqVO, getLoginUserId()));
  45 + }
  46 +
  47 + @PutMapping("/update")
  48 + @Operation(summary = "更新联系人分类")
  49 + public CommonResult<Boolean> updateContactCategory(
  50 + @Valid @RequestBody OaContactCategorySaveReqVO updateReqVO) {
  51 + contactCategoryService.updateContactCategory(updateReqVO, getLoginUserId());
  52 + return success(true);
  53 + }
  54 +
  55 + @DeleteMapping("/delete")
  56 + @Operation(summary = "删除联系人分类")
  57 + @Parameter(name = "id", description = "分类编号", required = true, example = "1024")
  58 + public CommonResult<Boolean> deleteContactCategory(@RequestParam("id") Long id) {
  59 + contactCategoryService.deleteContactCategory(id, getLoginUserId());
  60 + return success(true);
  61 + }
  62 +
  63 + @GetMapping("/get")
  64 + @Operation(summary = "获得联系人分类")
  65 + @Parameter(name = "id", description = "分类编号", required = true, example = "1024")
  66 + @PreAuthorize("@ss.hasPermission('oa:contact:query')")
  67 + public CommonResult<OaContactCategoryRespVO> getContactCategory(@RequestParam("id") Long id) {
  68 + OaContactCategoryDO category = contactCategoryService.getContactCategory(id, getLoginUserId());
  69 + return success(BeanUtils.toBean(category, OaContactCategoryRespVO.class));
  70 + }
  71 +
  72 + @GetMapping("/simple-list")
  73 + @Operation(summary = "获得分类精简列表")
  74 + public CommonResult<List<OaContactCategoryRespVO>> getSimpleContactCategoryList() {
  75 + List<OaContactCategoryDO> categories = contactCategoryService.getContactCategoryList(getLoginUserId());
  76 + return success(convertList(categories, category -> new OaContactCategoryRespVO()
  77 + .setId(category.getId()).setName(category.getName())));
  78 + }
  79 +
  80 + @GetMapping("/list")
  81 + @Operation(summary = "获得联系人分类列表")
  82 + @PreAuthorize("@ss.hasPermission('oa:contact:query')")
  83 + public CommonResult<List<OaContactCategoryRespVO>> getContactCategoryList() {
  84 + List<OaContactCategoryDO> categories = contactCategoryService.getContactCategoryList(getLoginUserId());
  85 + return success(BeanUtils.toBean(categories, OaContactCategoryRespVO.class));
  86 + }
  87 +
  88 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/contact/OaContactController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.contact;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.ObjectUtil;
  5 +import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
  6 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  7 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  8 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  9 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  10 +import cn.iocoder.yudao.module.oa.controller.admin.contact.vo.OaContactHandleReqVO;
  11 +import cn.iocoder.yudao.module.oa.controller.admin.contact.vo.OaContactPageReqVO;
  12 +import cn.iocoder.yudao.module.oa.controller.admin.contact.vo.OaContactRespVO;
  13 +import cn.iocoder.yudao.module.oa.controller.admin.contact.vo.OaContactSaveReqVO;
  14 +import cn.iocoder.yudao.module.oa.controller.admin.contact.vo.OaContactShareReqVO;
  15 +import cn.iocoder.yudao.module.oa.dal.dataobject.contact.OaContactDO;
  16 +import cn.iocoder.yudao.module.oa.dal.dataobject.contact.OaContactCategoryDO;
  17 +import cn.iocoder.yudao.module.oa.service.contact.OaContactCategoryService;
  18 +import cn.iocoder.yudao.module.oa.dal.dataobject.contact.OaContactShareDO;
  19 +import cn.iocoder.yudao.module.oa.service.contact.OaContactService;
  20 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  21 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  22 +import io.swagger.v3.oas.annotations.Operation;
  23 +import io.swagger.v3.oas.annotations.Parameter;
  24 +import io.swagger.v3.oas.annotations.tags.Tag;
  25 +import jakarta.annotation.Resource;
  26 +import jakarta.validation.Valid;
  27 +import org.springframework.security.access.prepost.PreAuthorize;
  28 +import org.springframework.validation.annotation.Validated;
  29 +import org.springframework.web.bind.annotation.DeleteMapping;
  30 +import org.springframework.web.bind.annotation.GetMapping;
  31 +import org.springframework.web.bind.annotation.PostMapping;
  32 +import org.springframework.web.bind.annotation.PutMapping;
  33 +import org.springframework.web.bind.annotation.RequestBody;
  34 +import org.springframework.web.bind.annotation.RequestMapping;
  35 +import org.springframework.web.bind.annotation.RequestParam;
  36 +import org.springframework.web.bind.annotation.RestController;
  37 +
  38 +import java.util.Collections;
  39 +import java.util.List;
  40 +import java.util.Map;
  41 +import java.util.Set;
  42 +
  43 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  44 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  45 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
  46 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  47 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertListByFlatMap;
  48 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
  49 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  50 +
  51 +@Tag(name = "管理后台 - OA 通讯录")
  52 +@RestController
  53 +@RequestMapping("/oa/contact")
  54 +@Validated
  55 +public class OaContactController {
  56 +
  57 + @Resource
  58 + private OaContactService contactService;
  59 + @Resource
  60 + private OaContactCategoryService contactCategoryService;
  61 +
  62 + @Resource
  63 + private AdminUserApi adminUserApi;
  64 +
  65 + // ==================== 我的联系人 ====================
  66 +
  67 + @PostMapping("/create")
  68 + @Operation(summary = "创建外部联系人")
  69 + @PreAuthorize("@ss.hasPermission('oa:contact:create')")
  70 + public CommonResult<Long> createContact(@Valid @RequestBody OaContactSaveReqVO createReqVO) {
  71 + return success(contactService.createContact(createReqVO, getLoginUserId()));
  72 + }
  73 +
  74 + @PutMapping("/update")
  75 + @Operation(summary = "更新外部联系人")
  76 + @PreAuthorize("@ss.hasPermission('oa:contact:update')")
  77 + public CommonResult<Boolean> updateContact(@Valid @RequestBody OaContactSaveReqVO updateReqVO) {
  78 + contactService.updateContact(updateReqVO, getLoginUserId());
  79 + return success(true);
  80 + }
  81 +
  82 + @DeleteMapping("/delete")
  83 + @Operation(summary = "删除外部联系人")
  84 + @Parameter(name = "id", description = "联系人编号", required = true, example = "1024")
  85 + @PreAuthorize("@ss.hasPermission('oa:contact:delete')")
  86 + public CommonResult<Boolean> deleteContact(@RequestParam("id") Long id) {
  87 + contactService.deleteContact(id, getLoginUserId());
  88 + return success(true);
  89 + }
  90 +
  91 + @PostMapping("/share")
  92 + @Operation(summary = "共享外部联系人")
  93 + public CommonResult<Boolean> shareContact(@Valid @RequestBody OaContactShareReqVO shareReqVO) {
  94 + contactService.shareContact(shareReqVO.getContactId(), shareReqVO.getUserIds(), getLoginUserId());
  95 + return success(true);
  96 + }
  97 +
  98 + // ==================== 共享与我 ====================
  99 +
  100 + @DeleteMapping("/delete-received")
  101 + @Operation(summary = "删除接收到的共享联系人")
  102 + @Parameter(name = "contactId", description = "联系人编号", required = true, example = "1024")
  103 + public CommonResult<Boolean> deleteReceivedContact(@RequestParam("contactId") Long contactId) {
  104 + contactService.deleteReceivedContact(contactId, getLoginUserId());
  105 + return success(true);
  106 + }
  107 +
  108 + @PutMapping("/handle-share")
  109 + @Operation(summary = "处理外部联系人共享")
  110 + public CommonResult<Boolean> handleContactShare(@Valid @RequestBody OaContactHandleReqVO handleReqVO) {
  111 + contactService.handleContactShare(handleReqVO.getContactId(), handleReqVO.getCategoryId(), getLoginUserId());
  112 + return success(true);
  113 + }
  114 +
  115 + // ==================== 公共查询 ====================
  116 +
  117 + @GetMapping("/get")
  118 + @Operation(summary = "获得外部联系人详情")
  119 + @Parameter(name = "id", description = "联系人编号", required = true, example = "1024")
  120 + @PreAuthorize("@ss.hasPermission('oa:contact:query')")
  121 + public CommonResult<OaContactRespVO> getContact(@RequestParam("id") Long id) {
  122 + OaContactDO contact = contactService.getContact(id, getLoginUserId());
  123 + return success(buildContactRespVO(contact));
  124 + }
  125 +
  126 + @GetMapping("/my-page")
  127 + @Operation(summary = "获得我的联系人分页")
  128 + @PreAuthorize("@ss.hasPermission('oa:contact:query')")
  129 + public CommonResult<PageResult<OaContactRespVO>> getMyContactPage(@Valid OaContactPageReqVO pageReqVO) {
  130 + PageResult<OaContactDO> pageResult = contactService.getMyContactPage(pageReqVO, getLoginUserId());
  131 + return success(new PageResult<>(buildContactRespVOList(pageResult.getList()), pageResult.getTotal()));
  132 + }
  133 +
  134 + @GetMapping("/received-page")
  135 + @Operation(summary = "获得共享给我的联系人分页")
  136 + @PreAuthorize("@ss.hasPermission('oa:contact:query')")
  137 + public CommonResult<PageResult<OaContactRespVO>> getReceivedContactPage(@Valid OaContactPageReqVO pageReqVO) {
  138 + PageResult<OaContactDO> pageResult = contactService.getReceivedContactPage(pageReqVO, getLoginUserId());
  139 + return success(new PageResult<>(buildContactRespVOList(pageResult.getList()), pageResult.getTotal()));
  140 + }
  141 +
  142 + @GetMapping("/shared-page")
  143 + @Operation(summary = "获得我共享的联系人分页")
  144 + @PreAuthorize("@ss.hasPermission('oa:contact:query')")
  145 + public CommonResult<PageResult<OaContactRespVO>> getSharedContactPage(@Valid OaContactPageReqVO pageReqVO) {
  146 + PageResult<OaContactShareDO> pageResult = contactService.getSharedContactPage(pageReqVO, getLoginUserId());
  147 + List<OaContactDO> contacts = contactService.getContactList(convertSet(pageResult.getList(), OaContactShareDO::getContactId));
  148 + // 拼接 VO
  149 + Map<Long, OaContactRespVO> contactMap = convertMap(buildContactRespVOList(contacts), OaContactRespVO::getId);
  150 + return success(new PageResult<>(convertList(pageResult.getList(), share -> {
  151 + OaContactRespVO contact = BeanUtils.toBean(contactMap.get(share.getContactId()), OaContactRespVO.class);
  152 + contact.setShare(CollUtil.findOne(contact.getShares(), item -> ObjectUtil.equal(item.getId(), share.getId())));
  153 + return contact;
  154 + }), pageResult.getTotal()));
  155 + }
  156 +
  157 + // ==================== 拼接 VO ====================
  158 +
  159 + /**
  160 + * 拼接联系人详情
  161 + *
  162 + * @param contact 联系人
  163 + * @return 联系人响应
  164 + */
  165 + private OaContactRespVO buildContactRespVO(OaContactDO contact) {
  166 + if (contact == null) {
  167 + return null;
  168 + }
  169 + return CollUtil.getFirst(buildContactRespVOList(Collections.singletonList(contact)));
  170 + }
  171 +
  172 + /**
  173 + * 拼接外部联系人响应列表
  174 + *
  175 + * @param contacts 联系人列表
  176 + * @return 联系人响应列表
  177 + */
  178 + private List<OaContactRespVO> buildContactRespVOList(List<OaContactDO> contacts) {
  179 + if (CollUtil.isEmpty(contacts)) {
  180 + return Collections.emptyList();
  181 + }
  182 + // 1.1 批量查询共享关系
  183 + Map<Long, List<OaContactShareDO>> contactShareListMap = contactService.getContactShareListMap(
  184 + convertSet(contacts, OaContactDO::getId));
  185 + List<OaContactShareDO> shares = convertListByFlatMap(contactShareListMap.values(), List::stream);
  186 + // 1.2 批量查询创建人和接收人信息
  187 + Set<Long> userIds = convertSet(contacts, contact -> NumberUtils.parseLong(contact.getCreator()));
  188 + userIds.addAll(convertSet(shares, OaContactShareDO::getUserId));
  189 + userIds.addAll(convertSet(shares, share -> NumberUtils.parseLong(share.getCreator())));
  190 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
  191 + // 1.3 批量查询创建人和接收人的分类
  192 + Set<Long> categoryIds = convertSet(contacts, OaContactDO::getCategoryId);
  193 + categoryIds.addAll(convertSet(shares, OaContactShareDO::getCategoryId));
  194 + Map<Long, OaContactCategoryDO> categoryMap = contactCategoryService.getContactCategoryMap(categoryIds);
  195 + Long loginUserId = getLoginUserId();
  196 + // 2. 拼接展示字段,接收人仅能查看自己的共享关系
  197 + return convertList(contacts, contactDO -> {
  198 + OaContactRespVO contact = BeanUtils.toBean(contactDO, OaContactRespVO.class)
  199 + .setOwnerUserId(NumberUtils.parseLong(contactDO.getCreator()));
  200 + MapUtils.findAndThen(categoryMap, contact.getCategoryId(),
  201 + category -> contact.setCategoryName(category.getName()));
  202 + MapUtils.findAndThen(userMap, contact.getOwnerUserId(),
  203 + user -> contact.setOwnerUserName(user.getNickname()));
  204 + List<OaContactShareDO> contactShares = contactShareListMap.getOrDefault(contact.getId(),
  205 + Collections.emptyList());
  206 + // 仅展示本人发出的共享,不将初始化的本人关联计入共享人数
  207 + List<OaContactShareDO> visibleContactShares = filterList(contactShares,
  208 + share -> ObjectUtil.equal(NumberUtils.parseLong(share.getCreator()), loginUserId)
  209 + && ObjectUtil.notEqual(share.getUserId(), loginUserId));
  210 + contact.setShares(convertList(visibleContactShares, share -> {
  211 + OaContactRespVO.Share shareVO = BeanUtils.toBean(share, OaContactRespVO.Share.class);
  212 + MapUtils.findAndThen(categoryMap, share.getCategoryId(),
  213 + category -> shareVO.setCategoryName(category.getName()));
  214 + MapUtils.findAndThen(userMap, NumberUtils.parseLong(share.getCreator()),
  215 + user -> shareVO.setCreatorName(user.getNickname()));
  216 + MapUtils.findAndThen(userMap, share.getUserId(),
  217 + user -> shareVO.setUserName(user.getNickname()).setUserAvatar(user.getAvatar()));
  218 + return shareVO;
  219 + }));
  220 + OaContactShareDO currentUserShare = CollUtil.findOne(contactShares,
  221 + share -> share.getUserId().equals(loginUserId));
  222 + if (currentUserShare != null) {
  223 + MapUtils.findAndThen(userMap, NumberUtils.parseLong(currentUserShare.getCreator()),
  224 + user -> contact.setSharerName(user.getNickname()));
  225 + contact.setHandleStatus(currentUserShare.getHandleStatus())
  226 + .setSharedCategoryId(currentUserShare.getCategoryId());
  227 + MapUtils.findAndThen(categoryMap, currentUserShare.getCategoryId(),
  228 + category -> contact.setSharedCategoryName(category.getName()));
  229 + }
  230 + return contact;
  231 + });
  232 + }
  233 +
  234 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/contact/vo/OaContactHandleReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.contact.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotNull;
  5 +import lombok.Data;
  6 +
  7 +@Schema(description = "管理后台 - OA 联系人共享处理 Request VO")
  8 +@Data
  9 +public class OaContactHandleReqVO {
  10 +
  11 + @Schema(description = "联系人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  12 + @NotNull(message = "联系人编号不能为空")
  13 + private Long contactId;
  14 +
  15 + @Schema(description = "接收人分类编号", example = "1024")
  16 + private Long categoryId;
  17 +
  18 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/contact/vo/OaContactPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.contact.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import jakarta.validation.constraints.Pattern;
  6 +import lombok.Data;
  7 +import lombok.EqualsAndHashCode;
  8 +
  9 +@Schema(description = "管理后台 - OA 外部联系人分页 Request VO")
  10 +@Data
  11 +@EqualsAndHashCode(callSuper = true)
  12 +public class OaContactPageReqVO extends PageParam {
  13 +
  14 + @Schema(description = "姓名、拼音、手机或公司名称关键字", example = "张三")
  15 + private String keyword;
  16 +
  17 + @Schema(description = "分类编号", example = "1024")
  18 + private Long categoryId;
  19 +
  20 + @Schema(description = "是否已处理", example = "false")
  21 + private Boolean handleStatus;
  22 +
  23 + @Schema(description = "姓名拼音首字母,空字符串表示全部", example = "A")
  24 + @Pattern(regexp = "^[A-Z]?$", message = "姓名拼音首字母必须为 A-Z")
  25 + private String alphabet;
  26 +
  27 +
  28 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/contact/vo/OaContactRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.contact.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +import java.util.List;
  8 +
  9 +@Schema(description = "管理后台 - OA 外部联系人 Response VO")
  10 +@Data
  11 +public class OaContactRespVO {
  12 +
  13 + @Schema(description = "联系人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  14 + private Long id;
  15 +
  16 + @Schema(description = "创建人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  17 + private Long ownerUserId;
  18 +
  19 + @Schema(description = "创建人用户昵称", example = "芋道源码")
  20 + private String ownerUserName;
  21 +
  22 + @Schema(description = "分类编号", example = "1024")
  23 + private Long categoryId;
  24 +
  25 + @Schema(description = "分类名称", example = "客户")
  26 + private String categoryName;
  27 +
  28 + @Schema(description = "姓名", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
  29 + private String name;
  30 +
  31 + @Schema(description = "姓名拼音", example = "zhangsan")
  32 + private String pinyin;
  33 +
  34 + @Schema(description = "性别", example = "1")
  35 + private Integer sex;
  36 +
  37 + @Schema(description = "手机号码", example = "13800138000")
  38 + private String mobile;
  39 +
  40 + @Schema(description = "邮箱", example = "yudao@example.com")
  41 + private String email;
  42 +
  43 + @Schema(description = "地址", example = "上海市浦东新区世纪大道 100 号")
  44 + private String address;
  45 +
  46 + @Schema(description = "公司名称", example = "芋道科技有限公司")
  47 + private String companyName;
  48 +
  49 + @Schema(description = "公司电话", example = "021-12345678")
  50 + private String companyPhone;
  51 +
  52 + @Schema(description = "头像地址", example = "https://example.com/image.png")
  53 + private String avatar;
  54 +
  55 + @Schema(description = "备注", example = "请提前联系行政部确认")
  56 + private String remark;
  57 +
  58 + @Schema(description = "共享记录列表")
  59 + private List<Share> shares;
  60 +
  61 + @Schema(description = "当前行的共享关系,仅我共享的列表返回")
  62 + private Share share;
  63 +
  64 + @Schema(description = "分享给当前用户的共享人昵称", example = "芋道源码")
  65 + private String sharerName;
  66 +
  67 + @Schema(description = "当前接收人的处理状态", example = "false")
  68 + private Boolean handleStatus;
  69 +
  70 + @Schema(description = "当前接收人的分类编号", example = "1024")
  71 + private Long sharedCategoryId;
  72 +
  73 + @Schema(description = "当前接收人的分类名称", example = "客户联系人")
  74 + private String sharedCategoryName;
  75 +
  76 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, type = "integer", format = "int64", example = "1789347600000")
  77 + private LocalDateTime createTime;
  78 +
  79 + @Schema(description = "管理后台 - OA 联系人共享 Response VO")
  80 + @Data
  81 + public static class Share {
  82 +
  83 + @Schema(description = "共享记录编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  84 + private Long id;
  85 +
  86 + @Schema(description = "共享接收人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  87 + private Long userId;
  88 +
  89 + @Schema(description = "实际共享人昵称", example = "张三")
  90 + private String creatorName;
  91 +
  92 + @Schema(description = "共享接收人用户昵称", example = "芋道源码")
  93 + private String userName;
  94 +
  95 + @Schema(description = "共享接收人用户头像", example = "https://example.com/avatar.png")
  96 + private String userAvatar;
  97 +
  98 + @Schema(description = "分类编号", example = "1024")
  99 + private Long categoryId;
  100 +
  101 + @Schema(description = "接收人的分类名称", example = "客户")
  102 + private String categoryName;
  103 +
  104 + @Schema(description = "处理状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
  105 + private Boolean handleStatus;
  106 +
  107 + @Schema(description = "共享时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "1789347600000")
  108 + private LocalDateTime createTime;
  109 +
  110 + }
  111 +
  112 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/contact/vo/OaContactSaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.contact.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.validation.Mobile;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import jakarta.validation.constraints.Email;
  6 +import jakarta.validation.constraints.NotBlank;
  7 +import jakarta.validation.constraints.Size;
  8 +import lombok.Data;
  9 +
  10 +@Schema(description = "管理后台 - OA 外部联系人新增/修改 Request VO")
  11 +@Data
  12 +public class OaContactSaveReqVO {
  13 +
  14 + @Schema(description = "联系人编号", example = "1024")
  15 + private Long id;
  16 +
  17 + @Schema(description = "分类编号", example = "1024")
  18 + private Long categoryId;
  19 +
  20 + @Schema(description = "姓名", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
  21 + @NotBlank(message = "姓名不能为空")
  22 + @Size(max = 50, message = "姓名长度不能超过 50 个字符")
  23 + private String name;
  24 +
  25 + @Schema(description = "性别", example = "1")
  26 + private Integer sex;
  27 +
  28 + @Schema(description = "手机号码", requiredMode = Schema.RequiredMode.REQUIRED, example = "13800138000")
  29 + @NotBlank(message = "手机号码不能为空")
  30 + @Mobile
  31 + @Size(max = 20, message = "手机号码长度不能超过 20 个字符")
  32 + private String mobile;
  33 +
  34 + @Schema(description = "邮箱", requiredMode = Schema.RequiredMode.REQUIRED, example = "user@example.com")
  35 + @NotBlank(message = "邮箱不能为空")
  36 + @Email(message = "邮箱格式不正确")
  37 + @Size(max = 100, message = "邮箱长度不能超过 100 个字符")
  38 + private String email;
  39 +
  40 + @Schema(description = "地址", example = "上海市浦东新区世纪大道 100 号")
  41 + @Size(max = 255, message = "地址长度不能超过 255 个字符")
  42 + private String address;
  43 +
  44 + @Schema(description = "公司名称", example = "芋道科技有限公司")
  45 + @Size(max = 100, message = "公司名称长度不能超过 100 个字符")
  46 + private String companyName;
  47 +
  48 + @Schema(description = "公司电话", example = "021-12345678")
  49 + @Size(max = 30, message = "公司电话长度不能超过 30 个字符")
  50 + private String companyPhone;
  51 +
  52 + @Schema(description = "头像地址", example = "https://example.com/image.png")
  53 + @Size(max = 512, message = "头像地址长度不能超过 512 个字符")
  54 + private String avatar;
  55 +
  56 + @Schema(description = "备注", example = "请提前联系行政部确认")
  57 + @Size(max = 500, message = "备注长度不能超过 500 个字符")
  58 + private String remark;
  59 +
  60 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/contact/vo/OaContactShareReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.contact.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotEmpty;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import lombok.Data;
  7 +
  8 +import java.util.List;
  9 +
  10 +@Schema(description = "管理后台 - OA 联系人共享 Request VO")
  11 +@Data
  12 +public class OaContactShareReqVO {
  13 +
  14 + @Schema(description = "联系人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  15 + @NotNull(message = "联系人编号不能为空")
  16 + private Long contactId;
  17 +
  18 + @Schema(description = "共享接收人用户编号列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 2]")
  19 + @NotEmpty(message = "共享接收人不能为空")
  20 + private List<@NotNull(message = "共享接收人编号不能为空") Long> userIds;
  21 +
  22 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/contact/vo/category/OaContactCategoryRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.contact.vo.category;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +@Schema(description = "管理后台 - OA 联系人分类 Response VO")
  9 +@Data
  10 +public class OaContactCategoryRespVO {
  11 +
  12 + @Schema(description = "分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  13 + private Long id;
  14 +
  15 + @Schema(description = "分类名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "客户")
  16 + private String name;
  17 +
  18 + @Schema(description = "显示排序", example = "10")
  19 + private Integer sort;
  20 +
  21 + @Schema(description = "创建时间", type = "integer", format = "int64", example = "1789347600000")
  22 + private LocalDateTime createTime;
  23 +
  24 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/contact/vo/category/OaContactCategorySaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.contact.vo.category;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotBlank;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import jakarta.validation.constraints.Size;
  7 +import lombok.Data;
  8 +
  9 +@Schema(description = "管理后台 - OA 联系人分类新增/修改 Request VO")
  10 +@Data
  11 +public class OaContactCategorySaveReqVO {
  12 +
  13 + @Schema(description = "分类编号", example = "1024")
  14 + private Long id;
  15 +
  16 + @Schema(description = "分类名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "客户")
  17 + @NotBlank(message = "分类名称不能为空")
  18 + @Size(max = 50, message = "分类名称长度不能超过 50 个字符")
  19 + private String name;
  20 +
  21 + @Schema(description = "显示排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  22 + @NotNull(message = "显示排序不能为空")
  23 + private Integer sort;
  24 +
  25 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/discussion/OaDiscussionController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.discussion;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  7 +import cn.iocoder.yudao.module.oa.controller.admin.discussion.vo.discussion.OaDiscussionPageReqVO;
  8 +import cn.iocoder.yudao.module.oa.controller.admin.discussion.vo.discussion.OaDiscussionRespVO;
  9 +import cn.iocoder.yudao.module.oa.controller.admin.discussion.vo.discussion.OaDiscussionSaveReqVO;
  10 +import cn.iocoder.yudao.module.oa.controller.admin.discussion.vo.vote.OaVoteOptionRespVO;
  11 +import cn.iocoder.yudao.module.oa.dal.dataobject.discussion.OaDiscussionDO;
  12 +import cn.iocoder.yudao.module.oa.dal.dataobject.discussion.OaDiscussionLikeDO;
  13 +import cn.iocoder.yudao.module.oa.dal.dataobject.discussion.OaDiscussionReplyDO;
  14 +import cn.iocoder.yudao.module.oa.dal.dataobject.discussion.OaVoteOptionDO;
  15 +import cn.iocoder.yudao.module.oa.dal.dataobject.discussion.OaVoteRecordDO;
  16 +import cn.iocoder.yudao.module.oa.service.discussion.OaDiscussionService;
  17 +import cn.iocoder.yudao.module.oa.service.discussion.OaDiscussionReplyService;
  18 +import cn.iocoder.yudao.module.oa.service.discussion.OaDiscussionLikeService;
  19 +import cn.iocoder.yudao.module.oa.service.discussion.OaDiscussionVoteService;
  20 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  21 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  22 +import io.swagger.v3.oas.annotations.Operation;
  23 +import io.swagger.v3.oas.annotations.Parameter;
  24 +import io.swagger.v3.oas.annotations.tags.Tag;
  25 +import jakarta.annotation.Resource;
  26 +import jakarta.validation.Valid;
  27 +import org.springframework.security.access.prepost.PreAuthorize;
  28 +import org.springframework.validation.annotation.Validated;
  29 +import org.springframework.web.bind.annotation.DeleteMapping;
  30 +import org.springframework.web.bind.annotation.GetMapping;
  31 +import org.springframework.web.bind.annotation.PostMapping;
  32 +import org.springframework.web.bind.annotation.PutMapping;
  33 +import org.springframework.web.bind.annotation.RequestBody;
  34 +import org.springframework.web.bind.annotation.RequestMapping;
  35 +import org.springframework.web.bind.annotation.RequestParam;
  36 +import org.springframework.web.bind.annotation.RestController;
  37 +
  38 +import java.util.Collections;
  39 +import java.util.List;
  40 +import java.util.Map;
  41 +import java.util.Set;
  42 +
  43 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  44 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  45 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
  46 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  47 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  48 +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
  49 +
  50 +@Tag(name = "管理后台 - OA 讨论")
  51 +@RestController
  52 +@RequestMapping("/oa/discussion")
  53 +@Validated
  54 +public class OaDiscussionController {
  55 +
  56 + @Resource
  57 + private OaDiscussionService discussionService;
  58 + @Resource
  59 + private AdminUserApi adminUserApi;
  60 + @Resource
  61 + private OaDiscussionLikeService discussionLikeService;
  62 + @Resource
  63 + private OaDiscussionReplyService discussionReplyService;
  64 + @Resource
  65 + private OaDiscussionVoteService discussionVoteService;
  66 +
  67 + @PostMapping("/create")
  68 + @Operation(summary = "创建讨论")
  69 + @PreAuthorize("@ss.hasPermission('oa:discussion:create')")
  70 + public CommonResult<Long> createDiscussion(@Valid @RequestBody OaDiscussionSaveReqVO createReqVO) {
  71 + return success(discussionService.createDiscussion(createReqVO, getLoginUserId()));
  72 + }
  73 +
  74 + @PutMapping("/update")
  75 + @Operation(summary = "更新讨论")
  76 + @PreAuthorize("@ss.hasPermission('oa:discussion:update')")
  77 + public CommonResult<Boolean> updateDiscussion(@Valid @RequestBody OaDiscussionSaveReqVO updateReqVO) {
  78 + discussionService.updateDiscussion(updateReqVO, getLoginUserId());
  79 + return success(true);
  80 + }
  81 +
  82 + @DeleteMapping("/delete")
  83 + @Operation(summary = "删除讨论")
  84 + @Parameter(name = "id", description = "讨论编号", required = true, example = "1024")
  85 + @PreAuthorize("@ss.hasPermission('oa:discussion:delete')")
  86 + public CommonResult<Boolean> deleteDiscussion(@RequestParam("id") Long id) {
  87 + discussionService.deleteDiscussion(id, getLoginUserId());
  88 + return success(true);
  89 + }
  90 +
  91 + @GetMapping("/get")
  92 + @Operation(summary = "获得讨论详情")
  93 + @Parameter(name = "visit", description = "是否记录本次访问,默认不记录", example = "true")
  94 + @Parameter(name = "id", description = "讨论编号", required = true, example = "1024")
  95 + @PreAuthorize("@ss.hasPermission('oa:discussion:query')")
  96 + public CommonResult<OaDiscussionRespVO> getDiscussion(@RequestParam("id") Long id,
  97 + @RequestParam(value = "visit", defaultValue = "false") Boolean visit) {
  98 + // 1.1 查询讨论
  99 + OaDiscussionDO discussion = discussionService.validateDiscussionExists(id);
  100 + // 1.2 按需记录本次访问
  101 + if (Boolean.TRUE.equals(visit)) {
  102 + discussionService.increaseDiscussionVisitCount(id);
  103 + discussion.setVisitCount(discussion.getVisitCount() + 1);
  104 + }
  105 +
  106 + // 2. 拼接讨论详情
  107 + return success(buildDiscussionRespVO(discussion));
  108 + }
  109 +
  110 + @GetMapping("/page")
  111 + @Operation(summary = "获得讨论分页")
  112 + @PreAuthorize("@ss.hasPermission('oa:discussion:query')")
  113 + public CommonResult<PageResult<OaDiscussionRespVO>> getDiscussionPage(
  114 + @Valid OaDiscussionPageReqVO pageReqVO) {
  115 + PageResult<OaDiscussionDO> pageResult = discussionService.getDiscussionPage(pageReqVO);
  116 + return success(new PageResult<>(buildDiscussionRespVOList(pageResult.getList()), pageResult.getTotal()));
  117 + }
  118 +
  119 + @GetMapping("/manage-page")
  120 + @Operation(summary = "获得管理范围内的讨论分页")
  121 + @PreAuthorize("@ss.hasPermission('oa:discussion:query')")
  122 + public CommonResult<PageResult<OaDiscussionRespVO>> getDiscussionManagePage(
  123 + @Valid OaDiscussionPageReqVO pageReqVO) {
  124 + PageResult<OaDiscussionDO> pageResult = discussionService.getDiscussionManagePage(pageReqVO, getLoginUserId());
  125 + return success(new PageResult<>(buildDiscussionRespVOList(pageResult.getList()), pageResult.getTotal()));
  126 + }
  127 +
  128 + // ==================== 拼接 VO ====================
  129 +
  130 + /**
  131 + * 拼接讨论详情
  132 + *
  133 + * @param discussion 讨论
  134 + * @return 讨论响应
  135 + */
  136 + private OaDiscussionRespVO buildDiscussionRespVO(OaDiscussionDO discussion) {
  137 + if (discussion == null) {
  138 + return null;
  139 + }
  140 + return CollUtil.getFirst(buildDiscussionRespVOList(Collections.singletonList(discussion)));
  141 + }
  142 +
  143 + /**
  144 + * 拼接讨论响应列表
  145 + *
  146 + * @param discussions 讨论列表
  147 + * @return 讨论响应列表
  148 + */
  149 + private List<OaDiscussionRespVO> buildDiscussionRespVOList(List<OaDiscussionDO> discussions) {
  150 + if (CollUtil.isEmpty(discussions)) {
  151 + return Collections.emptyList();
  152 + }
  153 + // 1.1 批量查询回复和点赞
  154 + Set<Long> discussionIds = convertSet(discussions, OaDiscussionDO::getId);
  155 + Map<Long, List<OaDiscussionReplyDO>> replyListMap = convertMultiMap(
  156 + discussionReplyService.getDiscussionReplyList(discussionIds), OaDiscussionReplyDO::getDiscussionId);
  157 + List<OaDiscussionLikeDO> discussionLikes = discussionLikeService.getDiscussionLikeList(discussionIds);
  158 + Map<Long, List<OaDiscussionLikeDO>> likeListMap = convertMultiMap(discussionLikes,
  159 + OaDiscussionLikeDO::getDiscussionId);
  160 + // 1.2 批量查询投票选项和记录
  161 + Map<Long, List<OaVoteOptionDO>> voteOptionListMap = convertMultiMap(
  162 + discussionVoteService.getVoteOptionList(discussionIds), OaVoteOptionDO::getDiscussionId);
  163 + List<OaVoteRecordDO> voteRecords = discussionVoteService.getVoteRecordList(discussionIds);
  164 + Map<Long, List<OaVoteRecordDO>> voteRecordListMap = convertMultiMap(voteRecords,
  165 + OaVoteRecordDO::getDiscussionId);
  166 + // 1.3 批量查询发布人、点赞人和投票人
  167 + Set<Long> userIds = convertSet(discussions, OaDiscussionDO::getUserId);
  168 + userIds.addAll(convertSet(discussionLikes, OaDiscussionLikeDO::getUserId));
  169 + userIds.addAll(convertSet(voteRecords, OaVoteRecordDO::getUserId));
  170 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
  171 + Long loginUserId = getLoginUserId();
  172 + // 2. 转换并拼接讨论展示字段
  173 + return BeanUtils.toBean(discussions, OaDiscussionRespVO.class, discussion -> {
  174 + findAndThen(userMap, discussion.getUserId(), user -> discussion.setUserName(user.getNickname()));
  175 + discussion.setReplyCount(CollUtil.size(replyListMap.get(discussion.getId())));
  176 + List<OaDiscussionLikeDO> likes = likeListMap.getOrDefault(discussion.getId(), Collections.emptyList());
  177 + discussion.setLikeCount(likes.size());
  178 + discussion.setLiked(CollUtil.findOne(likes, like -> like.getUserId().equals(loginUserId)) != null);
  179 + discussion.setLikeUserNames(convertList(likes,
  180 + like -> getUserNickname(userMap, like.getUserId())));
  181 + discussion.setVoteOptions(buildVoteOptionRespVOList(
  182 + voteOptionListMap.getOrDefault(discussion.getId(), Collections.emptyList()),
  183 + voteRecordListMap.getOrDefault(discussion.getId(), Collections.emptyList()), userMap, loginUserId));
  184 + });
  185 + }
  186 +
  187 + /**
  188 + * 拼接投票选项响应列表
  189 + *
  190 + * @param voteOptions 投票选项列表
  191 + * @param voteRecords 投票记录列表
  192 + * @param userMap 用户 Map
  193 + * @param loginUserId 当前登录用户编号
  194 + * @return 投票选项响应列表
  195 + */
  196 + private List<OaVoteOptionRespVO> buildVoteOptionRespVOList(List<OaVoteOptionDO> voteOptions,
  197 + List<OaVoteRecordDO> voteRecords,
  198 + Map<Long, AdminUserRespDTO> userMap,
  199 + Long loginUserId) {
  200 + Map<Long, List<OaVoteRecordDO>> optionVoteRecordListMap = convertMultiMap(voteRecords,
  201 + OaVoteRecordDO::getOptionId);
  202 + return BeanUtils.toBean(voteOptions, OaVoteOptionRespVO.class, voteOption -> {
  203 + voteOption.setVoteCount(0).setVoted(false).setVoterUserNames(Collections.emptyList());
  204 + findAndThen(optionVoteRecordListMap, voteOption.getId(), records ->
  205 + voteOption.setVoteCount(records.size())
  206 + .setVoted(CollUtil.findOne(records, record -> record.getUserId().equals(loginUserId)) != null)
  207 + .setVoterUserNames(convertList(records, record -> getUserNickname(userMap, record.getUserId()))));
  208 + });
  209 + }
  210 +
  211 + /**
  212 + * 获得用户昵称
  213 + *
  214 + * @param userMap 用户 Map
  215 + * @param userId 用户编号
  216 + * @return 用户昵称
  217 + */
  218 + private static String getUserNickname(Map<Long, AdminUserRespDTO> userMap, Long userId) {
  219 + AdminUserRespDTO user = userMap.get(userId);
  220 + return user != null ? user.getNickname() : null;
  221 + }
  222 +
  223 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/discussion/OaDiscussionLikeController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.discussion;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.module.oa.controller.admin.discussion.vo.like.OaDiscussionLikeReqVO;
  5 +import cn.iocoder.yudao.module.oa.service.discussion.OaDiscussionLikeService;
  6 +import io.swagger.v3.oas.annotations.Operation;
  7 +import io.swagger.v3.oas.annotations.tags.Tag;
  8 +import jakarta.annotation.Resource;
  9 +import jakarta.validation.Valid;
  10 +import org.springframework.validation.annotation.Validated;
  11 +import org.springframework.web.bind.annotation.DeleteMapping;
  12 +import org.springframework.web.bind.annotation.PostMapping;
  13 +import org.springframework.web.bind.annotation.RequestBody;
  14 +import org.springframework.web.bind.annotation.RequestMapping;
  15 +import org.springframework.web.bind.annotation.RestController;
  16 +
  17 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  18 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  19 +
  20 +@Tag(name = "管理后台 - OA 讨论点赞")
  21 +@RestController
  22 +@RequestMapping("/oa/discussion-like")
  23 +@Validated
  24 +public class OaDiscussionLikeController {
  25 +
  26 + @Resource
  27 + private OaDiscussionLikeService discussionLikeService;
  28 +
  29 + @PostMapping("/create")
  30 + @Operation(summary = "点赞讨论或主回复")
  31 + public CommonResult<Boolean> createDiscussionLike(@Valid @RequestBody OaDiscussionLikeReqVO reqVO) {
  32 + discussionLikeService.createDiscussionLike(reqVO.getDiscussionId(), reqVO.getReplyId(), getLoginUserId());
  33 + return success(true);
  34 + }
  35 +
  36 + @DeleteMapping("/delete")
  37 + @Operation(summary = "取消点赞讨论或主回复")
  38 + public CommonResult<Boolean> deleteDiscussionLike(@Valid OaDiscussionLikeReqVO reqVO) {
  39 + discussionLikeService.deleteDiscussionLike(reqVO.getDiscussionId(), reqVO.getReplyId(), getLoginUserId());
  40 + return success(true);
  41 + }
  42 +
  43 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/discussion/OaDiscussionReplyController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.discussion;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.ObjectUtil;
  5 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  6 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  7 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  8 +import cn.iocoder.yudao.module.oa.controller.admin.discussion.vo.reply.OaDiscussionReplyPageReqVO;
  9 +import cn.iocoder.yudao.module.oa.controller.admin.discussion.vo.reply.OaDiscussionReplyRespVO;
  10 +import cn.iocoder.yudao.module.oa.controller.admin.discussion.vo.reply.OaDiscussionReplyCreateReqVO;
  11 +import cn.iocoder.yudao.module.oa.dal.dataobject.discussion.OaDiscussionLikeDO;
  12 +import cn.iocoder.yudao.module.oa.dal.dataobject.discussion.OaDiscussionReplyDO;
  13 +import cn.iocoder.yudao.module.oa.service.discussion.OaDiscussionReplyService;
  14 +import cn.iocoder.yudao.module.oa.service.discussion.OaDiscussionLikeService;
  15 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  16 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  17 +import io.swagger.v3.oas.annotations.Operation;
  18 +import io.swagger.v3.oas.annotations.Parameter;
  19 +import io.swagger.v3.oas.annotations.tags.Tag;
  20 +import jakarta.annotation.Resource;
  21 +import jakarta.validation.Valid;
  22 +import org.springframework.security.access.prepost.PreAuthorize;
  23 +import org.springframework.validation.annotation.Validated;
  24 +import org.springframework.web.bind.annotation.DeleteMapping;
  25 +import org.springframework.web.bind.annotation.GetMapping;
  26 +import org.springframework.web.bind.annotation.PostMapping;
  27 +import org.springframework.web.bind.annotation.RequestBody;
  28 +import org.springframework.web.bind.annotation.RequestMapping;
  29 +import org.springframework.web.bind.annotation.RequestParam;
  30 +import org.springframework.web.bind.annotation.RestController;
  31 +
  32 +import java.util.Collections;
  33 +import java.util.ArrayList;
  34 +import java.util.List;
  35 +import java.util.Map;
  36 +import java.util.Set;
  37 +import java.util.stream.Stream;
  38 +
  39 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  40 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  41 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
  42 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap;
  43 +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
  44 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  45 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  46 +
  47 +@Tag(name = "管理后台 - OA 讨论回复")
  48 +@RestController
  49 +@RequestMapping("/oa/discussion-reply")
  50 +@Validated
  51 +public class OaDiscussionReplyController {
  52 +
  53 + @Resource
  54 + private OaDiscussionReplyService discussionReplyService;
  55 + @Resource
  56 + private AdminUserApi adminUserApi;
  57 + @Resource
  58 + private OaDiscussionLikeService discussionLikeService;
  59 +
  60 + @PostMapping("/create")
  61 + @Operation(summary = "创建讨论回复")
  62 + public CommonResult<Long> createDiscussionReply(
  63 + @Valid @RequestBody OaDiscussionReplyCreateReqVO createReqVO) {
  64 + return success(discussionReplyService.createDiscussionReply(createReqVO, getLoginUserId()));
  65 + }
  66 +
  67 + @DeleteMapping("/delete")
  68 + @Operation(summary = "删除讨论回复")
  69 + @Parameter(name = "id", description = "回复编号", required = true, example = "1024")
  70 + public CommonResult<Boolean> deleteDiscussionReply(@RequestParam("id") Long id) {
  71 + discussionReplyService.deleteDiscussionReply(id, getLoginUserId());
  72 + return success(true);
  73 + }
  74 +
  75 + @GetMapping("/page")
  76 + @Operation(summary = "获得讨论回复分页")
  77 + @PreAuthorize("@ss.hasPermission('oa:discussion:query')")
  78 + public CommonResult<PageResult<OaDiscussionReplyRespVO>> getDiscussionReplyPage(
  79 + @Valid OaDiscussionReplyPageReqVO pageReqVO) {
  80 + PageResult<OaDiscussionReplyDO> pageResult = discussionReplyService.getDiscussionReplyPage(pageReqVO);
  81 + // 1. 批量查询当前页楼层内的子回复
  82 + List<OaDiscussionReplyDO> replies = new ArrayList<>(pageResult.getList());
  83 + replies.addAll(discussionReplyService.getDiscussionReplyDescendantList(pageResult.getList()));
  84 +
  85 + // 2. 批量拼接展示字段,再按照父回复关系组织楼层
  86 + List<OaDiscussionReplyRespVO> replyVOs = buildDiscussionReplyRespVOList(replies);
  87 + Map<Long, OaDiscussionReplyRespVO> replyMap = convertMap(replyVOs, OaDiscussionReplyRespVO::getId);
  88 + replyVOs.forEach(reply -> reply.setChildren(new ArrayList<>()));
  89 + replyVOs.forEach(reply -> findAndThen(replyMap, reply.getParentId(),
  90 + parent -> parent.getChildren().add(reply)));
  91 + return success(new PageResult<>(convertList(pageResult.getList(),
  92 + reply -> replyMap.get(reply.getId())), pageResult.getTotal()));
  93 + }
  94 +
  95 + // ==================== 拼接 VO ====================
  96 +
  97 + /**
  98 + * 拼接讨论回复响应列表
  99 + *
  100 + * @param replies 讨论回复列表
  101 + * @return 讨论回复响应列表
  102 + */
  103 + private List<OaDiscussionReplyRespVO> buildDiscussionReplyRespVOList(List<OaDiscussionReplyDO> replies) {
  104 + if (CollUtil.isEmpty(replies)) {
  105 + return Collections.emptyList();
  106 + }
  107 + // 1.1 批量查询回复点赞
  108 + Map<Long, List<OaDiscussionLikeDO>> replyLikeListMap = discussionLikeService.getReplyLikeListMap(
  109 + convertSet(replies, OaDiscussionReplyDO::getId,
  110 + reply -> ObjectUtil.equal(reply.getParentId(), OaDiscussionReplyDO.PARENT_ID_ROOT)));
  111 + // 1.2 批量查询回复人、被回复人和点赞人
  112 + Set<Long> userIds = convertSetByFlatMap(replies,
  113 + reply -> Stream.of(reply.getUserId(), reply.getReplyUserId()));
  114 + userIds.addAll(convertSetByFlatMap(replyLikeListMap.values(),
  115 + likes -> likes.stream().map(OaDiscussionLikeDO::getUserId)));
  116 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
  117 + Long loginUserId = getLoginUserId();
  118 +
  119 + // 2. 转换并拼接回复展示字段
  120 + return BeanUtils.toBean(replies, OaDiscussionReplyRespVO.class, reply -> {
  121 + findAndThen(userMap, reply.getUserId(), user -> reply.setUserName(user.getNickname()));
  122 + findAndThen(userMap, reply.getReplyUserId(), user -> reply.setReplyUserName(user.getNickname()));
  123 + List<OaDiscussionLikeDO> likes = replyLikeListMap.getOrDefault(reply.getId(), Collections.emptyList());
  124 + reply.setLikeCount(likes.size())
  125 + .setLiked(CollUtil.findOne(likes, like -> like.getUserId().equals(loginUserId)) != null)
  126 + .setLikeUserNames(convertList(likes, like -> {
  127 + AdminUserRespDTO user = userMap.get(like.getUserId());
  128 + return user != null ? user.getNickname() : null;
  129 + }));
  130 + });
  131 + }
  132 +
  133 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/discussion/OaDiscussionVoteController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.discussion;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.module.oa.controller.admin.discussion.vo.vote.OaDiscussionVoteReqVO;
  5 +import cn.iocoder.yudao.module.oa.service.discussion.OaDiscussionVoteService;
  6 +import io.swagger.v3.oas.annotations.Operation;
  7 +import io.swagger.v3.oas.annotations.tags.Tag;
  8 +import jakarta.annotation.Resource;
  9 +import jakarta.validation.Valid;
  10 +import org.springframework.validation.annotation.Validated;
  11 +import org.springframework.web.bind.annotation.PostMapping;
  12 +import org.springframework.web.bind.annotation.RequestBody;
  13 +import org.springframework.web.bind.annotation.RequestMapping;
  14 +import org.springframework.web.bind.annotation.RestController;
  15 +
  16 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  17 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  18 +
  19 +@Tag(name = "管理后台 - OA 讨论投票")
  20 +@RestController
  21 +@RequestMapping("/oa/discussion-vote")
  22 +@Validated
  23 +public class OaDiscussionVoteController {
  24 +
  25 + @Resource
  26 + private OaDiscussionVoteService discussionVoteService;
  27 +
  28 + @PostMapping("/create")
  29 + @Operation(summary = "参与讨论投票")
  30 + public CommonResult<Boolean> voteDiscussion(@Valid @RequestBody OaDiscussionVoteReqVO voteReqVO) {
  31 + discussionVoteService.voteDiscussion(voteReqVO.getDiscussionId(), voteReqVO.getOptionIds(), getLoginUserId());
  32 + return success(true);
  33 + }
  34 +
  35 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/discussion/vo/discussion/OaDiscussionPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.discussion.vo.discussion;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.SortablePageParam;
  4 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  5 +import cn.iocoder.yudao.module.oa.enums.discussion.OaDiscussionTypeEnum;
  6 +import io.swagger.v3.oas.annotations.media.Schema;
  7 +import lombok.Data;
  8 +import lombok.EqualsAndHashCode;
  9 +import org.springframework.format.annotation.DateTimeFormat;
  10 +
  11 +import java.time.LocalDateTime;
  12 +
  13 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  14 +
  15 +@Schema(description = "管理后台 - OA 讨论分页 Request VO")
  16 +@Data
  17 +@EqualsAndHashCode(callSuper = true)
  18 +public class OaDiscussionPageReqVO extends SortablePageParam {
  19 +
  20 + @Schema(description = "标题", example = "产品方案讨论")
  21 + private String title;
  22 +
  23 + @Schema(description = "讨论类型", example = "1")
  24 + @InEnum(value = OaDiscussionTypeEnum.class, message = "讨论类型必须是 {value}")
  25 + private Integer type;
  26 +
  27 + @Schema(description = "发布人用户编号", example = "1")
  28 + private Long userId;
  29 +
  30 + @Schema(description = "创建时间范围", example = "[\"2026-09-14 00:00:00\", \"2026-09-14 23:59:59\"]")
  31 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  32 + private LocalDateTime[] createTime;
  33 +
  34 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/discussion/vo/discussion/OaDiscussionRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.discussion.vo.discussion;
  2 +
  3 +import cn.iocoder.yudao.module.oa.controller.admin.discussion.vo.vote.OaVoteOptionRespVO;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +
  7 +import java.time.LocalDateTime;
  8 +import java.util.List;
  9 +
  10 +@Schema(description = "管理后台 - OA 讨论 Response VO")
  11 +@Data
  12 +public class OaDiscussionRespVO {
  13 +
  14 + @Schema(description = "讨论编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  15 + private Long id;
  16 +
  17 + @Schema(description = "发布人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  18 + private Long userId;
  19 +
  20 + @Schema(description = "发布人用户昵称", example = "芋道源码")
  21 + private String userName;
  22 +
  23 + @Schema(description = "讨论类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  24 + private Integer type;
  25 +
  26 + @Schema(description = "标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "下周团队会议时间征集")
  27 + private String title;
  28 +
  29 + @Schema(description = "内容", example = "请大家反馈下周团队会议的时间安排。")
  30 + private String content;
  31 +
  32 + @Schema(description = "附件地址列表", example = "[\"https://example.com/file.pdf\"]")
  33 + private List<String> fileUrls;
  34 +
  35 + @Schema(description = "访问次数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  36 + private Integer visitCount;
  37 +
  38 + @Schema(description = "回复数", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
  39 + private Integer replyCount;
  40 +
  41 + @Schema(description = "点赞数", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
  42 + private Integer likeCount;
  43 +
  44 + @Schema(description = "当前用户是否已点赞", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
  45 + private Boolean liked;
  46 +
  47 + @Schema(description = "点赞人用户昵称列表", example = "[\"张三\", \"李四\"]")
  48 + private List<String> likeUserNames;
  49 +
  50 + @Schema(description = "投票是否允许多选", example = "false")
  51 + private Boolean voteMultiple;
  52 +
  53 + @Schema(description = "投票开始时间", type = "integer", format = "int64", example = "1789347600000")
  54 + private LocalDateTime voteStartTime;
  55 +
  56 + @Schema(description = "投票结束时间", type = "integer", format = "int64", example = "1789380000000")
  57 + private LocalDateTime voteEndTime;
  58 +
  59 + @Schema(description = "投票选项列表")
  60 + private List<OaVoteOptionRespVO> voteOptions;
  61 +
  62 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, type = "integer", format = "int64", example = "1789347600000")
  63 + private LocalDateTime createTime;
  64 +
  65 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/discussion/vo/discussion/OaDiscussionSaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.discussion.vo.discussion;
  2 +
  3 +import cn.iocoder.yudao.module.oa.controller.admin.discussion.vo.vote.OaVoteOptionSaveReqVO;
  4 +import cn.hutool.core.collection.CollUtil;
  5 +import cn.hutool.core.util.ObjectUtil;
  6 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  7 +import cn.iocoder.yudao.module.oa.enums.discussion.OaDiscussionTypeEnum;
  8 +import com.fasterxml.jackson.annotation.JsonIgnore;
  9 +import io.swagger.v3.oas.annotations.media.Schema;
  10 +import jakarta.validation.Valid;
  11 +import jakarta.validation.constraints.AssertTrue;
  12 +import jakarta.validation.constraints.NotBlank;
  13 +import jakarta.validation.constraints.NotNull;
  14 +import jakarta.validation.constraints.Size;
  15 +import lombok.Data;
  16 +
  17 +import java.time.LocalDateTime;
  18 +import java.util.List;
  19 +
  20 +@Schema(description = "管理后台 - OA 讨论新增/修改 Request VO")
  21 +@Data
  22 +public class OaDiscussionSaveReqVO {
  23 +
  24 + @Schema(description = "讨论编号", example = "1024")
  25 + private Long id;
  26 +
  27 + @Schema(description = "讨论类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  28 + @NotNull(message = "讨论类型不能为空")
  29 + @InEnum(value = OaDiscussionTypeEnum.class, message = "讨论类型必须是 {value}")
  30 + private Integer type;
  31 +
  32 + @Schema(description = "标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "下周团队会议时间征集")
  33 + @NotBlank(message = "标题不能为空")
  34 + @Size(max = 255, message = "标题长度不能超过 255 个字符")
  35 + private String title;
  36 +
  37 + @Schema(description = "内容", example = "请大家反馈下周团队会议的时间安排。")
  38 + private String content;
  39 +
  40 + @Schema(description = "附件地址列表", example = "[\"https://example.com/file.pdf\"]")
  41 + private List<@NotBlank(message = "附件地址不能为空") @Size(max = 512, message = "附件地址长度不能超过 512 个字符") String> fileUrls;
  42 +
  43 + @Schema(description = "投票是否允许多选", example = "false")
  44 + private Boolean voteMultiple;
  45 +
  46 + @Schema(description = "投票开始时间", type = "integer", format = "int64", example = "1789347600000")
  47 + private LocalDateTime voteStartTime;
  48 +
  49 + @Schema(description = "投票结束时间", type = "integer", format = "int64", example = "1789380000000")
  50 + private LocalDateTime voteEndTime;
  51 +
  52 + @Schema(description = "投票选项列表")
  53 + @Valid
  54 + private List<@NotNull(message = "投票选项不能为空") OaVoteOptionSaveReqVO> voteOptions;
  55 +
  56 + @JsonIgnore
  57 + @AssertTrue(message = "投票至少需要两个选项,并设置正确的开始和结束时间")
  58 + @Schema(hidden = true)
  59 + public boolean isVoteConfigValid() {
  60 + if (ObjectUtil.notEqual(OaDiscussionTypeEnum.VOTE.getType(), type)) {
  61 + return true;
  62 + }
  63 + if (voteMultiple == null || voteStartTime == null || voteEndTime == null) {
  64 + return false;
  65 + }
  66 + return voteEndTime.isAfter(voteStartTime) && CollUtil.size(voteOptions) >= 2;
  67 + }
  68 +
  69 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/discussion/vo/like/OaDiscussionLikeReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.discussion.vo.like;
  2 +
  3 +import com.fasterxml.jackson.annotation.JsonIgnore;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import jakarta.validation.constraints.AssertTrue;
  6 +import lombok.Data;
  7 +
  8 +@Schema(description = "管理后台 - OA 讨论点赞 Request VO")
  9 +@Data
  10 +public class OaDiscussionLikeReqVO {
  11 +
  12 + @Schema(description = "讨论编号,与回复编号必须且只能填写一个", example = "1024")
  13 + private Long discussionId;
  14 +
  15 + @Schema(description = "回复编号,与讨论编号必须且只能填写一个", example = "2048")
  16 + private Long replyId;
  17 +
  18 + @JsonIgnore
  19 + @AssertTrue(message = "讨论编号和回复编号必须且只能填写一个")
  20 + @Schema(hidden = true)
  21 + public boolean isTargetValid() {
  22 + if (discussionId != null) {
  23 + return replyId == null;
  24 + }
  25 + return replyId != null;
  26 + }
  27 +
  28 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/discussion/vo/reply/OaDiscussionReplyCreateReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.discussion.vo.reply;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotBlank;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import jakarta.validation.constraints.AssertTrue;
  7 +import com.fasterxml.jackson.annotation.JsonIgnore;
  8 +import lombok.Data;
  9 +
  10 +@Schema(description = "管理后台 - OA 讨论回复新增 Request VO")
  11 +@Data
  12 +public class OaDiscussionReplyCreateReqVO {
  13 +
  14 + @Schema(description = "讨论编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  15 + @NotNull(message = "讨论编号不能为空")
  16 + private Long discussionId;
  17 +
  18 + @Schema(description = "父回复编号", example = "0")
  19 + private Long parentId;
  20 +
  21 + @Schema(description = "回复内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "已收到,将按时完成。")
  22 + @NotBlank(message = "回复内容不能为空")
  23 + private String content;
  24 +
  25 + /**
  26 + * 楼内评论对齐源端 255 字限制,根回复使用 TEXT 正文。
  27 + *
  28 + * @return 评论长度是否合法
  29 + */
  30 + @AssertTrue(message = "楼内评论不能超过 255 个字符")
  31 + @JsonIgnore
  32 + public boolean isCommentLengthValid() {
  33 + return parentId == null || parentId == 0L || content == null || content.length() <= 255;
  34 + }
  35 +
  36 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/discussion/vo/reply/OaDiscussionReplyPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.discussion.vo.reply;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.SortablePageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import lombok.Data;
  7 +import lombok.EqualsAndHashCode;
  8 +
  9 +@Schema(description = "管理后台 - OA 讨论回复分页 Request VO")
  10 +@Data
  11 +@EqualsAndHashCode(callSuper = true)
  12 +public class OaDiscussionReplyPageReqVO extends SortablePageParam {
  13 +
  14 + @Schema(description = "讨论编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  15 + @NotNull(message = "讨论编号不能为空")
  16 + private Long discussionId;
  17 +
  18 + @Schema(description = "回复人用户编号", example = "1")
  19 + private Long userId;
  20 +
  21 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/discussion/vo/reply/OaDiscussionReplyRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.discussion.vo.reply;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +import java.util.List;
  8 +
  9 +@Schema(description = "管理后台 - OA 讨论回复 Response VO")
  10 +@Data
  11 +public class OaDiscussionReplyRespVO {
  12 +
  13 + @Schema(description = "回复编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  14 + private Long id;
  15 +
  16 + @Schema(description = "讨论编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
  17 + private Long discussionId;
  18 +
  19 + @Schema(description = "回复人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  20 + private Long userId;
  21 +
  22 + @Schema(description = "回复人用户昵称", example = "芋道源码")
  23 + private String userName;
  24 +
  25 + @Schema(description = "父回复编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  26 + private Long parentId;
  27 +
  28 + @Schema(description = "被回复人用户编号", example = "2")
  29 + private Long replyUserId;
  30 +
  31 + @Schema(description = "被回复人用户昵称", example = "管理员")
  32 + private String replyUserName;
  33 +
  34 + @Schema(description = "回复内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "已收到,将按时完成。")
  35 + private String content;
  36 +
  37 + @Schema(description = "点赞数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  38 + private Integer likeCount;
  39 +
  40 + @Schema(description = "当前用户是否已点赞", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
  41 + private Boolean liked;
  42 +
  43 + @Schema(description = "点赞人用户昵称列表", example = "[\"张三\", \"李四\"]")
  44 + private List<String> likeUserNames;
  45 +
  46 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, type = "integer", format = "int64", example = "1789347600000")
  47 + private LocalDateTime createTime;
  48 +
  49 + @Schema(description = "楼层内的子回复")
  50 + private List<OaDiscussionReplyRespVO> children;
  51 +
  52 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/discussion/vo/vote/OaDiscussionVoteReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.discussion.vo.vote;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotEmpty;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import lombok.Data;
  7 +
  8 +import java.util.List;
  9 +
  10 +@Schema(description = "管理后台 - OA 讨论投票 Request VO")
  11 +@Data
  12 +public class OaDiscussionVoteReqVO {
  13 +
  14 + @Schema(description = "讨论编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  15 + @NotNull(message = "讨论编号不能为空")
  16 + private Long discussionId;
  17 +
  18 + @Schema(description = "投票选项编号列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 2]")
  19 + @NotEmpty(message = "投票选项不能为空")
  20 + private List<@NotNull(message = "投票选项编号不能为空") Long> optionIds;
  21 +
  22 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/discussion/vo/vote/OaVoteOptionRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.discussion.vo.vote;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.util.List;
  7 +
  8 +@Schema(description = "管理后台 - OA 投票选项 Response VO")
  9 +@Data
  10 +public class OaVoteOptionRespVO {
  11 +
  12 + @Schema(description = "投票选项编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  13 + private Long id;
  14 +
  15 + @Schema(description = "选项标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "周五下午")
  16 + private String title;
  17 +
  18 + @Schema(description = "选项颜色", example = "#409EFF")
  19 + private String color;
  20 +
  21 + @Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  22 + private Integer sort;
  23 +
  24 + @Schema(description = "投票数", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
  25 + private Integer voteCount;
  26 +
  27 + @Schema(description = "当前用户是否已选择", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
  28 + private Boolean voted;
  29 +
  30 + @Schema(description = "投票人用户昵称列表", example = "[\"张三\", \"李四\"]")
  31 + private List<String> voterUserNames;
  32 +
  33 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/discussion/vo/vote/OaVoteOptionSaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.discussion.vo.vote;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotBlank;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import jakarta.validation.constraints.Size;
  7 +import lombok.Data;
  8 +
  9 +@Schema(description = "管理后台 - OA 投票选项新增/修改 Request VO")
  10 +@Data
  11 +public class OaVoteOptionSaveReqVO {
  12 +
  13 + @Schema(description = "选项标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "周五下午")
  14 + @NotBlank(message = "投票选项标题不能为空")
  15 + @Size(max = 200, message = "投票选项标题长度不能超过 200 个字符")
  16 + private String title;
  17 +
  18 + @Schema(description = "选项颜色", example = "#409EFF")
  19 + @Size(max = 20, message = "投票选项颜色长度不能超过 20 个字符")
  20 + private String color;
  21 +
  22 + @Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  23 + @NotNull(message = "显示顺序不能为空")
  24 + private Integer sort;
  25 +
  26 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/file/OaFileFavoriteController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.file;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.module.oa.service.file.OaFileFavoriteService;
  5 +import io.swagger.v3.oas.annotations.Operation;
  6 +import io.swagger.v3.oas.annotations.Parameter;
  7 +import io.swagger.v3.oas.annotations.tags.Tag;
  8 +import jakarta.annotation.Resource;
  9 +import org.springframework.security.access.prepost.PreAuthorize;
  10 +import org.springframework.validation.annotation.Validated;
  11 +import org.springframework.web.bind.annotation.*;
  12 +
  13 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  14 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  15 +
  16 +@Tag(name = "管理后台 - 云盘收藏")
  17 +@RestController
  18 +@RequestMapping("/oa/file-favorite")
  19 +@Validated
  20 +public class OaFileFavoriteController {
  21 +
  22 + @Resource
  23 + private OaFileFavoriteService fileFavoriteService;
  24 +
  25 + @PostMapping("/create")
  26 + @Operation(summary = "收藏文件")
  27 + @Parameter(name = "nodeId", description = "节点编号", required = true, example = "1024")
  28 + @PreAuthorize("@ss.hasPermission('oa:file:query')")
  29 + public CommonResult<Boolean> createFileFavorite(@RequestParam("nodeId") Long nodeId) {
  30 + fileFavoriteService.createFileFavorite(nodeId, getLoginUserId());
  31 + return success(true);
  32 + }
  33 +
  34 + @DeleteMapping("/delete")
  35 + @Operation(summary = "取消收藏文件")
  36 + @Parameter(name = "nodeId", description = "节点编号", required = true, example = "1024")
  37 + @PreAuthorize("@ss.hasPermission('oa:file:query')")
  38 + public CommonResult<Boolean> deleteFileFavorite(@RequestParam("nodeId") Long nodeId) {
  39 + fileFavoriteService.deleteFileFavorite(nodeId, getLoginUserId());
  40 + return success(true);
  41 + }
  42 +
  43 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/file/OaFileNodeController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.file;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.module.oa.controller.admin.file.vo.node.*;
  6 +import cn.iocoder.yudao.module.oa.service.file.OaFileNodeService;
  7 +import io.swagger.v3.oas.annotations.Operation;
  8 +import io.swagger.v3.oas.annotations.Parameter;
  9 +import io.swagger.v3.oas.annotations.tags.Tag;
  10 +import jakarta.annotation.Resource;
  11 +import jakarta.validation.Valid;
  12 +import org.springframework.security.access.prepost.PreAuthorize;
  13 +import org.springframework.validation.annotation.Validated;
  14 +import org.springframework.web.bind.annotation.*;
  15 +
  16 +import java.util.List;
  17 +
  18 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  19 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  20 +
  21 +@Tag(name = "管理后台 - 云盘文件")
  22 +@RestController
  23 +@RequestMapping("/oa/file-node")
  24 +@Validated
  25 +public class OaFileNodeController {
  26 +
  27 + @Resource
  28 + private OaFileNodeService fileNodeService;
  29 +
  30 + @GetMapping("/get-storage")
  31 + @Operation(summary = "获得本人云盘空间")
  32 + @PreAuthorize("@ss.hasPermission('oa:file:query')")
  33 + public CommonResult<OaFileStorageRespVO> getFileStorage() {
  34 + return success(fileNodeService.getFileStorage(getLoginUserId()));
  35 + }
  36 +
  37 + @GetMapping("/page")
  38 + @Operation(summary = "获得云盘文件分页")
  39 + @PreAuthorize("@ss.hasPermission('oa:file:query')")
  40 + public CommonResult<PageResult<OaFileNodeRespVO>> getFileNodePage(@Valid OaFileNodePageReqVO pageReqVO) {
  41 + return success(fileNodeService.getFileNodePage(pageReqVO, getLoginUserId()));
  42 + }
  43 +
  44 + @GetMapping("/directory-list")
  45 + @Operation(summary = "获得本人可用目录列表")
  46 + @PreAuthorize("@ss.hasPermission('oa:file:query')")
  47 + public CommonResult<List<OaFileNodeRespVO>> getFileDirectoryList() {
  48 + return success(fileNodeService.getFileDirectoryList(getLoginUserId()));
  49 + }
  50 +
  51 + @PostMapping("/create")
  52 + @Operation(summary = "创建云盘文件或目录")
  53 + @PreAuthorize("@ss.hasPermission('oa:file:create')")
  54 + public CommonResult<Long> createFileNode(@Valid @RequestBody OaFileNodeSaveReqVO reqVO) {
  55 + return success(fileNodeService.createFileNode(reqVO, getLoginUserId()));
  56 + }
  57 +
  58 + @PutMapping("/update-name")
  59 + @Operation(summary = "重命名云盘文件")
  60 + @PreAuthorize("@ss.hasPermission('oa:file:update')")
  61 + public CommonResult<Boolean> updateFileNodeName(@Valid @RequestBody OaFileNodeRenameReqVO reqVO) {
  62 + fileNodeService.updateFileNodeName(reqVO, getLoginUserId());
  63 + return success(true);
  64 + }
  65 +
  66 + @PutMapping("/update-parent")
  67 + @Operation(summary = "移动云盘文件")
  68 + @PreAuthorize("@ss.hasPermission('oa:file:update')")
  69 + public CommonResult<Boolean> updateFileNodeParent(@Valid @RequestBody OaFileNodeMoveReqVO reqVO) {
  70 + fileNodeService.updateFileNodeParent(reqVO, getLoginUserId());
  71 + return success(true);
  72 + }
  73 +
  74 + @PostMapping("/copy")
  75 + @Operation(summary = "复制云盘文件或目录")
  76 + @PreAuthorize("@ss.hasPermission('oa:file:create')")
  77 + public CommonResult<Long> copyFileNode(@Valid @RequestBody OaFileNodeCopyReqVO reqVO) {
  78 + return success(fileNodeService.copyFileNode(reqVO, getLoginUserId()));
  79 + }
  80 +
  81 + @PutMapping("/recycle")
  82 + @Operation(summary = "移入回收站")
  83 + @Parameter(name = "id", description = "节点编号", required = true, example = "1024")
  84 + @PreAuthorize("@ss.hasPermission('oa:file:delete')")
  85 + public CommonResult<Boolean> recycleFileNode(@RequestParam("id") Long id) {
  86 + fileNodeService.recycleFileNode(id, getLoginUserId());
  87 + return success(true);
  88 + }
  89 +
  90 + @PutMapping("/restore")
  91 + @Operation(summary = "恢复回收站文件")
  92 + @Parameter(name = "id", description = "节点编号", required = true, example = "1024")
  93 + @PreAuthorize("@ss.hasPermission('oa:file:delete')")
  94 + public CommonResult<Boolean> restoreFileNode(@RequestParam("id") Long id) {
  95 + fileNodeService.restoreFileNode(id, getLoginUserId());
  96 + return success(true);
  97 + }
  98 +
  99 + @DeleteMapping("/delete")
  100 + @Operation(summary = "彻底删除回收站文件")
  101 + @Parameter(name = "id", description = "节点编号", required = true, example = "1024")
  102 + @PreAuthorize("@ss.hasPermission('oa:file:delete')")
  103 + public CommonResult<Boolean> deleteFileNode(@RequestParam("id") Long id) {
  104 + fileNodeService.deleteFileNode(id, getLoginUserId());
  105 + return success(true);
  106 + }
  107 +
  108 + @GetMapping("/get")
  109 + @Operation(summary = "获得文件详情")
  110 + @Parameter(name = "id", description = "节点编号", required = true, example = "1024")
  111 + @PreAuthorize("@ss.hasPermission('oa:file:query')")
  112 + public CommonResult<OaFileNodeRespVO> getFileNode(@RequestParam("id") Long id) {
  113 + return success(fileNodeService.getFileNode(id, getLoginUserId()));
  114 + }
  115 +
  116 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/file/OaFilePermissionController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.file;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  5 +import cn.iocoder.yudao.module.oa.controller.admin.file.vo.permission.OaFilePermissionRespVO;
  6 +import cn.iocoder.yudao.module.oa.controller.admin.file.vo.permission.OaFilePermissionSaveReqVO;
  7 +import cn.iocoder.yudao.module.oa.dal.dataobject.file.OaFilePermissionDO;
  8 +import cn.iocoder.yudao.module.oa.service.file.OaFilePermissionService;
  9 +import io.swagger.v3.oas.annotations.Operation;
  10 +import io.swagger.v3.oas.annotations.Parameter;
  11 +import io.swagger.v3.oas.annotations.tags.Tag;
  12 +import jakarta.annotation.Resource;
  13 +import jakarta.validation.Valid;
  14 +import org.springframework.security.access.prepost.PreAuthorize;
  15 +import org.springframework.validation.annotation.Validated;
  16 +import org.springframework.web.bind.annotation.*;
  17 +
  18 +import java.util.List;
  19 +
  20 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  21 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  22 +
  23 +@Tag(name = "管理后台 - 云盘共享权限")
  24 +@RestController
  25 +@RequestMapping("/oa/file-permission")
  26 +@Validated
  27 +public class OaFilePermissionController {
  28 +
  29 + @Resource
  30 + private OaFilePermissionService filePermissionService;
  31 +
  32 + @GetMapping("/list")
  33 + @Operation(summary = "获得文件共享权限")
  34 + @Parameter(name = "nodeId", description = "节点编号", required = true, example = "1024")
  35 + @PreAuthorize("@ss.hasPermission('oa:file:share')")
  36 + public CommonResult<List<OaFilePermissionRespVO>> getFilePermissionList(@RequestParam("nodeId") Long nodeId) {
  37 + List<OaFilePermissionDO> permissions = filePermissionService.getFilePermissionList(nodeId, getLoginUserId());
  38 + return success(BeanUtils.toBean(permissions, OaFilePermissionRespVO.class));
  39 + }
  40 +
  41 + @PostMapping("/save")
  42 + @Operation(summary = "保存文件共享权限")
  43 + @PreAuthorize("@ss.hasPermission('oa:file:share')")
  44 + public CommonResult<Long> saveFilePermission(@Valid @RequestBody OaFilePermissionSaveReqVO reqVO) {
  45 + return success(filePermissionService.saveFilePermission(reqVO, getLoginUserId()));
  46 + }
  47 +
  48 + @DeleteMapping("/delete")
  49 + @Operation(summary = "取消文件共享权限")
  50 + @Parameter(name = "id", description = "权限编号", required = true, example = "1024")
  51 + @PreAuthorize("@ss.hasPermission('oa:file:share')")
  52 + public CommonResult<Boolean> deleteFilePermission(@RequestParam("id") Long id) {
  53 + filePermissionService.deleteFilePermission(id, getLoginUserId());
  54 + return success(true);
  55 + }
  56 +
  57 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/file/vo/node/OaFileNodeCopyReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.file.vo.node;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotNull;
  5 +import lombok.Data;
  6 +
  7 +@Schema(description = "管理后台 - 云盘复制 Request VO")
  8 +@Data
  9 +public class OaFileNodeCopyReqVO {
  10 +
  11 + @Schema(description = "节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  12 + @NotNull(message = "节点编号不能为空")
  13 + private Long id;
  14 +
  15 + @Schema(description = "目标目录编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  16 + @NotNull(message = "目标目录编号不能为空")
  17 + private Long parentId;
  18 +
  19 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/file/vo/node/OaFileNodeMoveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.file.vo.node;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotNull;
  5 +import lombok.Data;
  6 +
  7 +@Schema(description = "管理后台 - 云盘移动 Request VO")
  8 +@Data
  9 +public class OaFileNodeMoveReqVO {
  10 +
  11 + @Schema(description = "节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  12 + @NotNull(message = "节点编号不能为空")
  13 + private Long id;
  14 +
  15 + @Schema(description = "目标目录编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  16 + @NotNull(message = "目标目录编号不能为空")
  17 + private Long parentId;
  18 +
  19 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/file/vo/node/OaFileNodePageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.file.vo.node;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  5 +import cn.iocoder.yudao.module.oa.enums.file.OaFileCategoryEnum;
  6 +import cn.iocoder.yudao.module.oa.enums.file.OaFileScopeEnum;
  7 +import io.swagger.v3.oas.annotations.media.Schema;
  8 +import jakarta.validation.constraints.*;
  9 +import lombok.Data;
  10 +import lombok.EqualsAndHashCode;
  11 +import org.springframework.format.annotation.DateTimeFormat;
  12 +
  13 +import java.time.LocalDateTime;
  14 +
  15 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  16 +
  17 +@Schema(description = "管理后台 - 云盘文件分页 Request VO")
  18 +@Data
  19 +@EqualsAndHashCode(callSuper = true)
  20 +public class OaFileNodePageReqVO extends PageParam {
  21 +
  22 + @Schema(description = "列表范围:my、shared、favorite、recycle", requiredMode = Schema.RequiredMode.REQUIRED, example = "my")
  23 + @NotBlank(message = "列表范围不能为空")
  24 + @InEnum(value = OaFileScopeEnum.class, message = "列表范围必须是 {value}")
  25 + private String scope;
  26 +
  27 + @Schema(description = "父目录编号,不传时查询范围内全部文件", example = "1024")
  28 + private Long parentId;
  29 +
  30 + @Schema(description = "名称", example = "办公用品盘点结果.pdf")
  31 + @Size(max = 255, message = "名称长度不能超过 255 个字符")
  32 + private String name;
  33 +
  34 + @Schema(description = "文件分类", example = "1")
  35 + @InEnum(value = OaFileCategoryEnum.class, message = "文件分类必须是 {value}")
  36 + private Integer category;
  37 +
  38 + @Schema(description = "创建时间范围", example = "[\"2026-09-14 00:00:00\", \"2026-09-14 23:59:59\"]")
  39 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  40 + private LocalDateTime[] createTime;
  41 +
  42 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/file/vo/node/OaFileNodeRenameReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.file.vo.node;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.*;
  5 +import lombok.Data;
  6 +
  7 +@Schema(description = "管理后台 - 云盘重命名 Request VO")
  8 +@Data
  9 +public class OaFileNodeRenameReqVO {
  10 +
  11 + @Schema(description = "节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  12 + @NotNull(message = "节点编号不能为空")
  13 + private Long id;
  14 +
  15 + @Schema(description = "名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "办公用品盘点结果.pdf")
  16 + @NotBlank(message = "名称不能为空")
  17 + @Size(max = 255, message = "名称长度不能超过 255 个字符")
  18 + private String name;
  19 +
  20 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/file/vo/node/OaFileNodeRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.file.vo.node;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +@Schema(description = "管理后台 - 云盘文件 Response VO")
  9 +@Data
  10 +public class OaFileNodeRespVO {
  11 +
  12 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  13 + private Long id;
  14 +
  15 + @Schema(description = "父目录编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  16 + private Long parentId;
  17 +
  18 + @Schema(description = "节点类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  19 + private Integer type;
  20 +
  21 + @Schema(description = "名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "办公用品盘点结果.pdf")
  22 + private String name;
  23 +
  24 + @Schema(description = "临时文件地址,仅详情中具有下载权限时返回", example = "https://example.com/file.pdf")
  25 + private String url;
  26 +
  27 + @Schema(description = "扩展名", example = "pdf")
  28 + private String extension;
  29 +
  30 + @Schema(description = "文件分类", example = "1")
  31 + private Integer category;
  32 +
  33 + @Schema(description = "大小,单位字节", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  34 + private Long size;
  35 +
  36 + @Schema(description = "节点状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  37 + private Integer status;
  38 +
  39 + @Schema(description = "创建人", example = "1024")
  40 + private String creator;
  41 +
  42 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, type = "integer", format = "int64", example = "1789347600000")
  43 + private LocalDateTime createTime;
  44 +
  45 + @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED, type = "integer", format = "int64", example = "1789347600000")
  46 + private LocalDateTime updateTime;
  47 +
  48 + @Schema(description = "当前用户权限级别", example = "1")
  49 + private Integer level;
  50 +
  51 + @Schema(description = "是否收藏", example = "false")
  52 + private Boolean favorite;
  53 +
  54 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/file/vo/node/OaFileNodeSaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.file.vo.node;
  2 +
  3 +import cn.hutool.core.util.ObjUtil;
  4 +import cn.hutool.core.util.StrUtil;
  5 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  6 +import cn.iocoder.yudao.module.oa.enums.file.OaFileNodeTypeEnum;
  7 +import com.fasterxml.jackson.annotation.JsonIgnore;
  8 +import io.swagger.v3.oas.annotations.media.Schema;
  9 +import jakarta.validation.constraints.*;
  10 +import lombok.Data;
  11 +
  12 +@Schema(description = "管理后台 - 云盘文件新增 Request VO")
  13 +@Data
  14 +public class OaFileNodeSaveReqVO {
  15 +
  16 + @Schema(description = "父目录编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  17 + @NotNull(message = "父目录编号不能为空")
  18 + private Long parentId;
  19 +
  20 + @Schema(description = "节点类型:0 文件夹、1 文件", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  21 + @NotNull(message = "节点类型不能为空")
  22 + @InEnum(value = OaFileNodeTypeEnum.class, message = "节点类型必须是 {value}")
  23 + private Integer type;
  24 +
  25 + @Schema(description = "节点名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "办公用品盘点结果.pdf")
  26 + @NotBlank(message = "节点名称不能为空")
  27 + @Size(max = 255, message = "节点名称长度不能超过 255 个字符")
  28 + private String name;
  29 +
  30 + @Schema(description = "上传文件地址,文件夹不传", example = "https://example.com/file.pdf")
  31 + @Size(max = 2048, message = "文件地址长度不能超过 2048 个字符")
  32 + private String url;
  33 +
  34 + @Schema(description = "文件大小,单位字节,文件夹不传", example = "1024")
  35 + private Long size;
  36 +
  37 + @AssertTrue(message = "文件地址不能为空")
  38 + @JsonIgnore
  39 + public boolean isUrlValid() {
  40 + return ObjUtil.notEqual(type, OaFileNodeTypeEnum.FILE.getType()) || StrUtil.isNotBlank(url);
  41 + }
  42 +
  43 + @AssertTrue(message = "文件大小不能为空且不能小于 0")
  44 + @JsonIgnore
  45 + public boolean isSizeValid() {
  46 + return ObjUtil.notEqual(type, OaFileNodeTypeEnum.FILE.getType()) || (size != null && size >= 0);
  47 + }
  48 +
  49 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/file/vo/node/OaFileStorageRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.file.vo.node;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +@Schema(description = "管理后台 - 云盘空间 Response VO")
  7 +@Data
  8 +public class OaFileStorageRespVO {
  9 +
  10 + @Schema(description = "已用容量,单位字节", example = "1024")
  11 + private Long usedSize;
  12 +
  13 + @Schema(description = "总容量,单位字节", example = "1073741824")
  14 + private Long totalSize;
  15 +
  16 + @Schema(description = "本人正常文件数量,不含目录和回收站", example = "1")
  17 + private Long fileCount;
  18 +
  19 + @Schema(description = "本人有效共享节点数量,按文件或目录去重", example = "1")
  20 + private Long sharedCount;
  21 +
  22 + @Schema(description = "收到的共享入口数量,不重复统计子节点", example = "1")
  23 + private Long receivedCount;
  24 +
  25 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/file/vo/permission/OaFilePermissionRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.file.vo.permission;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +@Schema(description = "管理后台 - 云盘共享权限 Response VO")
  9 +@Data
  10 +public class OaFilePermissionRespVO {
  11 +
  12 + @Schema(description = "权限编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  13 + private Long id;
  14 +
  15 + @Schema(description = "节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  16 + private Long nodeId;
  17 +
  18 + @Schema(description = "主体类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  19 + private Integer subjectType;
  20 +
  21 + @Schema(description = "主体编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  22 + private Long subjectId;
  23 +
  24 + @Schema(description = "权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  25 + private Integer level;
  26 +
  27 + @Schema(description = "是否继承", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
  28 + private Boolean inherit;
  29 +
  30 + @Schema(description = "到期时间", type = "integer", format = "int64", example = "1789347600000")
  31 + private LocalDateTime expireTime;
  32 +
  33 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/file/vo/permission/OaFilePermissionSaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.file.vo.permission;
  2 +
  3 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  4 +import cn.iocoder.yudao.module.oa.enums.file.OaFilePermissionLevelEnum;
  5 +import cn.iocoder.yudao.module.oa.enums.file.OaFileSubjectTypeEnum;
  6 +import io.swagger.v3.oas.annotations.media.Schema;
  7 +import jakarta.validation.constraints.*;
  8 +import lombok.Data;
  9 +
  10 +import java.time.LocalDateTime;
  11 +
  12 +@Schema(description = "管理后台 - 云盘共享权限 Request VO")
  13 +@Data
  14 +public class OaFilePermissionSaveReqVO {
  15 +
  16 + @Schema(description = "节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  17 + @NotNull(message = "节点编号不能为空")
  18 + private Long nodeId;
  19 +
  20 + @Schema(description = "共享主体", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  21 + @NotNull(message = "共享主体类型不能为空")
  22 + @InEnum(value = OaFileSubjectTypeEnum.class, message = "共享主体类型必须是 {value}")
  23 + private Integer subjectType;
  24 +
  25 + @Schema(description = "主体编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  26 + @NotNull(message = "主体编号不能为空")
  27 + private Long subjectId;
  28 +
  29 + @Schema(description = "权限", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  30 + @NotNull(message = "权限级别不能为空")
  31 + @InEnum(value = OaFilePermissionLevelEnum.class, message = "权限级别必须是 {value}")
  32 + private Integer level;
  33 +
  34 + @Schema(description = "是否继承到子目录及文件", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
  35 + @NotNull(message = "是否继承不能为空")
  36 + private Boolean inherit;
  37 +
  38 + @Schema(description = "到期时间", type = "integer", format = "int64", example = "1789347600000")
  39 + @Future(message = "到期时间必须晚于当前时间")
  40 + private LocalDateTime expireTime;
  41 +
  42 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/leave/OaLeaveApplyController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.leave;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  7 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  8 +import cn.iocoder.yudao.module.oa.controller.admin.leave.vo.OaLeaveApplySaveReqVO;
  9 +import cn.iocoder.yudao.module.oa.controller.admin.leave.vo.OaLeaveApplySubmitReqVO;
  10 +import cn.iocoder.yudao.module.oa.controller.admin.leave.vo.OaLeaveApplyPageReqVO;
  11 +import cn.iocoder.yudao.module.oa.controller.admin.leave.vo.OaLeaveApplyRespVO;
  12 +import cn.iocoder.yudao.module.oa.dal.dataobject.leave.OaLeaveApplyDO;
  13 +import cn.iocoder.yudao.module.oa.service.leave.OaLeaveApplyService;
  14 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  15 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  16 +import io.swagger.v3.oas.annotations.Operation;
  17 +import io.swagger.v3.oas.annotations.Parameter;
  18 +import io.swagger.v3.oas.annotations.tags.Tag;
  19 +import jakarta.annotation.Resource;
  20 +import jakarta.validation.Valid;
  21 +import org.springframework.security.access.prepost.PreAuthorize;
  22 +import org.springframework.validation.annotation.Validated;
  23 +import org.springframework.web.bind.annotation.*;
  24 +
  25 +import java.util.Collections;
  26 +import java.util.List;
  27 +import java.util.Map;
  28 +
  29 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  30 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  31 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  32 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  33 +
  34 +@Tag(name = "管理后台 - 请假申请")
  35 +@RestController
  36 +@RequestMapping("/oa/leave-apply")
  37 +@Validated
  38 +public class OaLeaveApplyController {
  39 +
  40 + @Resource
  41 + private OaLeaveApplyService leaveApplyService;
  42 +
  43 + @Resource
  44 + private AdminUserApi adminUserApi;
  45 +
  46 + @PostMapping("/create")
  47 + @Operation(summary = "创建请假申请草稿")
  48 + @PreAuthorize("@ss.hasPermission('oa:leave-apply:create')")
  49 + public CommonResult<Long> createLeaveApply(@Valid @RequestBody OaLeaveApplySaveReqVO createReqVO) {
  50 + return success(leaveApplyService.createLeaveApply(createReqVO));
  51 + }
  52 +
  53 + @PutMapping("/update")
  54 + @Operation(summary = "更新请假申请草稿")
  55 + @PreAuthorize("@ss.hasPermission('oa:leave-apply:create')")
  56 + public CommonResult<Boolean> updateLeaveApply(@Valid @RequestBody OaLeaveApplySaveReqVO saveReqVO) {
  57 + leaveApplyService.updateLeaveApply(saveReqVO, getLoginUserId());
  58 + return success(true);
  59 + }
  60 +
  61 + @PostMapping("/submit")
  62 + @Operation(summary = "提交请假申请")
  63 + @PreAuthorize("@ss.hasPermission('oa:leave-apply:create')")
  64 + public CommonResult<Boolean> submitLeaveApply(@Valid @RequestBody OaLeaveApplySubmitReqVO submitReqVO) {
  65 + leaveApplyService.submitLeaveApply(submitReqVO, getLoginUserId());
  66 + return success(true);
  67 + }
  68 +
  69 + @GetMapping("/page")
  70 + @Operation(summary = "获得本人请假申请分页")
  71 + @PreAuthorize("@ss.hasPermission('oa:leave-apply:query')")
  72 + public CommonResult<PageResult<OaLeaveApplyRespVO>> getLeaveApplyPage(@Valid OaLeaveApplyPageReqVO pageReqVO) {
  73 + PageResult<OaLeaveApplyDO> page = leaveApplyService.getLeaveApplyPage(getLoginUserId(), pageReqVO);
  74 + return success(new PageResult<>(buildLeaveApplyRespVOList(page.getList()), page.getTotal()));
  75 + }
  76 +
  77 + @GetMapping("/get")
  78 + @Operation(summary = "获得请假申请详情")
  79 + @Parameter(name = "id", description = "申请编号", required = true, example = "1024")
  80 + @PreAuthorize("@ss.hasPermission('oa:leave-apply:query')")
  81 + public CommonResult<OaLeaveApplyRespVO> getLeaveApply(@RequestParam("id") Long id) {
  82 + OaLeaveApplyDO application = leaveApplyService.getLeaveApply(id);
  83 + return success(buildLeaveApplyRespVO(application));
  84 + }
  85 +
  86 + // ==================== 拼接 VO ====================
  87 +
  88 + /**
  89 + * 拼接请假申请详情
  90 + *
  91 + * @param application 申请
  92 + * @return 请假申请响应
  93 + */
  94 + private OaLeaveApplyRespVO buildLeaveApplyRespVO(OaLeaveApplyDO application) {
  95 + if (application == null) {
  96 + return null;
  97 + }
  98 + return CollUtil.getFirst(buildLeaveApplyRespVOList(Collections.singletonList(application)));
  99 + }
  100 +
  101 + /**
  102 + * 批量拼接申请人展示信息
  103 + *
  104 + * @param applications 申请列表
  105 + * @return 响应列表
  106 + */
  107 + private List<OaLeaveApplyRespVO> buildLeaveApplyRespVOList(List<OaLeaveApplyDO> applications) {
  108 + if (CollUtil.isEmpty(applications)) {
  109 + return Collections.emptyList();
  110 + }
  111 + // 1. 批量查询申请人
  112 + Map<Long, AdminUserRespDTO> users = adminUserApi.getUserMap(
  113 + convertSet(applications, application -> Long.valueOf(application.getCreator())));
  114 + // 2. 转换并拼接 VO
  115 + return convertList(applications, application -> {
  116 + OaLeaveApplyRespVO vo = BeanUtils.toBean(application, OaLeaveApplyRespVO.class);
  117 + MapUtils.findAndThen(users, Long.valueOf(application.getCreator()), user -> vo.setCreatorName(user.getNickname()));
  118 + return vo;
  119 + });
  120 + }
  121 +
  122 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/leave/vo/OaLeaveApplyPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.leave.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +
  8 +@Schema(description = "管理后台 - 请假申请分页 Request VO")
  9 +@Data
  10 +@EqualsAndHashCode(callSuper = true)
  11 +public class OaLeaveApplyPageReqVO extends PageParam {
  12 +
  13 + @Schema(description = "标题", example = "个人事务请假申请")
  14 + private String title;
  15 +
  16 + @Schema(description = "审批状态", example = "-1")
  17 + private Integer status;
  18 +
  19 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/leave/vo/OaLeaveApplyRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.leave.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +import java.util.List;
  8 +
  9 +@Schema(description = "管理后台 - 请假申请 Response VO")
  10 +@Data
  11 +public class OaLeaveApplyRespVO {
  12 +
  13 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  14 + private Long id;
  15 +
  16 + @Schema(description = "标题", example = "个人事务请假申请")
  17 + private String title;
  18 +
  19 + @Schema(description = "紧急程度", example = "1")
  20 + private Integer urgency;
  21 +
  22 + @Schema(description = "请假类型", example = "1")
  23 + private Integer type;
  24 +
  25 + @Schema(description = "开始时间", type = "integer", format = "int64", example = "1789347600000")
  26 + private LocalDateTime startTime;
  27 +
  28 + @Schema(description = "结束时间", type = "integer", format = "int64", example = "1789380000000")
  29 + private LocalDateTime endTime;
  30 +
  31 + @Schema(description = "天数", example = "1")
  32 + private Integer days;
  33 +
  34 + @Schema(description = "申请原因", example = "处理个人事务")
  35 + private String reason;
  36 +
  37 + @Schema(description = "审批状态", example = "-1")
  38 + private Integer status;
  39 +
  40 + @Schema(description = "流程实例编号", example = "1024")
  41 + private String processInstanceId;
  42 +
  43 + @Schema(description = "附件地址列表", example = "[\"https://example.com/file.pdf\"]")
  44 + private List<String> fileUrls;
  45 +
  46 + @Schema(description = "申请人编号", example = "1024")
  47 + private String creator;
  48 +
  49 + @Schema(description = "申请人昵称", example = "张三")
  50 + private String creatorName;
  51 +
  52 + @Schema(description = "申请时间", requiredMode = Schema.RequiredMode.REQUIRED, type = "integer", format = "int64", example = "1789347600000")
  53 + private LocalDateTime createTime;
  54 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/leave/vo/OaLeaveApplySaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.leave.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.dict.validation.InDict;
  4 +import cn.iocoder.yudao.module.oa.enums.DictTypeConstants;
  5 +import com.fasterxml.jackson.annotation.JsonIgnore;
  6 +import io.swagger.v3.oas.annotations.media.Schema;
  7 +import jakarta.validation.constraints.*;
  8 +import lombok.Data;
  9 +
  10 +import java.time.LocalDateTime;
  11 +import java.util.List;
  12 +
  13 +@Schema(description = "管理后台 - 请假申请新增/修改 Request VO")
  14 +@Data
  15 +public class OaLeaveApplySaveReqVO {
  16 +
  17 + @Schema(description = "申请编号", example = "1024")
  18 + private Long id;
  19 +
  20 + @Schema(description = "标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "个人事务请假申请")
  21 + @NotBlank(message = "标题不能为空")
  22 + @Size(max = 255, message = "标题不能超过 255 个字符")
  23 + private String title;
  24 +
  25 + @Schema(description = "紧急程度", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  26 + @NotNull(message = "紧急程度不能为空")
  27 + @InDict(type = DictTypeConstants.APPLY_URGENCY, message = "紧急程度必须是 {value}")
  28 + private Integer urgency;
  29 +
  30 + @Schema(description = "请假类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  31 + @NotNull(message = "请假类型不能为空")
  32 + @InDict(type = DictTypeConstants.LEAVE_TYPE, message = "请假类型必须是 {value}")
  33 + private Integer type;
  34 +
  35 + @Schema(description = "开始时间", requiredMode = Schema.RequiredMode.REQUIRED, type = "integer", format = "int64", example = "1789347600000")
  36 + @NotNull(message = "开始时间不能为空")
  37 + private LocalDateTime startTime;
  38 +
  39 + @Schema(description = "结束时间", requiredMode = Schema.RequiredMode.REQUIRED, type = "integer", format = "int64", example = "1789380000000")
  40 + @NotNull(message = "结束时间不能为空")
  41 + private LocalDateTime endTime;
  42 +
  43 + @Schema(description = "申请原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "处理个人事务")
  44 + @NotBlank(message = "申请原因不能为空")
  45 + @Size(max = 5000, message = "申请原因不能超过 5000 个字符")
  46 + private String reason;
  47 +
  48 + @Schema(description = "附件地址列表", example = "[\"https://example.com/file.pdf\"]")
  49 + @Size(max = 5, message = "附件不能超过 5 个")
  50 + private List<@NotBlank(message = "附件地址不能为空") @Size(max = 1024, message = "附件地址不能超过 1024 个字符") String> fileUrls;
  51 +
  52 + @AssertTrue(message = "结束时间不能早于开始时间")
  53 + @JsonIgnore
  54 + public boolean isTimeRangeValid() {
  55 + // 缺失值交由字段上的必填校验处理
  56 + return startTime == null || endTime == null || !endTime.isBefore(startTime);
  57 + }
  58 +
  59 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/leave/vo/OaLeaveApplySubmitReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.leave.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotNull;
  5 +import lombok.Data;
  6 +
  7 +import java.util.List;
  8 +import java.util.Map;
  9 +
  10 +@Schema(description = "管理后台 - 请假申请提交 Request VO")
  11 +@Data
  12 +public class OaLeaveApplySubmitReqVO {
  13 +
  14 + @Schema(description = "申请编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  15 + @NotNull(message = "申请编号不能为空")
  16 + private Long id;
  17 +
  18 + @Schema(description = "发起人自选审批人")
  19 + private Map<String, List<Long>> startUserSelectAssignees;
  20 +
  21 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/mail/OaMailAccountController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.mail;
  2 +
  3 +import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  6 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  7 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  8 +import cn.iocoder.yudao.module.oa.controller.admin.mail.vo.account.*;
  9 +import cn.iocoder.yudao.module.oa.service.mail.OaMailAccountService;
  10 +import cn.iocoder.yudao.module.oa.dal.dataobject.mail.OaMailAccountDO;
  11 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  12 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  13 +import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
  14 +import io.swagger.v3.oas.annotations.Operation;
  15 +import io.swagger.v3.oas.annotations.Parameter;
  16 +import io.swagger.v3.oas.annotations.tags.Tag;
  17 +import jakarta.annotation.Resource;
  18 +import jakarta.validation.Valid;
  19 +import org.springframework.security.access.prepost.PreAuthorize;
  20 +import org.springframework.validation.annotation.Validated;
  21 +import org.springframework.web.bind.annotation.*;
  22 +
  23 +import java.util.List;
  24 +import java.util.Map;
  25 +import java.util.Set;
  26 +import cn.hutool.core.util.StrUtil;
  27 +
  28 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  29 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  30 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  31 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  32 +
  33 +@Tag(name = "管理后台 - 企业邮箱账号")
  34 +@RestController
  35 +@RequestMapping("/oa/mail-account")
  36 +@Validated
  37 +public class OaMailAccountController {
  38 +
  39 + @Resource
  40 + private OaMailAccountService mailAccountService;
  41 +
  42 + @Resource
  43 + private AdminUserApi adminUserApi;
  44 +
  45 + @GetMapping("/list")
  46 + @Operation(summary = "获得本人邮箱账号列表")
  47 + @Parameter(name = "status", description = "状态,未传时查询全部状态")
  48 + @PreAuthorize("@ss.hasPermission('oa:mail-account:query')")
  49 + public CommonResult<List<OaMailAccountRespVO>> getMailAccountList(@RequestParam(value = "status", required = false) Integer status) {
  50 + List<OaMailAccountDO> accounts = mailAccountService.getMailAccountListByUserIdAndStatus(getLoginUserId(), status);
  51 + return success(BeanUtils.toBean(accounts, OaMailAccountRespVO.class));
  52 + }
  53 +
  54 + @GetMapping("/simple-list")
  55 + @Operation(summary = "获得当前租户邮箱地址精简列表")
  56 + public CommonResult<List<OaMailAccountRespVO>> getSimpleMailAccountList() {
  57 + // 1.1 查询邮箱及归属人,批量获取用户展示信息
  58 + List<OaMailAccountDO> accounts = mailAccountService.getMailAccountListByStatus(CommonStatusEnum.ENABLE.getStatus());
  59 + Map<Long, AdminUserRespDTO> users = adminUserApi.getUserMap(
  60 + convertSet(accounts, account -> NumberUtils.parseLong(account.getCreator())));
  61 + // 1.2 全员选择器只返回邮箱和姓名,不暴露登录名及连接配置
  62 + List<OaMailAccountRespVO> result = convertList(accounts, account -> {
  63 + OaMailAccountRespVO respVO = new OaMailAccountRespVO().setMail(account.getMail());
  64 + MapUtils.findAndThen(users, NumberUtils.parseLong(account.getCreator()),
  65 + user -> respVO.setUserName(user.getNickname()));
  66 + return respVO;
  67 + });
  68 +
  69 + // 2. 补充已配置邮箱的启用用户
  70 + Set<String> mails = convertSet(result, account -> account.getMail().toLowerCase(java.util.Locale.ROOT));
  71 + List<AdminUserRespDTO> systemUsers = adminUserApi.getUserListByStatus(CommonStatusEnum.ENABLE.getStatus());
  72 + for (AdminUserRespDTO user : systemUsers) {
  73 + if (StrUtil.isNotBlank(user.getEmail()) && mails.add(user.getEmail().toLowerCase(java.util.Locale.ROOT))) {
  74 + result.add(new OaMailAccountRespVO().setMail(user.getEmail()).setUserName(user.getNickname()));
  75 + }
  76 + }
  77 + return success(result);
  78 + }
  79 +
  80 + @GetMapping("/get")
  81 + @Operation(summary = "获得本人邮箱账号")
  82 + @Parameter(name = "id", description = "邮箱账号编号", required = true, example = "1024")
  83 + @PreAuthorize("@ss.hasPermission('oa:mail-account:query')")
  84 + public CommonResult<OaMailAccountRespVO> getMailAccount(@RequestParam("id") Long id) {
  85 + OaMailAccountDO account = mailAccountService.validateMailAccount(id, getLoginUserId());
  86 + return success(BeanUtils.toBean(account, OaMailAccountRespVO.class));
  87 + }
  88 +
  89 + @PostMapping("/create")
  90 + @ApiAccessLog(requestEnable = false) // 特殊:password 密码,避免日志记录
  91 + @Operation(summary = "绑定本人邮箱账号")
  92 + @PreAuthorize("@ss.hasPermission('oa:mail-account:create')")
  93 + public CommonResult<Long> createMailAccount(@Valid @RequestBody OaMailAccountSaveReqVO reqVO) {
  94 + return success(mailAccountService.createMailAccount(reqVO, getLoginUserId()));
  95 + }
  96 +
  97 + @PutMapping("/update")
  98 + @ApiAccessLog(requestEnable = false) // 特殊:password 密码,避免日志记录
  99 + @Operation(summary = "更新本人邮箱账号")
  100 + @PreAuthorize("@ss.hasPermission('oa:mail-account:update')")
  101 + public CommonResult<Boolean> updateMailAccount(@Valid @RequestBody OaMailAccountSaveReqVO reqVO) {
  102 + mailAccountService.updateMailAccount(reqVO, getLoginUserId());
  103 + return success(true);
  104 + }
  105 +
  106 + @PutMapping("/update-default")
  107 + @Operation(summary = "设置本人默认发件账号")
  108 + @Parameter(name = "id", description = "邮箱账号编号", required = true, example = "1024")
  109 + @PreAuthorize("@ss.hasPermission('oa:mail-account:update')")
  110 + public CommonResult<Boolean> updateMailAccountDefault(@RequestParam("id") Long id) {
  111 + mailAccountService.updateMailAccountDefault(id, getLoginUserId());
  112 + return success(true);
  113 + }
  114 +
  115 + @DeleteMapping("/delete")
  116 + @Operation(summary = "移除本人邮箱绑定")
  117 + @Parameter(name = "id", description = "邮箱账号编号", required = true, example = "1024")
  118 + @PreAuthorize("@ss.hasPermission('oa:mail-account:delete')")
  119 + public CommonResult<Boolean> deleteMailAccount(@RequestParam("id") Long id) {
  120 + mailAccountService.deleteMailAccount(id, getLoginUserId());
  121 + return success(true);
  122 + }
  123 +
  124 + @PostMapping("/test-connection")
  125 + @Operation(summary = "测试本人邮箱连接")
  126 + @Parameter(name = "id", description = "邮箱账号编号", required = true, example = "1024")
  127 + @PreAuthorize("@ss.hasPermission('oa:mail-account:query')")
  128 + public CommonResult<Map<String, Boolean>> testMailAccountConnection(@RequestParam("id") Long id) {
  129 + return success(mailAccountService.testMailAccountConnection(id, getLoginUserId()));
  130 + }
  131 +
  132 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/mail/OaMailFolderController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.mail;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.module.oa.controller.admin.mail.vo.message.OaMailFolderRespVO;
  5 +import cn.iocoder.yudao.module.oa.service.mail.OaMailMessageService;
  6 +import io.swagger.v3.oas.annotations.Operation;
  7 +import io.swagger.v3.oas.annotations.Parameter;
  8 +import io.swagger.v3.oas.annotations.tags.Tag;
  9 +import jakarta.annotation.Resource;
  10 +import org.springframework.security.access.prepost.PreAuthorize;
  11 +import org.springframework.validation.annotation.Validated;
  12 +import org.springframework.web.bind.annotation.*;
  13 +
  14 +import java.util.List;
  15 +
  16 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  17 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  18 +
  19 +@Tag(name = "管理后台 - 企业邮箱文件夹")
  20 +@RestController
  21 +@RequestMapping("/oa/mail-folder")
  22 +@Validated
  23 +public class OaMailFolderController {
  24 +
  25 + @Resource
  26 + private OaMailMessageService mailMessageService;
  27 +
  28 + @GetMapping("/list")
  29 + @Operation(summary = "获得邮箱文件夹列表")
  30 + @Parameter(name = "accountId", description = "邮箱账号编号", required = true, example = "1024")
  31 + @PreAuthorize("@ss.hasPermission('oa:mail-account:query')")
  32 + public CommonResult<List<OaMailFolderRespVO>> getMailFolderList(
  33 + @RequestParam Long accountId) {
  34 + return success(mailMessageService.getMailFolderList(accountId, getLoginUserId()));
  35 + }
  36 +
  37 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/mail/OaMailMessageController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.mail;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.*;
  4 +import cn.iocoder.yudao.module.oa.controller.admin.mail.vo.message.*;
  5 +import cn.iocoder.yudao.module.oa.dal.dataobject.mail.*;
  6 +import cn.iocoder.yudao.module.oa.service.mail.OaMailMessageService;
  7 +import io.swagger.v3.oas.annotations.Operation;
  8 +import io.swagger.v3.oas.annotations.Parameter;
  9 +import io.swagger.v3.oas.annotations.tags.Tag;
  10 +import jakarta.annotation.Resource;
  11 +import cn.hutool.core.lang.Pair;
  12 +import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
  13 +import jakarta.servlet.http.HttpServletResponse;
  14 +import org.springframework.web.multipart.MultipartFile;
  15 +import java.io.IOException;
  16 +import java.util.List;
  17 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  18 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  19 +import cn.iocoder.yudao.module.oa.enums.mail.OaMailComposeModeEnum;
  20 +import jakarta.validation.Valid;
  21 +import org.springframework.security.access.prepost.PreAuthorize;
  22 +import org.springframework.validation.annotation.Validated;
  23 +import org.springframework.web.bind.annotation.*;
  24 +
  25 +
  26 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  27 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  28 +
  29 +@Tag(name = "管理后台 - 企业邮箱邮件")
  30 +@RestController
  31 +@RequestMapping("/oa/mail-message")
  32 +@Validated
  33 +@PreAuthorize("@ss.hasPermission('oa:mail-account:query')")
  34 +public class OaMailMessageController {
  35 +
  36 + @Resource
  37 + private OaMailMessageService mailMessageService;
  38 +
  39 + @GetMapping("/compose")
  40 + @Operation(summary = "获得写信预填数据")
  41 + @Parameter(name = "id", description = "邮件编号", required = true, example = "1024")
  42 + @Parameter(name = "mode", description = "写信方式", required = true)
  43 + public CommonResult<OaMailMessageSaveReqVO> getMailMessageCompose(@RequestParam Long id,
  44 + @RequestParam @InEnum(value = OaMailComposeModeEnum.class, message = "写信方式必须是 {value}") String mode) {
  45 + return success(mailMessageService.getMailMessageCompose(id, mode, getLoginUserId()));
  46 + }
  47 +
  48 + @PostMapping("/sync")
  49 + @Operation(summary = "同步本人邮箱索引")
  50 + @Parameter(name = "accountId", description = "邮箱账号编号", required = true, example = "1024")
  51 + public CommonResult<Integer> syncMailMessageList(@RequestParam Long accountId) {
  52 + return success(mailMessageService.syncMailMessageList(accountId, getLoginUserId()));
  53 + }
  54 +
  55 + @GetMapping("/page")
  56 + @Operation(summary = "获得邮件索引分页")
  57 + public CommonResult<PageResult<OaMailMessageRespVO>> getMailMessagePage(@Valid OaMailMessagePageReqVO reqVO) {
  58 + PageResult<OaMailMessageDO> pageResult = mailMessageService.getMailMessagePage(reqVO, getLoginUserId());
  59 + return success(BeanUtils.toBean(pageResult, OaMailMessageRespVO.class));
  60 + }
  61 +
  62 + @GetMapping("/get")
  63 + @Operation(summary = "获得邮件详情")
  64 + @Parameter(name = "id", description = "邮件编号", required = true, example = "1024")
  65 + public CommonResult<OaMailMessageRespVO> getMailMessage(@RequestParam Long id) {
  66 + OaMailMessageDO message = mailMessageService.getMailMessage(id, getLoginUserId());
  67 + return success(BeanUtils.toBean(message, OaMailMessageRespVO.class)
  68 + .setAttachments(BeanUtils.toBean(message.getAttachments(), OaMailMessageRespVO.Attachment.class)));
  69 + }
  70 +
  71 + @PutMapping("/update-read")
  72 + @Operation(summary = "更新邮件已读状态")
  73 + @Parameter(name = "id", description = "邮件编号", required = true, example = "1024")
  74 + @Parameter(name = "readStatus", description = "是否已读", required = true)
  75 + public CommonResult<Boolean> updateMailMessageRead(@RequestParam Long id,
  76 + @RequestParam Boolean readStatus) {
  77 + mailMessageService.updateMailMessageRead(id, readStatus, getLoginUserId());
  78 + return success(true);
  79 + }
  80 +
  81 + @DeleteMapping("/delete")
  82 + @Operation(summary = "删除邮件")
  83 + @Parameter(name = "id", description = "邮件编号", required = true, example = "1024")
  84 + public CommonResult<Boolean> deleteMailMessage(@RequestParam Long id) {
  85 + mailMessageService.deleteMailMessage(id, getLoginUserId());
  86 + return success(true);
  87 + }
  88 +
  89 + @PutMapping("/restore")
  90 + @Operation(summary = "恢复邮件到收件箱")
  91 + @Parameter(name = "id", description = "邮件编号", required = true, example = "1024")
  92 + public CommonResult<Boolean> restoreMailMessage(@RequestParam Long id) {
  93 + mailMessageService.restoreMailMessage(id, getLoginUserId());
  94 + return success(true);
  95 + }
  96 +
  97 + @PostMapping(value = "/save-draft", consumes = "application/json")
  98 + @Operation(summary = "保存草稿")
  99 + public CommonResult<Long> saveMailMessageDraft(@Valid @RequestBody OaMailMessageSaveReqVO reqVO) {
  100 + return success(mailMessageService.saveMailMessageDraft(reqVO, getLoginUserId()));
  101 + }
  102 +
  103 + @PostMapping(value = "/send", consumes = "application/json")
  104 + @Operation(summary = "发送邮件")
  105 + public CommonResult<String> sendMailMessage(@Valid @RequestBody OaMailMessageSaveReqVO reqVO) {
  106 + return success(mailMessageService.sendMailMessage(reqVO, getLoginUserId()));
  107 + }
  108 +
  109 + @PostMapping(value = "/save-draft", consumes = "multipart/form-data")
  110 + @Operation(summary = "保存带附件的草稿")
  111 + public CommonResult<Long> saveMailMessageDraftWithFiles(@Valid @RequestPart("data") OaMailMessageSaveReqVO reqVO,
  112 + @RequestPart(value = "files", required = false) List<MultipartFile> files) {
  113 + return success(mailMessageService.saveMailMessageDraft(reqVO.setFiles(files), getLoginUserId()));
  114 + }
  115 +
  116 + @PostMapping(value = "/send", consumes = "multipart/form-data")
  117 + @Operation(summary = "发送带附件的邮件")
  118 + public CommonResult<String> sendMailMessageWithFiles(@Valid @RequestPart("data") OaMailMessageSaveReqVO reqVO,
  119 + @RequestPart(value = "files", required = false) List<MultipartFile> files) {
  120 + return success(mailMessageService.sendMailMessage(reqVO.setFiles(files), getLoginUserId()));
  121 + }
  122 +
  123 + @GetMapping("/attachment")
  124 + @Operation(summary = "下载本人邮件附件")
  125 + public void getMailMessageAttachment(@RequestParam Long id, @RequestParam String part,
  126 + HttpServletResponse response) throws IOException {
  127 + Pair<String, byte[]> attachment = mailMessageService.getMailMessageAttachment(id, part, getLoginUserId());
  128 + ServletUtils.writeAttachment(response, attachment.getKey(), attachment.getValue());
  129 + }
  130 +
  131 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/mail/OaMailProviderController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.mail;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  5 +import cn.iocoder.yudao.module.oa.controller.admin.mail.vo.provider.OaMailProviderRespVO;
  6 +import cn.iocoder.yudao.module.oa.controller.admin.mail.vo.provider.OaMailProviderSaveReqVO;
  7 +import cn.iocoder.yudao.module.oa.dal.dataobject.mail.OaMailProviderDO;
  8 +import cn.iocoder.yudao.module.oa.service.mail.OaMailProviderService;
  9 +import io.swagger.v3.oas.annotations.Operation;
  10 +import io.swagger.v3.oas.annotations.Parameter;
  11 +import io.swagger.v3.oas.annotations.tags.Tag;
  12 +import jakarta.annotation.Resource;
  13 +import jakarta.validation.Valid;
  14 +import org.springframework.security.access.prepost.PreAuthorize;
  15 +import org.springframework.validation.annotation.Validated;
  16 +import org.springframework.web.bind.annotation.*;
  17 +
  18 +import java.util.List;
  19 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  20 +
  21 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  22 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  23 +
  24 +@Tag(name = "管理后台 - 企业邮箱服务配置")
  25 +@RestController
  26 +@RequestMapping("/oa/mail-provider")
  27 +@Validated
  28 +public class OaMailProviderController {
  29 +
  30 + @Resource
  31 + private OaMailProviderService mailProviderService;
  32 +
  33 + @GetMapping("/list")
  34 + @Operation(summary = "获得邮箱服务配置列表")
  35 + @Parameter(name = "status", description = "状态,未传时查询全部状态")
  36 + @PreAuthorize("@ss.hasPermission('oa:mail-provider:query')")
  37 + public CommonResult<List<OaMailProviderRespVO>> getMailProviderList(@RequestParam(value = "status", required = false) Integer status) {
  38 + List<OaMailProviderDO> providers = mailProviderService.getMailProviderList(status);
  39 + return success(BeanUtils.toBean(providers, OaMailProviderRespVO.class));
  40 + }
  41 +
  42 + @GetMapping("/simple-list")
  43 + @Operation(summary = "获得邮箱服务配置精简列表")
  44 + public CommonResult<List<OaMailProviderRespVO>> getSimpleMailProviderList() {
  45 + List<OaMailProviderDO> providers = mailProviderService.getMailProviderList(CommonStatusEnum.ENABLE.getStatus());
  46 + return success(convertList(providers, provider -> new OaMailProviderRespVO()
  47 + .setId(provider.getId()).setName(provider.getName()).setStatus(provider.getStatus())));
  48 + }
  49 +
  50 + @GetMapping("/get")
  51 + @Operation(summary = "获得邮箱服务配置")
  52 + @Parameter(name = "id", description = "服务配置编号", required = true, example = "1024")
  53 + @PreAuthorize("@ss.hasPermission('oa:mail-provider:query')")
  54 + public CommonResult<OaMailProviderRespVO> getMailProvider(@RequestParam("id") Long id) {
  55 + OaMailProviderDO provider = mailProviderService.validateMailProviderExists(id);
  56 + return success(BeanUtils.toBean(provider, OaMailProviderRespVO.class));
  57 + }
  58 +
  59 + @PostMapping("/create")
  60 + @Operation(summary = "创建邮箱服务配置")
  61 + @PreAuthorize("@ss.hasPermission('oa:mail-provider:create')")
  62 + public CommonResult<Long> createMailProvider(@Valid @RequestBody OaMailProviderSaveReqVO reqVO) {
  63 + return success(mailProviderService.createMailProvider(reqVO));
  64 + }
  65 +
  66 + @PutMapping("/update")
  67 + @Operation(summary = "更新邮箱服务配置")
  68 + @PreAuthorize("@ss.hasPermission('oa:mail-provider:update')")
  69 + public CommonResult<Boolean> updateMailProvider(@Valid @RequestBody OaMailProviderSaveReqVO reqVO) {
  70 + mailProviderService.updateMailProvider(reqVO);
  71 + return success(true);
  72 + }
  73 +
  74 + @DeleteMapping("/delete")
  75 + @Operation(summary = "删除邮箱服务配置")
  76 + @Parameter(name = "id", description = "服务配置编号", required = true, example = "1024")
  77 + @PreAuthorize("@ss.hasPermission('oa:mail-provider:delete')")
  78 + public CommonResult<Boolean> deleteMailProvider(@RequestParam("id") Long id) {
  79 + mailProviderService.deleteMailProvider(id);
  80 + return success(true);
  81 + }
  82 +
  83 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/mail/vo/account/OaMailAccountRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.mail.vo.account;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +@Data
  7 +@Schema(description = "管理后台 - 企业邮箱账号 Response VO")
  8 +public class OaMailAccountRespVO {
  9 +
  10 + @Schema(description = "账号编号", example = "1024")
  11 + private Long id;
  12 +
  13 + @Schema(description = "服务配置编号", example = "1024")
  14 + private Long providerId;
  15 +
  16 + @Schema(description = "邮箱地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "user@example.com")
  17 + private String mail;
  18 +
  19 + @Schema(description = "归属人姓名", example = "张三")
  20 + private String userName;
  21 +
  22 + @Schema(description = "登录用户名", example = "user@example.com")
  23 + private String username;
  24 +
  25 + @Schema(description = "是否默认发件账号", example = "false")
  26 + private Boolean defaultStatus;
  27 +
  28 + @Schema(description = "状态", example = "0")
  29 + private Integer status;
  30 +
  31 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/mail/vo/account/OaMailAccountSaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.mail.vo.account;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  5 +import io.swagger.v3.oas.annotations.media.Schema;
  6 +import jakarta.validation.constraints.*;
  7 +import lombok.Data;
  8 +import com.fasterxml.jackson.annotation.JsonIgnore;
  9 +import lombok.ToString;
  10 +
  11 +@Data
  12 +@Schema(description = "管理后台 - 企业邮箱账号新增/修改 Request VO")
  13 +public class OaMailAccountSaveReqVO {
  14 +
  15 + @Schema(description = "账号编号", example = "1024")
  16 + private Long id;
  17 +
  18 + @Schema(description = "服务配置编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  19 + @NotNull(message = "服务配置编号不能为空")
  20 + private Long providerId;
  21 +
  22 + @Schema(description = "邮箱地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "user@example.com")
  23 + @NotBlank(message = "邮箱地址不能为空")
  24 + @Email(message = "邮箱地址格式不正确")
  25 + @Size(max = 255, message = "邮箱地址长度不能超过 {max} 个字符")
  26 + private String mail;
  27 +
  28 + @Schema(description = "登录用户名", requiredMode = Schema.RequiredMode.REQUIRED, example = "user@example.com")
  29 + @NotBlank(message = "登录用户名不能为空")
  30 + @Size(max = 255, message = "登录用户名长度不能超过 {max} 个字符")
  31 + private String username;
  32 +
  33 + @Schema(description = "密码或授权码,新增时必填,修改时留空表示不变", accessMode = Schema.AccessMode.WRITE_ONLY)
  34 + @ToString.Exclude
  35 + @Size(max = 4096, message = "密码或授权码长度不能超过 {max} 个字符")
  36 + private String password;
  37 +
  38 + @Schema(description = "是否默认发件账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
  39 + @NotNull(message = "是否默认发件账号不能为空")
  40 + private Boolean defaultStatus;
  41 +
  42 + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  43 + @NotNull(message = "状态不能为空")
  44 + @InEnum(value = CommonStatusEnum.class, message = "状态必须是 {value}")
  45 + private Integer status;
  46 +
  47 + /**
  48 + * 校验默认邮箱账号的启用状态
  49 + *
  50 + * @return 是否符合默认账号规则
  51 + */
  52 + @AssertTrue(message = "默认邮箱账号必须启用")
  53 + @JsonIgnore
  54 + public boolean isDefaultStatusValid() {
  55 + return !Boolean.TRUE.equals(defaultStatus) || CommonStatusEnum.ENABLE.getStatus().equals(status);
  56 + }
  57 +
  58 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/mail/vo/message/OaMailFolderRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.mail.vo.message;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +@Schema(description = "管理后台 - 邮件目录 Response VO")
  7 +@Data
  8 +public class OaMailFolderRespVO {
  9 +
  10 + @Schema(description = "目录查询标识", example = "INBOX")
  11 + private String key;
  12 +
  13 + @Schema(description = "显示名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "收件箱")
  14 + private String name;
  15 +
  16 + @Schema(description = "未读数量", example = "1")
  17 + private Long unreadCount;
  18 +
  19 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/mail/vo/message/OaMailMessagePageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.mail.vo.message;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import cn.hutool.core.util.ArrayUtil;
  5 +import cn.hutool.core.util.StrUtil;
  6 +import cn.iocoder.yudao.module.oa.enums.mail.OaMailFolderKeyEnum;
  7 +import com.fasterxml.jackson.annotation.JsonIgnore;
  8 +import io.swagger.v3.oas.annotations.media.Schema;
  9 +import jakarta.validation.constraints.*;
  10 +import lombok.Data;
  11 +import lombok.EqualsAndHashCode;
  12 +
  13 +@Schema(description = "管理后台 - 邮件分页 Request VO")
  14 +@Data
  15 +@EqualsAndHashCode(callSuper = true)
  16 +public class OaMailMessagePageReqVO extends PageParam {
  17 +
  18 + @Schema(description = "邮箱账号编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  19 + @NotNull(message = "邮箱账号编号不能为空")
  20 + private Long accountId;
  21 +
  22 + @Schema(description = "文件夹类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "INBOX")
  23 + @NotBlank(message = "文件夹标识不能为空")
  24 + private String folderKey;
  25 +
  26 + @Schema(description = "主题或发件人", example = "项目评审")
  27 + @Size(max = 200, message = "搜索关键字不能超过 200 个字符")
  28 + private String keyword;
  29 +
  30 + @Schema(description = "是否已读", example = "false")
  31 + private Boolean readStatus;
  32 +
  33 + @Schema(description = "是否有附件", example = "false")
  34 + private Boolean hasAttach;
  35 +
  36 + /**
  37 + * 校验文件夹入口
  38 + *
  39 + * @return 是否为标准入口或合法的自定义目录编号
  40 + */
  41 + @AssertTrue(message = "文件夹标识不正确")
  42 + @JsonIgnore
  43 + public boolean isFolderKeyValid() {
  44 + if (StrUtil.isBlank(folderKey) || ArrayUtil.contains(OaMailFolderKeyEnum.ARRAYS, folderKey)) {
  45 + return true; // 空值由 NotBlank 校验
  46 + }
  47 + return folderKey.matches("[1-9][0-9]{0,17}");
  48 + }
  49 +
  50 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/mail/vo/message/OaMailMessageRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.mail.vo.message;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +import java.util.List;
  8 +
  9 +@Schema(description = "管理后台 - 企业邮箱邮件 Response VO")
  10 +@Data
  11 +public class OaMailMessageRespVO {
  12 +
  13 + @Schema(description = "邮件编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  14 + private Long id;
  15 +
  16 + @Schema(description = "邮箱账号编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  17 + private Long accountId;
  18 +
  19 + @Schema(description = "文件夹编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  20 + private Long folderId;
  21 +
  22 + @Schema(description = "远端邮件 UID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1001")
  23 + private Long uid;
  24 +
  25 + @Schema(description = "远端 UID 有效期", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  26 + private Long uidValidity;
  27 +
  28 + @Schema(description = "邮件主题", requiredMode = Schema.RequiredMode.REQUIRED, example = "项目评审会议通知")
  29 + private String subject;
  30 +
  31 + @Schema(description = "发件人", example = "sender@example.com")
  32 + private String sender;
  33 +
  34 + @Schema(description = "收件人", example = "[\"recipient@example.com\"]")
  35 + private List<String> recipients;
  36 +
  37 + @Schema(description = "抄送人", example = "[\"cc@example.com\"]")
  38 + private List<String> ccs;
  39 +
  40 + @Schema(description = "回复地址", example = "[\"reply@example.com\"]")
  41 + private List<String> replyTos;
  42 +
  43 + @Schema(description = "接收时间", type = "integer", format = "int64", example = "1789347600000")
  44 + private LocalDateTime receiveTime;
  45 +
  46 + @Schema(description = "是否已读", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
  47 + private Boolean readStatus;
  48 +
  49 + @Schema(description = "是否有附件", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
  50 + private Boolean hasAttach;
  51 +
  52 + @Schema(description = "邮件大小(字节)", example = "1024")
  53 + private Integer size;
  54 +
  55 + @Schema(description = "安全 HTML 正文", example = "<p>请查收本周工作安排。</p>")
  56 + private String content;
  57 +
  58 + @Schema(description = "附件信息")
  59 + private List<Attachment> attachments;
  60 +
  61 + @Schema(description = "管理后台 - 邮件附件 Response VO")
  62 + @Data
  63 + public static class Attachment {
  64 +
  65 + @Schema(description = "MIME 部件路径", example = "1.2")
  66 + private String part;
  67 +
  68 + @Schema(description = "附件名称", example = "办公用品盘点结果.pdf")
  69 + private String name;
  70 +
  71 + @Schema(description = "附件大小(字节)", example = "1024")
  72 + private Integer size;
  73 +
  74 + }
  75 +
  76 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/mail/vo/message/OaMailMessageSaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.mail.vo.message;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.*;
  5 +import lombok.Data;
  6 +import com.fasterxml.jackson.annotation.JsonIgnore;
  7 +import org.springframework.web.multipart.MultipartFile;
  8 +import cn.iocoder.yudao.module.oa.dal.dataobject.mail.OaMailMessageDO;
  9 +import java.util.List;
  10 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  11 +import cn.iocoder.yudao.module.oa.enums.mail.OaMailComposeModeEnum;
  12 +
  13 +@Schema(description = "管理后台 - 写信及草稿 Request VO")
  14 +@Data
  15 +public class OaMailMessageSaveReqVO {
  16 +
  17 + @Schema(description = "邮箱账号编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  18 + @NotNull(message = "邮箱账号编号不能为空")
  19 + private Long accountId;
  20 +
  21 + @Schema(description = "原草稿编号", example = "1024")
  22 + private Long draftId;
  23 +
  24 + @Schema(description = "收件人", example = "[\"recipient@example.com\"]")
  25 + private List<@NotBlank(message = "收件人地址不能为空") String> recipients;
  26 +
  27 + @Schema(description = "抄送人", example = "[\"cc@example.com\"]")
  28 + private List<@NotBlank(message = "抄送人地址不能为空") String> ccs;
  29 +
  30 + @Schema(description = "主题", example = "项目评审会议通知")
  31 + @Size(max = 65535, message = "邮件主题不能超过 65535 个字符")
  32 + private String subject;
  33 +
  34 + @Schema(description = "正文 HTML", example = "<p>请查收本周工作安排。</p>")
  35 + @Size(max = 500000, message = "正文 HTML长度不能超过 {max} 个字符")
  36 + private String content;
  37 +
  38 + @Schema(description = "回复或转发的原邮件编号", example = "1024")
  39 + private Long sourceId;
  40 +
  41 + @Schema(description = "写信方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "new")
  42 + @InEnum(value = OaMailComposeModeEnum.class, message = "写信方式必须是 {value}")
  43 + @NotBlank(message = "写信方式不能为空")
  44 + private String mode;
  45 +
  46 + @Schema(description = "保留的原邮件附件路径;空列表表示移除全部原附件")
  47 + private List<String> attachmentParts;
  48 +
  49 + @Schema(description = "原邮件附件,仅用于写信回显", accessMode = Schema.AccessMode.READ_ONLY)
  50 + private List<OaMailMessageDO.Attachment> attachments;
  51 +
  52 + @JsonIgnore
  53 + @Schema(hidden = true)
  54 + private List<MultipartFile> files;
  55 +
  56 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/mail/vo/provider/OaMailProviderRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.mail.vo.provider;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +@Schema(description = "管理后台 - 邮箱服务配置 Response VO")
  7 +@Data
  8 +public class OaMailProviderRespVO {
  9 +
  10 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  11 + private Long id;
  12 +
  13 + @Schema(description = "名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "企业邮箱")
  14 + private String name;
  15 +
  16 + @Schema(description = "IMAP 连接配置")
  17 + private OaMailProviderSaveReqVO.ConnectionConfig imap;
  18 +
  19 + @Schema(description = "SMTP 连接配置")
  20 + private OaMailProviderSaveReqVO.ConnectionConfig smtp;
  21 +
  22 + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  23 + private Integer status;
  24 +
  25 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/mail/vo/provider/OaMailProviderSaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.mail.vo.provider;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  5 +import io.swagger.v3.oas.annotations.media.Schema;
  6 +import jakarta.validation.Valid;
  7 +import jakarta.validation.constraints.*;
  8 +import lombok.Data;
  9 +import com.fasterxml.jackson.annotation.JsonIgnore;
  10 +
  11 +@Data
  12 +@Schema(description = "管理后台 - 企业邮箱服务配置新增/修改 Request VO")
  13 +public class OaMailProviderSaveReqVO {
  14 +
  15 + @Schema(description = "服务配置编号", example = "1024")
  16 + private Long id;
  17 +
  18 + @Schema(description = "名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "企业邮箱")
  19 + @NotBlank(message = "名称不能为空")
  20 + @Size(max = 255, message = "名称长度不能超过 {max} 个字符")
  21 + private String name;
  22 +
  23 + @Schema(description = "IMAP 连接配置", requiredMode = Schema.RequiredMode.REQUIRED)
  24 + @NotNull(message = "IMAP 连接配置不能为空")
  25 + @Valid
  26 + private ConnectionConfig imap;
  27 +
  28 + @Schema(description = "SMTP 连接配置", requiredMode = Schema.RequiredMode.REQUIRED)
  29 + @NotNull(message = "SMTP 连接配置不能为空")
  30 + @Valid
  31 + private ConnectionConfig smtp;
  32 +
  33 + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  34 + @NotNull(message = "状态不能为空")
  35 + @InEnum(value = CommonStatusEnum.class, message = "状态必须是 {value}")
  36 + private Integer status;
  37 +
  38 + @Schema(description = "管理后台 - 邮箱连接配置 Request VO")
  39 + @Data
  40 + public static class ConnectionConfig {
  41 +
  42 + @Schema(description = "服务器域名", requiredMode = Schema.RequiredMode.REQUIRED, example = "imap.example.com")
  43 + @NotBlank(message = "服务器域名不能为空")
  44 + @Size(max = 253, message = "服务器域名长度不能超过 253 个字符")
  45 + private String host;
  46 +
  47 + @Schema(description = "服务器端口", requiredMode = Schema.RequiredMode.REQUIRED, example = "993")
  48 + @NotNull(message = "服务器端口不能为空")
  49 + @Min(value = 1, message = "服务器端口不能小于 1")
  50 + @Max(value = 65535, message = "服务器端口不能大于 65535")
  51 + private Integer port;
  52 +
  53 + @Schema(description = "是否开启 SSL", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
  54 + @NotNull(message = "是否开启 SSL 不能为空")
  55 + private Boolean sslEnable;
  56 +
  57 + @Schema(description = "是否开启 STARTTLS", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
  58 + @NotNull(message = "是否开启 STARTTLS 不能为空")
  59 + private Boolean starttlsEnable;
  60 +
  61 + /**
  62 + * 校验邮箱连接加密方式
  63 + *
  64 + * @return 是否仅启用一种加密方式,空值由字段必填注解校验
  65 + */
  66 + @AssertTrue(message = "请选择 SSL 或 STARTTLS,且不能同时开启")
  67 + @JsonIgnore
  68 + public boolean isEncryptionValid() {
  69 + return sslEnable == null || !sslEnable.equals(starttlsEnable);
  70 + }
  71 +
  72 + }
  73 +
  74 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/meetingroom/OaMeetingRoomBookingController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.meetingroom;
  2 +
  3 +import cn.iocoder.yudao.module.oa.controller.admin.meetingroom.vo.booking.*;
  4 +import cn.iocoder.yudao.module.system.api.dept.DeptApi;
  5 +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
  6 +import cn.hutool.core.collection.CollUtil;
  7 +import cn.hutool.core.util.NumberUtil;
  8 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  9 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  10 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  11 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  12 +import cn.iocoder.yudao.module.oa.dal.dataobject.meetingroom.*;
  13 +import cn.iocoder.yudao.module.oa.service.meetingroom.*;
  14 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  15 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  16 +import io.swagger.v3.oas.annotations.Operation;
  17 +import io.swagger.v3.oas.annotations.Parameter;
  18 +import io.swagger.v3.oas.annotations.tags.Tag;
  19 +import jakarta.annotation.Resource;
  20 +import jakarta.validation.Valid;
  21 +import org.springframework.security.access.prepost.PreAuthorize;
  22 +import org.springframework.validation.annotation.Validated;
  23 +import org.springframework.web.bind.annotation.*;
  24 +
  25 +import java.util.*;
  26 +
  27 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  28 +
  29 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
  30 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  31 +
  32 +@Tag(name = "管理后台 - 会议室预定")
  33 +@RestController
  34 +@RequestMapping("/oa/meeting-room-booking")
  35 +@Validated
  36 +public class OaMeetingRoomBookingController {
  37 +
  38 + @Resource
  39 + private OaMeetingRoomBookingService meetingRoomBookingService;
  40 + @Resource
  41 + private OaMeetingRoomService meetingRoomService;
  42 +
  43 + @Resource
  44 + private AdminUserApi adminUserApi;
  45 + @Resource
  46 + private DeptApi deptApi;
  47 +
  48 + @PostMapping("/create")
  49 + @Operation(summary = "创建会议室预定草稿")
  50 + @PreAuthorize("@ss.hasPermission('oa:meeting-room-booking:create')")
  51 + public CommonResult<Long> createMeetingRoomBooking(@Valid @RequestBody OaMeetingRoomBookingSaveReqVO reqVO) {
  52 + return success(meetingRoomBookingService.createMeetingRoomBooking(reqVO, getLoginUserId()));
  53 + }
  54 +
  55 + @PutMapping("/update")
  56 + @Operation(summary = "更新会议室预定草稿")
  57 + @PreAuthorize("@ss.hasPermission('oa:meeting-room-booking:update')")
  58 + public CommonResult<Boolean> updateMeetingRoomBooking(@Valid @RequestBody OaMeetingRoomBookingSaveReqVO reqVO) {
  59 + meetingRoomBookingService.updateMeetingRoomBooking(reqVO, getLoginUserId());
  60 + return success(true);
  61 + }
  62 +
  63 + @DeleteMapping("/delete")
  64 + @Operation(summary = "删除会议室预定草稿")
  65 + @Parameter(name = "id", description = "预定编号", required = true, example = "1024")
  66 + @PreAuthorize("@ss.hasPermission('oa:meeting-room-booking:delete')")
  67 + public CommonResult<Boolean> deleteMeetingRoomBooking(@RequestParam("id") Long id) {
  68 + meetingRoomBookingService.deleteMeetingRoomBooking(id, getLoginUserId());
  69 + return success(true);
  70 + }
  71 +
  72 + @GetMapping("/get")
  73 + @Operation(summary = "获得会议室预定详情")
  74 + @Parameter(name = "id", description = "预定编号", required = true, example = "1024")
  75 + @PreAuthorize("@ss.hasPermission('oa:meeting-room-booking:query')")
  76 + public CommonResult<OaMeetingRoomBookingRespVO> getMeetingRoomBooking(@RequestParam("id") Long id) {
  77 + OaMeetingRoomBookingDO booking = meetingRoomBookingService.getMeetingRoomBooking(id);
  78 + return success(buildMeetingRoomBookingRespVO(booking));
  79 + }
  80 +
  81 + @GetMapping("/page")
  82 + @Operation(summary = "获得本人的会议室预定分页")
  83 + @PreAuthorize("@ss.hasPermission('oa:meeting-room-booking:query')")
  84 + public CommonResult<PageResult<OaMeetingRoomBookingRespVO>> getMeetingRoomBookingPage(@Valid OaMeetingRoomBookingPageReqVO reqVO) {
  85 + PageResult<OaMeetingRoomBookingDO> pageResult = meetingRoomBookingService.getMeetingRoomBookingPage(getLoginUserId(), reqVO);
  86 + return success(new PageResult<>(buildMeetingRoomBookingRespVOList(pageResult.getList()), pageResult.getTotal()));
  87 + }
  88 +
  89 + @PutMapping("/submit")
  90 + @Operation(summary = "提交会议室预定")
  91 + @Parameter(name = "id", description = "预定编号", required = true, example = "1024")
  92 + @PreAuthorize("@ss.hasPermission('oa:meeting-room-booking:create')")
  93 + public CommonResult<Boolean> submitMeetingRoomBooking(@RequestParam("id") Long id) {
  94 + meetingRoomBookingService.submitMeetingRoomBooking(id, getLoginUserId());
  95 + return success(true);
  96 + }
  97 +
  98 + @PutMapping("/cancel")
  99 + @Operation(summary = "取消会议室预定")
  100 + @Parameter(name = "id", description = "预定编号", required = true, example = "1024")
  101 + @PreAuthorize("@ss.hasPermission('oa:meeting-room-booking:update')")
  102 + public CommonResult<Boolean> cancelMeetingRoomBooking(@RequestParam("id") Long id) {
  103 + meetingRoomBookingService.cancelMeetingRoomBooking(id, getLoginUserId());
  104 + return success(true);
  105 + }
  106 +
  107 + @PutMapping("/start")
  108 + @Operation(summary = "开始使用会议室预定")
  109 + @Parameter(name = "id", description = "预定编号", required = true, example = "1024")
  110 + @PreAuthorize("@ss.hasPermission('oa:meeting-room-booking:update')")
  111 + public CommonResult<Boolean> startMeetingRoomBooking(@RequestParam("id") Long id) {
  112 + meetingRoomBookingService.startMeetingRoomBooking(id, getLoginUserId());
  113 + return success(true);
  114 + }
  115 +
  116 + @PutMapping("/finish")
  117 + @Operation(summary = "完成使用会议室预定")
  118 + @Parameter(name = "id", description = "预定编号", required = true, example = "1024")
  119 + @PreAuthorize("@ss.hasPermission('oa:meeting-room-booking:update')")
  120 + public CommonResult<Boolean> finishMeetingRoomBooking(@RequestParam("id") Long id) {
  121 + meetingRoomBookingService.finishMeetingRoomBooking(id, getLoginUserId());
  122 + return success(true);
  123 + }
  124 +
  125 + @GetMapping("/schedule")
  126 + @Operation(summary = "获得会议室日程")
  127 + @PreAuthorize("@ss.hasAnyPermissions('oa:meeting-room:query', 'oa:meeting-room-booking:query')")
  128 + public CommonResult<List<OaMeetingRoomBookingRespVO>> getMeetingRoomBookingListBySchedule(@Valid OaMeetingRoomBookingScheduleReqVO reqVO) {
  129 + List<OaMeetingRoomBookingDO> bookings = meetingRoomBookingService.getMeetingRoomBookingListBySchedule(reqVO);
  130 + return success(buildMeetingRoomBookingRespVOList(bookings));
  131 + }
  132 +
  133 + // ==================== 拼接 VO ====================
  134 +
  135 + /**
  136 + * 拼接会议室预定详情
  137 + *
  138 + * @param booking 会议室预定
  139 + * @return 会议室预定响应
  140 + */
  141 + private OaMeetingRoomBookingRespVO buildMeetingRoomBookingRespVO(OaMeetingRoomBookingDO booking) {
  142 + if (booking == null) {
  143 + return null;
  144 + }
  145 + return CollUtil.getFirst(buildMeetingRoomBookingRespVOList(Collections.singletonList(booking)));
  146 + }
  147 +
  148 + /**
  149 + * 构建会议室预定响应列表
  150 + *
  151 + * @param bookings 会议室预定列表
  152 + * @return 响应列表
  153 + */
  154 + private List<OaMeetingRoomBookingRespVO> buildMeetingRoomBookingRespVOList(List<OaMeetingRoomBookingDO> bookings) {
  155 + if (CollUtil.isEmpty(bookings)) {
  156 + return Collections.emptyList();
  157 + }
  158 + // 1.1 批量查询申请人、主持人和参会人
  159 + Set<Long> userIds = convertSet(bookings, OaMeetingRoomBookingDO::getModeratorUserId);
  160 + userIds.addAll(convertSet(bookings, booking -> Long.valueOf(booking.getCreator()),
  161 + booking -> NumberUtil.isLong(booking.getCreator())));
  162 + userIds.addAll(convertSetByFlatMap(bookings, OaMeetingRoomBookingDO::getAttendeeUserIds, Collection::stream));
  163 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
  164 + // 1.2 批量查询房间及申请部门
  165 + Map<Long, OaMeetingRoomDO> roomMap = meetingRoomService.getMeetingRoomMap(
  166 + convertSet(bookings, OaMeetingRoomBookingDO::getRoomId));
  167 + Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(bookings, OaMeetingRoomBookingDO::getDeptId));
  168 + // 2. 转换并拼接展示字段
  169 + return convertList(bookings, booking -> {
  170 + OaMeetingRoomBookingRespVO vo = BeanUtils.toBean(booking, OaMeetingRoomBookingRespVO.class);
  171 + MapUtils.findAndThen(roomMap, booking.getRoomId(), room ->
  172 + vo.setRoomName(room.getName()).setRoomLocation(room.getLocation()).setRoomType(room.getType()));
  173 + MapUtils.findAndThen(userMap, booking.getModeratorUserId(), user -> vo.setModeratorName(user.getNickname()));
  174 + if (NumberUtil.isLong(booking.getCreator())) {
  175 + MapUtils.findAndThen(userMap, Long.valueOf(booking.getCreator()), user -> vo.setCreatorName(user.getNickname()));
  176 + }
  177 + MapUtils.findAndThen(deptMap, booking.getDeptId(), dept -> vo.setDeptName(dept.getName()));
  178 + vo.setAttendeeNames(convertList(booking.getAttendeeUserIds(), userId ->
  179 + userMap.containsKey(userId) ? userMap.get(userId).getNickname() : userId.toString()));
  180 + return vo;
  181 + });
  182 + }
  183 +
  184 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/meetingroom/OaMeetingRoomController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.meetingroom;
  2 +
  3 +import cn.iocoder.yudao.module.oa.controller.admin.meetingroom.vo.room.*;
  4 +import cn.hutool.core.collection.CollUtil;
  5 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  6 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  7 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  8 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  9 +import cn.iocoder.yudao.module.oa.dal.dataobject.meetingroom.*;
  10 +import cn.iocoder.yudao.module.oa.service.meetingroom.*;
  11 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  12 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  13 +import io.swagger.v3.oas.annotations.Operation;
  14 +import io.swagger.v3.oas.annotations.Parameter;
  15 +import io.swagger.v3.oas.annotations.tags.Tag;
  16 +import jakarta.annotation.Resource;
  17 +import jakarta.validation.Valid;
  18 +import org.springframework.security.access.prepost.PreAuthorize;
  19 +import org.springframework.validation.annotation.Validated;
  20 +import org.springframework.web.bind.annotation.*;
  21 +
  22 +import java.util.*;
  23 +
  24 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  25 +
  26 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
  27 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  28 +
  29 +@Tag(name = "管理后台 - 会议室")
  30 +@RestController
  31 +@RequestMapping("/oa/meeting-room")
  32 +@Validated
  33 +public class OaMeetingRoomController {
  34 +
  35 + @Resource
  36 + private OaMeetingRoomService meetingRoomService;
  37 + @Resource
  38 + private AdminUserApi adminUserApi;
  39 +
  40 + @PostMapping("/create")
  41 + @Operation(summary = "创建会议室")
  42 + @PreAuthorize("@ss.hasPermission('oa:meeting-room:create')")
  43 + public CommonResult<Long> createMeetingRoom(@Valid @RequestBody OaMeetingRoomSaveReqVO reqVO) {
  44 + return success(meetingRoomService.createMeetingRoom(reqVO));
  45 + }
  46 +
  47 + @PutMapping("/update")
  48 + @Operation(summary = "更新会议室")
  49 + @PreAuthorize("@ss.hasPermission('oa:meeting-room:update')")
  50 + public CommonResult<Boolean> updateMeetingRoom(@Valid @RequestBody OaMeetingRoomSaveReqVO reqVO) {
  51 + meetingRoomService.updateMeetingRoom(reqVO);
  52 + return success(true);
  53 + }
  54 +
  55 + @DeleteMapping("/delete")
  56 + @Operation(summary = "删除会议室")
  57 + @Parameter(name = "id", description = "会议室编号", required = true, example = "1024")
  58 + @PreAuthorize("@ss.hasPermission('oa:meeting-room:delete')")
  59 + public CommonResult<Boolean> deleteMeetingRoom(@RequestParam("id") Long id) {
  60 + meetingRoomService.deleteMeetingRoom(id);
  61 + return success(true);
  62 + }
  63 +
  64 + @GetMapping("/get")
  65 + @Operation(summary = "获得会议室详情")
  66 + @Parameter(name = "id", description = "会议室编号", required = true, example = "1024")
  67 + @PreAuthorize("@ss.hasPermission('oa:meeting-room:query')")
  68 + public CommonResult<OaMeetingRoomRespVO> getMeetingRoom(@RequestParam("id") Long id) {
  69 + OaMeetingRoomDO room = meetingRoomService.validateMeetingRoomExists(id);
  70 + return success(buildMeetingRoomRespVO(room));
  71 + }
  72 +
  73 + @GetMapping("/page")
  74 + @Operation(summary = "获得会议室分页")
  75 + @PreAuthorize("@ss.hasPermission('oa:meeting-room:query')")
  76 + public CommonResult<PageResult<OaMeetingRoomRespVO>> getMeetingRoomPage(@Valid OaMeetingRoomPageReqVO reqVO) {
  77 + PageResult<OaMeetingRoomDO> pageResult = meetingRoomService.getMeetingRoomPage(reqVO);
  78 + return success(new PageResult<>(buildMeetingRoomRespVOList(pageResult.getList()), pageResult.getTotal()));
  79 + }
  80 +
  81 + @GetMapping("/bookable-page")
  82 + @Operation(summary = "获得可预定会议室分页")
  83 + public CommonResult<PageResult<OaMeetingRoomRespVO>> getMeetingRoomPageByBookingUserId(@Valid OaMeetingRoomPageReqVO reqVO) {
  84 + PageResult<OaMeetingRoomDO> pageResult = meetingRoomService.getMeetingRoomPageByBookingUserId(reqVO, getLoginUserId());
  85 + List<OaMeetingRoomRespVO> list = buildMeetingRoomRespVOList(pageResult.getList());
  86 + return success(new PageResult<>(convertList(list, room -> new OaMeetingRoomRespVO()
  87 + .setId(room.getId()).setName(room.getName()).setLocation(room.getLocation())
  88 + .setType(room.getType()).setStatus(room.getStatus()).setSeatCount(room.getSeatCount())
  89 + .setManagerName(room.getManagerName())), pageResult.getTotal()));
  90 + }
  91 +
  92 + // ==================== 拼接 VO ====================
  93 +
  94 + /**
  95 + * 构建会议室响应
  96 + *
  97 + * @param room 会议室
  98 + * @return 会议室响应
  99 + */
  100 + private OaMeetingRoomRespVO buildMeetingRoomRespVO(OaMeetingRoomDO room) {
  101 + AdminUserRespDTO user = adminUserApi.getUser(room.getManagerUserId());
  102 + OaMeetingRoomRespVO respVO = BeanUtils.toBean(room, OaMeetingRoomRespVO.class);
  103 + if (user != null) {
  104 + respVO.setManagerName(user.getNickname()).setManagerPhone(user.getMobile());
  105 + }
  106 + return respVO;
  107 + }
  108 +
  109 + /**
  110 + * 构建会议室响应列表
  111 + *
  112 + * @param rooms 会议室列表
  113 + * @return 响应列表
  114 + */
  115 + private List<OaMeetingRoomRespVO> buildMeetingRoomRespVOList(List<OaMeetingRoomDO> rooms) {
  116 + if (CollUtil.isEmpty(rooms)) {
  117 + return Collections.emptyList();
  118 + }
  119 + // 1. 批量查询管理员
  120 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertSet(rooms, OaMeetingRoomDO::getManagerUserId));
  121 + // 2. 转换并拼接管理员姓名、电话
  122 + return convertList(rooms, room -> {
  123 + OaMeetingRoomRespVO vo = BeanUtils.toBean(room, OaMeetingRoomRespVO.class);
  124 + MapUtils.findAndThen(userMap, room.getManagerUserId(), user ->
  125 + vo.setManagerName(user.getNickname()).setManagerPhone(user.getMobile()));
  126 + return vo;
  127 + });
  128 + }
  129 +
  130 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/meetingroom/vo/booking/OaMeetingRoomBookingPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.meetingroom.vo.booking;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import org.springframework.format.annotation.DateTimeFormat;
  8 +
  9 +import java.time.LocalDateTime;
  10 +
  11 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  12 +
  13 +@Schema(description = "管理后台 - 会议室预定分页 Request VO")
  14 +@Data
  15 +@EqualsAndHashCode(callSuper = true)
  16 +public class OaMeetingRoomBookingPageReqVO extends PageParam {
  17 +
  18 + @Schema(description = "单据编号", example = "HY20260914000001")
  19 + private String no;
  20 +
  21 + @Schema(description = "会议室名称", example = "101 项目讨论室")
  22 + private String roomName;
  23 +
  24 + @Schema(description = "会议主题", example = "产品需求评审")
  25 + private String title;
  26 +
  27 + @Schema(description = "主持人姓名", example = "芋艿")
  28 + private String moderatorName;
  29 +
  30 + @Schema(description = "申请部门编号", example = "100")
  31 + private Long deptId;
  32 +
  33 + @Schema(description = "审批状态", example = "2")
  34 + private Integer status;
  35 +
  36 + @Schema(description = "使用状态", example = "0")
  37 + private Integer useStatus;
  38 +
  39 + @Schema(description = "会议开始时间", example = "[\"2026-09-14 00:00:00\",\"2026-09-14 23:59:59\"]")
  40 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  41 + private LocalDateTime[] startTime;
  42 +
  43 + @Schema(description = "会议结束时间")
  44 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  45 + private LocalDateTime[] endTime;
  46 +
  47 + @Schema(description = "创建人编号", example = "1")
  48 + private String creator;
  49 +
  50 + @Schema(description = "创建时间", example = "[\"2026-09-14 00:00:00\",\"2026-09-14 23:59:59\"]")
  51 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  52 + private LocalDateTime[] createTime;
  53 +
  54 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/meetingroom/vo/booking/OaMeetingRoomBookingRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.meetingroom.vo.booking;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +import java.util.List;
  8 +
  9 +@Schema(description = "管理后台 - 会议室预定 Response VO")
  10 +@Data
  11 +public class OaMeetingRoomBookingRespVO {
  12 +
  13 + @Schema(description = "编号", example = "1024", requiredMode = Schema.RequiredMode.REQUIRED)
  14 + private Long id;
  15 +
  16 + @Schema(description = "会议室编号", example = "1024", requiredMode = Schema.RequiredMode.REQUIRED)
  17 + private Long roomId;
  18 +
  19 + @Schema(description = "会议主题", example = "产品需求评审", requiredMode = Schema.RequiredMode.REQUIRED)
  20 + private String title;
  21 +
  22 + @Schema(description = "会议开始时间", example = "1789351200000", requiredMode = Schema.RequiredMode.REQUIRED, type = "integer", format = "int64")
  23 + private LocalDateTime startTime;
  24 +
  25 + @Schema(description = "会议结束时间", example = "1789354800000", requiredMode = Schema.RequiredMode.REQUIRED, type = "integer", format = "int64")
  26 + private LocalDateTime endTime;
  27 +
  28 + @Schema(description = "主持人用户编号", example = "1", requiredMode = Schema.RequiredMode.REQUIRED)
  29 + private Long moderatorUserId;
  30 +
  31 + @Schema(description = "参会人用户编号列表", example = "[1,2]")
  32 + private List<Long> attendeeUserIds;
  33 +
  34 + @Schema(description = "会议提醒", example = "1", requiredMode = Schema.RequiredMode.REQUIRED)
  35 + private Integer reminderType;
  36 +
  37 + @Schema(description = "会议说明", example = "讨论产品需求和排期")
  38 + private String description;
  39 +
  40 + @Schema(description = "申请备注", example = "请提前安排会议室")
  41 + private String remark;
  42 +
  43 + @Schema(description = "附件地址列表", example = "[\"https://www.iocoder.cn/meeting.pdf\"]")
  44 + private List<String> fileUrls;
  45 +
  46 + @Schema(description = "申请单号", example = "HY20260914000001", requiredMode = Schema.RequiredMode.REQUIRED)
  47 + private String no;
  48 +
  49 + @Schema(description = "会议室名称", example = "101 项目讨论室")
  50 + private String roomName;
  51 +
  52 + @Schema(description = "位置", example = "办公楼一层 101")
  53 + private String roomLocation;
  54 +
  55 + @Schema(description = "会议室类型", example = "1")
  56 + private Integer roomType;
  57 +
  58 + @Schema(description = "主持人姓名", example = "芋艿")
  59 + private String moderatorName;
  60 +
  61 + @Schema(description = "参会人姓名", example = "[\"芋艿\",\"小王\"]")
  62 + private List<String> attendeeNames;
  63 +
  64 + @Schema(description = "申请人", example = "芋艿")
  65 + private String creatorName;
  66 +
  67 + @Schema(description = "申请部门编号", example = "100")
  68 + private Long deptId;
  69 +
  70 + @Schema(description = "部门名称", example = "研发部门")
  71 + private String deptName;
  72 +
  73 + @Schema(description = "审批状态", example = "2", requiredMode = Schema.RequiredMode.REQUIRED)
  74 + private Integer status;
  75 +
  76 + @Schema(description = "使用状态", example = "0", requiredMode = Schema.RequiredMode.REQUIRED)
  77 + private Integer useStatus;
  78 +
  79 + @Schema(description = "提交时是否需要审批", example = "false")
  80 + private Boolean needApproval;
  81 +
  82 + @Schema(description = "流程实例编号", example = "process-1024")
  83 + private String processInstanceId;
  84 +
  85 + @Schema(description = "创建时间", example = "1789347600000", requiredMode = Schema.RequiredMode.REQUIRED, type = "integer", format = "int64")
  86 + private LocalDateTime createTime;
  87 +
  88 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/meetingroom/vo/booking/OaMeetingRoomBookingSaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.meetingroom.vo.booking;
  2 +
  3 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  4 +import cn.iocoder.yudao.module.oa.enums.meetingroom.*;
  5 +import io.swagger.v3.oas.annotations.media.Schema;
  6 +import jakarta.validation.constraints.*;
  7 +import lombok.Data;
  8 +
  9 +import java.time.LocalDateTime;
  10 +import java.util.List;
  11 +
  12 +@Schema(description = "管理后台 - 会议室预定新增/修改 Request VO")
  13 +@Data
  14 +public class OaMeetingRoomBookingSaveReqVO {
  15 +
  16 + @Schema(description = "编号", example = "1024")
  17 + private Long id;
  18 +
  19 + @Schema(description = "会议室编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  20 + @NotNull(message = "会议室不能为空")
  21 + private Long roomId;
  22 +
  23 + @Schema(description = "会议主题", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品需求评审")
  24 + @NotBlank(message = "会议主题不能为空")
  25 + @Size(max = 200, message = "会议主题长度不能超过 {max} 个字符")
  26 + private String title;
  27 +
  28 + @Schema(description = "会议开始时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "1789351200000", type = "integer", format = "int64")
  29 + @NotNull(message = "开始时间不能为空")
  30 + private LocalDateTime startTime;
  31 +
  32 + @Schema(description = "会议结束时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "1789354800000", type = "integer", format = "int64")
  33 + @NotNull(message = "结束时间不能为空")
  34 + private LocalDateTime endTime;
  35 +
  36 + @Schema(description = "主持人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  37 + @NotNull(message = "主持人不能为空")
  38 + private Long moderatorUserId;
  39 +
  40 + @Schema(description = "参会人用户编号列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1,2]")
  41 + @NotNull(message = "参会人不能为空")
  42 + private List<@NotNull(message = "参会人不能为空") Long> attendeeUserIds;
  43 +
  44 + @Schema(description = "会议提醒", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  45 + @NotNull(message = "会议提醒不能为空")
  46 + @InEnum(value = OaMeetingRoomReminderTypeEnum.class, message = "会议提醒不正确")
  47 + private Integer reminderType;
  48 +
  49 + @Schema(description = "会议说明", example = "讨论产品需求和排期")
  50 + @Size(max = 500, message = "会议说明长度不能超过 {max} 个字符")
  51 + private String description;
  52 +
  53 + @Schema(description = "申请备注", example = "请提前安排会议室")
  54 + @Size(max = 500, message = "申请备注长度不能超过 {max} 个字符")
  55 + private String remark;
  56 +
  57 + @Schema(description = "附件地址列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[\"https://www.iocoder.cn/meeting.pdf\"]")
  58 + @NotNull(message = "附件地址列表不能为空")
  59 + private List<@NotBlank(message = "附件地址列表不能为空") String> fileUrls;
  60 +
  61 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/meetingroom/vo/booking/OaMeetingRoomBookingScheduleReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.meetingroom.vo.booking;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotNull;
  5 +import lombok.Data;
  6 +import org.springframework.format.annotation.DateTimeFormat;
  7 +
  8 +import java.time.LocalDateTime;
  9 +
  10 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  11 +
  12 +@Schema(description = "管理后台 - 会议室占用时段 Request VO")
  13 +@Data
  14 +public class OaMeetingRoomBookingScheduleReqVO {
  15 +
  16 + @Schema(description = "会议室编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  17 + @NotNull(message = "会议室编号不能为空")
  18 + private Long roomId;
  19 +
  20 + @Schema(description = "查询开始时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-09-14 10:00:00")
  21 + @NotNull(message = "查询开始时间不能为空")
  22 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  23 + private LocalDateTime startTime;
  24 +
  25 + @Schema(description = "查询结束时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-09-14 11:00:00")
  26 + @NotNull(message = "查询结束时间不能为空")
  27 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  28 + private LocalDateTime endTime;
  29 +
  30 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/meetingroom/vo/room/OaMeetingRoomPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.meetingroom.vo.room;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +
  8 +@Schema(description = "管理后台 - 会议室分页 Request VO")
  9 +@Data
  10 +@EqualsAndHashCode(callSuper = true)
  11 +public class OaMeetingRoomPageReqVO extends PageParam {
  12 +
  13 + @Schema(description = "会议室名称", example = "101 项目讨论室")
  14 + private String name;
  15 +
  16 + @Schema(description = "位置", example = "办公楼一层 101")
  17 + private String location;
  18 +
  19 + @Schema(description = "类型", example = "1")
  20 + private Integer type;
  21 +
  22 + @Schema(description = "可用状态", example = "0")
  23 + private Integer status;
  24 +
  25 + @Schema(description = "负责人姓名", example = "芋艿")
  26 + private String managerName;
  27 +
  28 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/meetingroom/vo/room/OaMeetingRoomRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.meetingroom.vo.room;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +import java.util.List;
  8 +
  9 +@Schema(description = "管理后台 - 会议室 Response VO")
  10 +@Data
  11 +public class OaMeetingRoomRespVO {
  12 +
  13 + @Schema(description = "编号", example = "1024", requiredMode = Schema.RequiredMode.REQUIRED)
  14 + private Long id;
  15 +
  16 + @Schema(description = "会议室名称", example = "101 项目讨论室", requiredMode = Schema.RequiredMode.REQUIRED)
  17 + private String name;
  18 +
  19 + @Schema(description = "位置", example = "办公楼一层 101", requiredMode = Schema.RequiredMode.REQUIRED)
  20 + private String location;
  21 +
  22 + @Schema(description = "会议室类型", example = "1", requiredMode = Schema.RequiredMode.REQUIRED)
  23 + private Integer type;
  24 +
  25 + @Schema(description = "负责人用户编号", example = "1", requiredMode = Schema.RequiredMode.REQUIRED)
  26 + private Long managerUserId;
  27 +
  28 + @Schema(description = "可用状态", example = "0", requiredMode = Schema.RequiredMode.REQUIRED)
  29 + private Integer status;
  30 +
  31 + @Schema(description = "会议室图片", example = "https://www.iocoder.cn/meeting-room.jpg")
  32 + private String picUrl;
  33 +
  34 + @Schema(description = "坐席数", example = "8")
  35 + private Integer seatCount;
  36 +
  37 + @Schema(description = "设备列表", example = "[1,2]")
  38 + private List<Integer> equipments;
  39 +
  40 + @Schema(description = "允许预定", example = "true", requiredMode = Schema.RequiredMode.REQUIRED)
  41 + private Boolean allowBooking;
  42 +
  43 + @Schema(description = "预定需审批", example = "false", requiredMode = Schema.RequiredMode.REQUIRED)
  44 + private Boolean needApproval;
  45 +
  46 + @Schema(description = "可预定范围", example = "0", requiredMode = Schema.RequiredMode.REQUIRED)
  47 + private Integer bookingScope;
  48 +
  49 + @Schema(description = "可预定成员用户编号列表", example = "[1,2]")
  50 + private List<Long> bookingUserIds;
  51 +
  52 + @Schema(description = "显示顺序", example = "1", requiredMode = Schema.RequiredMode.REQUIRED)
  53 + private Integer sort;
  54 +
  55 + @Schema(description = "备注", example = "请提前安排会议室")
  56 + private String remark;
  57 +
  58 + @Schema(description = "附件地址列表", example = "[\"https://www.iocoder.cn/meeting.pdf\"]")
  59 + private List<String> fileUrls;
  60 +
  61 + @Schema(description = "负责人姓名", example = "芋艿")
  62 + private String managerName;
  63 +
  64 + @Schema(description = "负责人联系方式", example = "13800138000")
  65 + private String managerPhone;
  66 +
  67 + @Schema(description = "创建时间", example = "1789347600000", requiredMode = Schema.RequiredMode.REQUIRED, type = "integer", format = "int64")
  68 + private LocalDateTime createTime;
  69 +
  70 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/meetingroom/vo/room/OaMeetingRoomSaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.meetingroom.vo.room;
  2 +
  3 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  4 +import cn.iocoder.yudao.module.oa.enums.meetingroom.*;
  5 +import io.swagger.v3.oas.annotations.media.Schema;
  6 +import jakarta.validation.constraints.*;
  7 +import lombok.Data;
  8 +
  9 +import java.util.List;
  10 +
  11 +@Schema(description = "管理后台 - 会议室新增/修改 Request VO")
  12 +@Data
  13 +public class OaMeetingRoomSaveReqVO {
  14 +
  15 + @Schema(description = "编号", example = "1024")
  16 + private Long id;
  17 +
  18 + @Schema(description = "会议室名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "101 项目讨论室")
  19 + @NotBlank(message = "会议室名称不能为空")
  20 + @Size(max = 100, message = "会议室名称长度不能超过 {max} 个字符")
  21 + private String name;
  22 +
  23 + @Schema(description = "位置", requiredMode = Schema.RequiredMode.REQUIRED, example = "办公楼一层 101")
  24 + @NotBlank(message = "位置不能为空")
  25 + @Size(max = 255, message = "位置长度不能超过 {max} 个字符")
  26 + private String location;
  27 +
  28 + @Schema(description = "会议室类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  29 + @NotNull(message = "类型不能为空")
  30 + @InEnum(value = OaMeetingRoomTypeEnum.class, message = "会议室类型不正确")
  31 + private Integer type;
  32 +
  33 + @Schema(description = "负责人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  34 + @NotNull(message = "负责人不能为空")
  35 + private Long managerUserId;
  36 +
  37 + @Schema(description = "可用状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  38 + @NotNull(message = "可用状态不能为空")
  39 + @InEnum(value = OaMeetingRoomStatusEnum.class, message = "可用状态不正确")
  40 + private Integer status;
  41 +
  42 + @Schema(description = "会议室图片", example = "https://www.iocoder.cn/meeting-room.jpg")
  43 + @Size(max = 1024, message = "会议室图片长度不能超过 {max} 个字符")
  44 + private String picUrl;
  45 +
  46 + @Schema(description = "坐席数", example = "8")
  47 + @Min(value = 1, message = "坐席数不能小于 {value}")
  48 + private Integer seatCount;
  49 +
  50 + @Schema(description = "设备列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1,2]")
  51 + @NotNull(message = "设备列表不能为空")
  52 + private List<@NotNull(message = "设备列表不能为空") Integer> equipments;
  53 +
  54 + @Schema(description = "允许预定", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
  55 + @NotNull(message = "允许预定不能为空")
  56 + private Boolean allowBooking;
  57 +
  58 + @Schema(description = "预定需审批", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
  59 + @NotNull(message = "预定需审批不能为空")
  60 + private Boolean needApproval;
  61 +
  62 + @Schema(description = "可预定范围", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  63 + @NotNull(message = "可预定范围不能为空")
  64 + @InEnum(value = OaMeetingRoomBookingScopeEnum.class, message = "可预定范围不正确")
  65 + private Integer bookingScope;
  66 +
  67 + @Schema(description = "可预定成员用户编号列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1,2]")
  68 + @NotNull(message = "可预定成员不能为空")
  69 + private List<@NotNull(message = "可预定成员不能为空") Long> bookingUserIds;
  70 +
  71 + @Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  72 + @NotNull(message = "显示顺序不能为空")
  73 + private Integer sort;
  74 +
  75 + @Schema(description = "备注", example = "请提前安排会议室")
  76 + @Size(max = 200, message = "备注长度不能超过 {max} 个字符")
  77 + private String remark;
  78 +
  79 + @Schema(description = "附件地址列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[\"https://www.iocoder.cn/meeting.pdf\"]")
  80 + @NotNull(message = "附件地址列表不能为空")
  81 + private List<@NotBlank(message = "附件地址列表不能为空") String> fileUrls;
  82 +
  83 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/note/OaNoteCategoryController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.note;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  5 +import cn.iocoder.yudao.module.oa.controller.admin.note.vo.category.OaNoteCategoryRespVO;
  6 +import cn.iocoder.yudao.module.oa.controller.admin.note.vo.category.OaNoteCategorySaveReqVO;
  7 +import cn.iocoder.yudao.module.oa.dal.dataobject.note.OaNoteCategoryDO;
  8 +import cn.iocoder.yudao.module.oa.service.note.OaNoteCategoryService;
  9 +import io.swagger.v3.oas.annotations.Operation;
  10 +import io.swagger.v3.oas.annotations.Parameter;
  11 +import io.swagger.v3.oas.annotations.tags.Tag;
  12 +import jakarta.annotation.Resource;
  13 +import jakarta.validation.Valid;
  14 +import org.springframework.security.access.prepost.PreAuthorize;
  15 +import org.springframework.validation.annotation.Validated;
  16 +import org.springframework.web.bind.annotation.DeleteMapping;
  17 +import org.springframework.web.bind.annotation.GetMapping;
  18 +import org.springframework.web.bind.annotation.PostMapping;
  19 +import org.springframework.web.bind.annotation.PutMapping;
  20 +import org.springframework.web.bind.annotation.RequestBody;
  21 +import org.springframework.web.bind.annotation.RequestMapping;
  22 +import org.springframework.web.bind.annotation.RequestParam;
  23 +import org.springframework.web.bind.annotation.RestController;
  24 +
  25 +import java.util.List;
  26 +
  27 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  28 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  29 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  30 +
  31 +@Tag(name = "管理后台 - OA 笔记目录")
  32 +@RestController
  33 +@RequestMapping("/oa/note-category")
  34 +@Validated
  35 +public class OaNoteCategoryController {
  36 +
  37 + @Resource
  38 + private OaNoteCategoryService noteCategoryService;
  39 +
  40 + // ==================== 目录维护 ====================
  41 +
  42 + @PostMapping("/create")
  43 + @Operation(summary = "创建笔记目录")
  44 + public CommonResult<Long> createNoteCategory(@Valid @RequestBody OaNoteCategorySaveReqVO createReqVO) {
  45 + return success(noteCategoryService.createNoteCategory(createReqVO, getLoginUserId()));
  46 + }
  47 +
  48 + @PutMapping("/update")
  49 + @Operation(summary = "更新笔记目录")
  50 + public CommonResult<Boolean> updateNoteCategory(@Valid @RequestBody OaNoteCategorySaveReqVO updateReqVO) {
  51 + noteCategoryService.updateNoteCategory(updateReqVO, getLoginUserId());
  52 + return success(true);
  53 + }
  54 +
  55 + @DeleteMapping("/delete")
  56 + @Operation(summary = "删除笔记目录")
  57 + @Parameter(name = "id", description = "目录编号", required = true, example = "1024")
  58 + public CommonResult<Boolean> deleteNoteCategory(@RequestParam("id") Long id) {
  59 + noteCategoryService.deleteNoteCategory(id, getLoginUserId());
  60 + return success(true);
  61 + }
  62 +
  63 + // ==================== 目录查询 ====================
  64 +
  65 + @GetMapping("/get")
  66 + @Operation(summary = "获得笔记目录")
  67 + @Parameter(name = "id", description = "目录编号", required = true, example = "1024")
  68 + @PreAuthorize("@ss.hasPermission('oa:note:query')")
  69 + public CommonResult<OaNoteCategoryRespVO> getNoteCategory(@RequestParam("id") Long id) {
  70 + OaNoteCategoryDO category = noteCategoryService.getNoteCategory(id, getLoginUserId());
  71 + return success(BeanUtils.toBean(category, OaNoteCategoryRespVO.class));
  72 + }
  73 +
  74 + @GetMapping("/simple-list")
  75 + @Operation(summary = "获得笔记目录精简列表")
  76 + public CommonResult<List<OaNoteCategoryRespVO>> getSimpleNoteCategoryList() {
  77 + List<OaNoteCategoryDO> categories = noteCategoryService.getNoteCategoryList(getLoginUserId());
  78 + return success(convertList(categories, category -> new OaNoteCategoryRespVO()
  79 + .setId(category.getId()).setName(category.getName())));
  80 + }
  81 +
  82 + @GetMapping("/list")
  83 + @Operation(summary = "获得笔记目录列表")
  84 + @PreAuthorize("@ss.hasPermission('oa:note:query')")
  85 + public CommonResult<List<OaNoteCategoryRespVO>> getNoteCategoryList() {
  86 + List<OaNoteCategoryDO> categories = noteCategoryService.getNoteCategoryList(getLoginUserId());
  87 + return success(BeanUtils.toBean(categories, OaNoteCategoryRespVO.class));
  88 + }
  89 +
  90 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/note/OaNoteController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.note;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.ObjectUtil;
  5 +import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
  6 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  7 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  8 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  9 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  10 +import cn.iocoder.yudao.module.oa.controller.admin.note.vo.OaNoteFavoriteUpdateReqVO;
  11 +import cn.iocoder.yudao.module.oa.controller.admin.note.vo.OaNotePageReqVO;
  12 +import cn.iocoder.yudao.module.oa.controller.admin.note.vo.OaNoteRespVO;
  13 +import cn.iocoder.yudao.module.oa.controller.admin.note.vo.OaNoteSaveReqVO;
  14 +import cn.iocoder.yudao.module.oa.controller.admin.note.vo.OaNoteShareUpdateReqVO;
  15 +import cn.iocoder.yudao.module.oa.dal.dataobject.note.OaNoteCategoryDO;
  16 +import cn.iocoder.yudao.module.oa.dal.dataobject.note.OaNoteDO;
  17 +import cn.iocoder.yudao.module.oa.service.note.OaNoteCategoryService;
  18 +import cn.iocoder.yudao.module.oa.service.note.OaNoteService;
  19 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  20 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  21 +import io.swagger.v3.oas.annotations.Operation;
  22 +import io.swagger.v3.oas.annotations.Parameter;
  23 +import io.swagger.v3.oas.annotations.tags.Tag;
  24 +import jakarta.annotation.Resource;
  25 +import jakarta.validation.Valid;
  26 +import org.springframework.security.access.prepost.PreAuthorize;
  27 +import org.springframework.validation.annotation.Validated;
  28 +import org.springframework.web.bind.annotation.DeleteMapping;
  29 +import org.springframework.web.bind.annotation.GetMapping;
  30 +import org.springframework.web.bind.annotation.PostMapping;
  31 +import org.springframework.web.bind.annotation.PutMapping;
  32 +import org.springframework.web.bind.annotation.RequestBody;
  33 +import org.springframework.web.bind.annotation.RequestMapping;
  34 +import org.springframework.web.bind.annotation.RequestParam;
  35 +import org.springframework.web.bind.annotation.RestController;
  36 +
  37 +import java.util.Collections;
  38 +import java.util.List;
  39 +import java.util.Map;
  40 +import java.util.Set;
  41 +
  42 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  43 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  44 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
  45 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  46 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
  47 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  48 +
  49 +@Tag(name = "管理后台 - OA 笔记")
  50 +@RestController
  51 +@RequestMapping("/oa/note")
  52 +@Validated
  53 +public class OaNoteController {
  54 +
  55 + @Resource
  56 + private OaNoteService noteService;
  57 + @Resource
  58 + private OaNoteCategoryService noteCategoryService;
  59 + @Resource
  60 + private AdminUserApi adminUserApi;
  61 +
  62 + // ==================== 我的笔记 ====================
  63 +
  64 + @PostMapping("/create")
  65 + @Operation(summary = "创建笔记")
  66 + @PreAuthorize("@ss.hasPermission('oa:note:create')")
  67 + public CommonResult<Long> createNote(@Valid @RequestBody OaNoteSaveReqVO createReqVO) {
  68 + return success(noteService.createNote(createReqVO, getLoginUserId()));
  69 + }
  70 +
  71 + @PutMapping("/update")
  72 + @Operation(summary = "更新笔记")
  73 + @PreAuthorize("@ss.hasPermission('oa:note:update')")
  74 + public CommonResult<Boolean> updateNote(@Valid @RequestBody OaNoteSaveReqVO updateReqVO) {
  75 + noteService.updateNote(updateReqVO, getLoginUserId());
  76 + return success(true);
  77 + }
  78 +
  79 + @DeleteMapping("/delete")
  80 + @Operation(summary = "删除笔记")
  81 + @Parameter(name = "id", description = "笔记编号", required = true, example = "1024")
  82 + @PreAuthorize("@ss.hasPermission('oa:note:delete')")
  83 + public CommonResult<Boolean> deleteNote(@RequestParam("id") Long id) {
  84 + noteService.deleteNote(id, getLoginUserId());
  85 + return success(true);
  86 + }
  87 +
  88 + @DeleteMapping("/delete-received")
  89 + @Operation(summary = "移除收到的共享笔记")
  90 + @Parameter(name = "id", description = "笔记编号", required = true, example = "1024")
  91 + public CommonResult<Boolean> deleteReceivedNote(@RequestParam("id") Long id) {
  92 + noteService.deleteReceivedNote(id, getLoginUserId());
  93 + return success(true);
  94 + }
  95 +
  96 + @PutMapping("/update-favorite")
  97 + @Operation(summary = "更新笔记收藏状态")
  98 + public CommonResult<Boolean> updateNoteFavorite(@Valid @RequestBody OaNoteFavoriteUpdateReqVO updateReqVO) {
  99 + noteService.updateNoteFavorite(updateReqVO.getId(), updateReqVO.getFavorite(), getLoginUserId());
  100 + return success(true);
  101 + }
  102 +
  103 + @PutMapping("/update-share")
  104 + @Operation(summary = "更新笔记共享接收人")
  105 + @PreAuthorize("@ss.hasPermission('oa:note:update')")
  106 + public CommonResult<Boolean> updateNoteShare(@Valid @RequestBody OaNoteShareUpdateReqVO updateReqVO) {
  107 + noteService.updateNoteShare(updateReqVO.getId(), updateReqVO.getReceiverUserIds(), getLoginUserId());
  108 + return success(true);
  109 + }
  110 +
  111 + // ==================== 公共查询(我的笔记、共享给我) ====================
  112 +
  113 + @GetMapping("/get")
  114 + @Operation(summary = "获得笔记详情")
  115 + @Parameter(name = "id", description = "笔记编号", required = true, example = "1024")
  116 + @PreAuthorize("@ss.hasPermission('oa:note:query')")
  117 + public CommonResult<OaNoteRespVO> getNote(@RequestParam("id") Long id) {
  118 + OaNoteDO note = noteService.getNote(id, getLoginUserId());
  119 + return success(buildNoteRespVO(note));
  120 + }
  121 +
  122 + @GetMapping("/my-page")
  123 + @Operation(summary = "获得我的笔记分页")
  124 + @PreAuthorize("@ss.hasPermission('oa:note:query')")
  125 + public CommonResult<PageResult<OaNoteRespVO>> getMyNotePage(@Valid OaNotePageReqVO pageReqVO) {
  126 + PageResult<OaNoteDO> pageResult = noteService.getMyNotePage(pageReqVO, getLoginUserId());
  127 + return success(new PageResult<>(buildNoteRespVOList(pageResult.getList()), pageResult.getTotal()));
  128 + }
  129 +
  130 + @GetMapping("/received-page")
  131 + @Operation(summary = "获得共享给我的笔记分页")
  132 + @PreAuthorize("@ss.hasPermission('oa:note:query')")
  133 + public CommonResult<PageResult<OaNoteRespVO>> getReceivedNotePage(@Valid OaNotePageReqVO pageReqVO) {
  134 + PageResult<OaNoteDO> pageResult = noteService.getReceivedNotePage(pageReqVO, getLoginUserId());
  135 + return success(new PageResult<>(buildNoteRespVOList(pageResult.getList()), pageResult.getTotal()));
  136 + }
  137 +
  138 + // ==================== 拼接 VO ====================
  139 +
  140 + /**
  141 + * 拼接笔记详情
  142 + *
  143 + * @param note 笔记
  144 + * @return 笔记响应
  145 + */
  146 + private OaNoteRespVO buildNoteRespVO(OaNoteDO note) {
  147 + if (note == null) {
  148 + return null;
  149 + }
  150 + return CollUtil.getFirst(buildNoteRespVOList(Collections.singletonList(note)));
  151 + }
  152 +
  153 + /**
  154 + * 拼接笔记响应列表
  155 + *
  156 + * @param notes 笔记列表
  157 + * @return 笔记响应列表
  158 + */
  159 + private List<OaNoteRespVO> buildNoteRespVOList(List<OaNoteDO> notes) {
  160 + if (CollUtil.isEmpty(notes)) {
  161 + return Collections.emptyList();
  162 + }
  163 + // 1. 查询接收关系、用户及目录信息
  164 + Map<Long, List<Long>> receiverUserIdListMap = noteService.getNoteReceiverUserIdListMap(
  165 + convertSet(notes, OaNoteDO::getId));
  166 + Set<Long> userIds = convertSet(notes, note -> NumberUtils.parseLong(note.getCreator()));
  167 + receiverUserIdListMap.values().forEach(userIds::addAll);
  168 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
  169 + Map<Long, OaNoteCategoryDO> categoryMap = noteCategoryService.getNoteCategoryMap(convertSet(notes, OaNoteDO::getCategoryId));
  170 + // 2. 转换笔记并拼接展示字段
  171 + return convertList(notes, noteDO -> {
  172 + OaNoteRespVO note = BeanUtils.toBean(noteDO, OaNoteRespVO.class)
  173 + .setCreatorUserId(NumberUtils.parseLong(noteDO.getCreator()));
  174 + MapUtils.findAndThen(userMap, note.getCreatorUserId(), user -> note.setCreatorUserName(user.getNickname()));
  175 + MapUtils.findAndThen(categoryMap, note.getCategoryId(), category -> note.setCategoryName(category.getName()));
  176 + // 持有关系包含创建人,共享名单仅展示其他接收人
  177 + List<Long> receiverUserIds = filterList(receiverUserIdListMap.getOrDefault(note.getId(), Collections.emptyList()),
  178 + userId -> ObjectUtil.notEqual(userId, note.getCreatorUserId()));
  179 + note.setReceiverUserIds(receiverUserIds);
  180 + note.setReceiverUserNames(convertList(receiverUserIds, receiverUserId -> {
  181 + AdminUserRespDTO receiverUser = userMap.get(receiverUserId);
  182 + return receiverUser != null ? receiverUser.getNickname() : null;
  183 + }));
  184 + return note;
  185 + });
  186 + }
  187 +
  188 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/note/vo/OaNoteFavoriteUpdateReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.note.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotNull;
  5 +import lombok.Data;
  6 +
  7 +@Schema(description = "管理后台 - OA 笔记收藏状态修改 Request VO")
  8 +@Data
  9 +public class OaNoteFavoriteUpdateReqVO {
  10 +
  11 + @Schema(description = "笔记编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  12 + @NotNull(message = "笔记编号不能为空")
  13 + private Long id;
  14 +
  15 + @Schema(description = "是否收藏", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
  16 + @NotNull(message = "收藏状态不能为空")
  17 + private Boolean favorite;
  18 +
  19 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/note/vo/OaNotePageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.note.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  5 +import cn.iocoder.yudao.module.oa.enums.note.OaNoteTypeEnum;
  6 +import cn.iocoder.yudao.module.oa.enums.schedule.OaPriorityEnum;
  7 +import io.swagger.v3.oas.annotations.media.Schema;
  8 +import lombok.Data;
  9 +import lombok.EqualsAndHashCode;
  10 +import org.springframework.format.annotation.DateTimeFormat;
  11 +
  12 +import java.time.LocalDateTime;
  13 +
  14 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  15 +
  16 +@Schema(description = "管理后台 - OA 笔记分页 Request VO")
  17 +@Data
  18 +@EqualsAndHashCode(callSuper = true)
  19 +public class OaNotePageReqVO extends PageParam {
  20 +
  21 + @Schema(description = "标题", example = "会议纪要")
  22 + private String title;
  23 +
  24 + @Schema(description = "目录编号", example = "1024")
  25 + private Long categoryId;
  26 +
  27 + @Schema(description = "笔记类型", example = "1")
  28 + @InEnum(value = OaNoteTypeEnum.class, message = "笔记类型必须是 {value}")
  29 + private Integer type;
  30 +
  31 + @Schema(description = "优先级", example = "1")
  32 + @InEnum(value = OaPriorityEnum.class, message = "优先级必须是 {value}")
  33 + private Integer priority;
  34 +
  35 + @Schema(description = "是否收藏", example = "false")
  36 + private Boolean favorite;
  37 +
  38 + @Schema(description = "创建时间范围", example = "[\"2026-09-14 00:00:00\", \"2026-09-14 23:59:59\"]")
  39 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  40 + private LocalDateTime[] createTime;
  41 +
  42 +
  43 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/note/vo/OaNoteRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.note.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +import java.util.List;
  8 +
  9 +@Schema(description = "管理后台 - OA 笔记 Response VO")
  10 +@Data
  11 +public class OaNoteRespVO {
  12 +
  13 + @Schema(description = "笔记编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  14 + private Long id;
  15 +
  16 + @Schema(description = "创建人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  17 + private Long creatorUserId;
  18 +
  19 + @Schema(description = "创建人用户昵称", example = "芋道源码")
  20 + private String creatorUserName;
  21 +
  22 + @Schema(description = "目录编号", example = "1024")
  23 + private Long categoryId;
  24 +
  25 + @Schema(description = "目录名称", example = "工作")
  26 + private String categoryName;
  27 +
  28 + @Schema(description = "笔记类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  29 + private Integer type;
  30 +
  31 + @Schema(description = "优先级", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  32 + private Integer priority;
  33 +
  34 + @Schema(description = "标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "项目会议纪要")
  35 + private String title;
  36 +
  37 + @Schema(description = "内容", example = "记录本周会议结论与待办事项。")
  38 + private String content;
  39 +
  40 + @Schema(description = "是否收藏", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
  41 + private Boolean favorite;
  42 +
  43 + @Schema(description = "附件地址列表", example = "[\"https://example.com/file.pdf\"]")
  44 + private List<String> fileUrls;
  45 +
  46 + @Schema(description = "接收人用户编号列表", example = "[1, 2]")
  47 + private List<Long> receiverUserIds;
  48 +
  49 + @Schema(description = "接收人用户昵称列表", example = "[\"张三\", \"李四\"]")
  50 + private List<String> receiverUserNames;
  51 +
  52 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, type = "integer", format = "int64", example = "1789347600000")
  53 + private LocalDateTime createTime;
  54 +
  55 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/note/vo/OaNoteSaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.note.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  4 +import cn.iocoder.yudao.module.oa.enums.note.OaNoteTypeEnum;
  5 +import cn.iocoder.yudao.module.oa.enums.schedule.OaPriorityEnum;
  6 +import io.swagger.v3.oas.annotations.media.Schema;
  7 +import jakarta.validation.constraints.NotBlank;
  8 +import jakarta.validation.constraints.Max;
  9 +import jakarta.validation.constraints.NotNull;
  10 +import jakarta.validation.constraints.Size;
  11 +import lombok.Data;
  12 +
  13 +import java.util.List;
  14 +
  15 +@Schema(description = "管理后台 - OA 笔记新增/修改 Request VO")
  16 +@Data
  17 +public class OaNoteSaveReqVO {
  18 +
  19 + @Schema(description = "笔记编号", example = "1024")
  20 + private Long id;
  21 +
  22 + @Schema(description = "目录编号", example = "1024")
  23 + private Long categoryId;
  24 +
  25 + @Schema(description = "笔记类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  26 + @NotNull(message = "笔记类型不能为空")
  27 + @InEnum(value = OaNoteTypeEnum.class, message = "笔记类型必须是 {value}")
  28 + private Integer type;
  29 +
  30 + @Schema(description = "优先级", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  31 + @NotNull(message = "优先级不能为空")
  32 + @InEnum(value = OaPriorityEnum.class, message = "优先级必须是 {value}")
  33 + @Max(value = 2, message = "笔记优先级只能为一般或重要")
  34 + private Integer priority;
  35 +
  36 + @Schema(description = "标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "项目会议纪要")
  37 + @NotBlank(message = "标题不能为空")
  38 + @Size(max = 255, message = "标题长度不能超过 255 个字符")
  39 + private String title;
  40 +
  41 + @Schema(description = "内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "记录本周会议结论与待办事项。")
  42 + @NotBlank(message = "笔记内容不能为空")
  43 + private String content;
  44 +
  45 + @Schema(description = "附件地址列表", example = "[\"https://example.com/file.pdf\"]")
  46 + private List<@NotBlank(message = "附件地址不能为空") @Size(max = 512, message = "附件地址长度不能超过 512 个字符") String> fileUrls;
  47 +
  48 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/note/vo/OaNoteShareUpdateReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.note.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotNull;
  5 +import lombok.Data;
  6 +
  7 +import java.util.List;
  8 +
  9 +@Schema(description = "管理后台 - OA 笔记共享修改 Request VO")
  10 +@Data
  11 +public class OaNoteShareUpdateReqVO {
  12 +
  13 + @Schema(description = "笔记编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  14 + @NotNull(message = "笔记编号不能为空")
  15 + private Long id;
  16 +
  17 + @Schema(description = "接收人用户编号列表,空列表表示取消全部共享", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 2]")
  18 + @NotNull(message = "接收人列表不能为空")
  19 + private List<@NotNull(message = "接收人编号不能为空") Long> receiverUserIds;
  20 +
  21 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/note/vo/category/OaNoteCategoryRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.note.vo.category;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +@Schema(description = "管理后台 - OA 笔记目录 Response VO")
  9 +@Data
  10 +public class OaNoteCategoryRespVO {
  11 +
  12 + @Schema(description = "目录编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  13 + private Long id;
  14 +
  15 + @Schema(description = "目录名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "工作")
  16 + private String name;
  17 +
  18 + @Schema(description = "显示顺序", example = "10")
  19 + private Integer sort;
  20 +
  21 + @Schema(description = "创建时间", type = "integer", format = "int64", example = "1789347600000")
  22 + private LocalDateTime createTime;
  23 +
  24 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/note/vo/category/OaNoteCategorySaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.note.vo.category;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotBlank;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import jakarta.validation.constraints.Size;
  7 +import lombok.Data;
  8 +
  9 +@Schema(description = "管理后台 - OA 笔记目录新增/修改 Request VO")
  10 +@Data
  11 +public class OaNoteCategorySaveReqVO {
  12 +
  13 + @Schema(description = "目录编号", example = "1024")
  14 + private Long id;
  15 +
  16 + @Schema(description = "目录名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "工作")
  17 + @NotBlank(message = "目录名称不能为空")
  18 + @Size(max = 255, message = "目录名称长度不能超过 255 个字符")
  19 + private String name;
  20 +
  21 + @Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
  22 + @NotNull(message = "显示顺序不能为空")
  23 + private Integer sort;
  24 +
  25 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/officialdoc/OaOfficialDocReceiveController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.officialdoc;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  7 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  8 +import cn.iocoder.yudao.module.oa.controller.admin.officialdoc.vo.receive.OaOfficialDocReceivePageReqVO;
  9 +import cn.iocoder.yudao.module.oa.controller.admin.officialdoc.vo.receive.OaOfficialDocReceiveRespVO;
  10 +import cn.iocoder.yudao.module.oa.controller.admin.officialdoc.vo.receive.OaOfficialDocReceiveSaveReqVO;
  11 +import cn.iocoder.yudao.module.oa.dal.dataobject.officialdoc.OaOfficialDocReceiveDO;
  12 +import cn.iocoder.yudao.module.oa.dal.dataobject.officialdoc.OaOfficialDocSendDO;
  13 +import cn.iocoder.yudao.module.oa.service.officialdoc.OaOfficialDocReceiveService;
  14 +import cn.iocoder.yudao.module.oa.service.officialdoc.OaOfficialDocSendService;
  15 +import cn.iocoder.yudao.module.system.api.dept.DeptApi;
  16 +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
  17 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  18 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  19 +import io.swagger.v3.oas.annotations.Operation;
  20 +import io.swagger.v3.oas.annotations.Parameter;
  21 +import io.swagger.v3.oas.annotations.tags.Tag;
  22 +import jakarta.annotation.Resource;
  23 +import jakarta.validation.Valid;
  24 +import org.springframework.security.access.prepost.PreAuthorize;
  25 +import org.springframework.validation.annotation.Validated;
  26 +import org.springframework.web.bind.annotation.*;
  27 +
  28 +import java.util.Collections;
  29 +import java.util.List;
  30 +import java.util.Map;
  31 +import java.util.stream.Stream;
  32 +
  33 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  34 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  35 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  36 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap;
  37 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  38 +
  39 +@Tag(name = "管理后台 - 公文收文")
  40 +@RestController
  41 +@RequestMapping("/oa/officialdoc-receive")
  42 +@Validated
  43 +public class OaOfficialDocReceiveController {
  44 +
  45 + @Resource
  46 + private OaOfficialDocReceiveService officialDocReceiveService;
  47 + @Resource
  48 + private OaOfficialDocSendService officialDocSendService;
  49 +
  50 + @Resource
  51 + private AdminUserApi adminUserApi;
  52 + @Resource
  53 + private DeptApi deptApi;
  54 +
  55 + @PostMapping("/create")
  56 + @Operation(summary = "创建公文收文")
  57 + @PreAuthorize("@ss.hasPermission('oa:officialdoc-receive:create')")
  58 + public CommonResult<Long> createOfficialDocReceive(@Valid @RequestBody OaOfficialDocReceiveSaveReqVO reqVO) {
  59 + return success(officialDocReceiveService.createOfficialDocReceive(reqVO));
  60 + }
  61 +
  62 + @PutMapping("/update")
  63 + @Operation(summary = "更新公文收文")
  64 + @PreAuthorize("@ss.hasPermission('oa:officialdoc-receive:update')")
  65 + public CommonResult<Boolean> updateOfficialDocReceive(@Valid @RequestBody OaOfficialDocReceiveSaveReqVO reqVO) {
  66 + officialDocReceiveService.updateOfficialDocReceive(reqVO, getLoginUserId());
  67 + return success(true);
  68 + }
  69 +
  70 + @DeleteMapping("/delete")
  71 + @Operation(summary = "删除公文收文")
  72 + @Parameter(name = "id", description = "收文编号", required = true, example = "1024")
  73 + @PreAuthorize("@ss.hasPermission('oa:officialdoc-receive:delete')")
  74 + public CommonResult<Boolean> deleteOfficialDocReceive(@RequestParam("id") Long id) {
  75 + officialDocReceiveService.deleteOfficialDocReceive(id, getLoginUserId());
  76 + return success(true);
  77 + }
  78 +
  79 + @PostMapping("/submit")
  80 + @Operation(summary = "提交公文收文审批")
  81 + @Parameter(name = "id", description = "收文编号", required = true, example = "1024")
  82 + @PreAuthorize("@ss.hasPermission('oa:officialdoc-receive:update')")
  83 + public CommonResult<Boolean> submitOfficialDocReceive(@RequestParam("id") Long id) {
  84 + officialDocReceiveService.submitOfficialDocReceive(id, getLoginUserId());
  85 + return success(true);
  86 + }
  87 +
  88 + @PutMapping("/cancel")
  89 + @Operation(summary = "撤销公文收文审批")
  90 + @Parameter(name = "id", description = "收文编号", required = true, example = "1024")
  91 + @PreAuthorize("@ss.hasPermission('oa:officialdoc-receive:update')")
  92 + public CommonResult<Boolean> cancelOfficialDocReceive(@RequestParam("id") Long id) {
  93 + officialDocReceiveService.cancelOfficialDocReceive(id, getLoginUserId());
  94 + return success(true);
  95 + }
  96 +
  97 + @PutMapping("/claim")
  98 + @Operation(summary = "签收公文收文")
  99 + @Parameter(name = "id", description = "收文编号", required = true, example = "1024")
  100 + @PreAuthorize("@ss.hasPermission('oa:officialdoc-receive:update')")
  101 + public CommonResult<Boolean> claimOfficialDocReceive(@RequestParam("id") Long id) {
  102 + officialDocReceiveService.claimOfficialDocReceive(id, getLoginUserId());
  103 + return success(true);
  104 + }
  105 +
  106 + @GetMapping("/page")
  107 + @Operation(summary = "获得公文收文分页列表")
  108 + @PreAuthorize("@ss.hasPermission('oa:officialdoc-receive:query')")
  109 + public CommonResult<PageResult<OaOfficialDocReceiveRespVO>> getOfficialDocReceivePage(
  110 + @Valid OaOfficialDocReceivePageReqVO pageReqVO) {
  111 + PageResult<OaOfficialDocReceiveDO> pageResult = officialDocReceiveService.getOfficialDocReceivePage(pageReqVO, getLoginUserId());
  112 + return success(new PageResult<>(buildOfficialDocReceiveRespVOList(pageResult.getList()), pageResult.getTotal()));
  113 + }
  114 +
  115 + @GetMapping("/get")
  116 + @Operation(summary = "获得公文收文详情")
  117 + @Parameter(name = "id", description = "收文编号", required = true, example = "1024")
  118 + @PreAuthorize("@ss.hasPermission('oa:officialdoc-receive:query')")
  119 + public CommonResult<OaOfficialDocReceiveRespVO> getOfficialDocReceive(@RequestParam("id") Long id) {
  120 + OaOfficialDocReceiveDO receive = officialDocReceiveService.getOfficialDocReceive(id);
  121 + return success(buildOfficialDocReceiveRespVO(receive));
  122 + }
  123 +
  124 + // ==================== 拼接 VO ====================
  125 +
  126 + /**
  127 + * 拼接收文详情及关联发文信息
  128 + *
  129 + * @param receive 收文
  130 + * @return 收文详情
  131 + */
  132 + private OaOfficialDocReceiveRespVO buildOfficialDocReceiveRespVO(OaOfficialDocReceiveDO receive) {
  133 + if (receive == null) {
  134 + return null;
  135 + }
  136 + return CollUtil.getFirst(buildOfficialDocReceiveRespVOList(Collections.singletonList(receive)));
  137 + }
  138 +
  139 + /**
  140 + * 拼接公文收文的用户、部门名称及关联发文信息
  141 + *
  142 + * @param receives 公文收文列表
  143 + * @return 公文收文响应列表
  144 + */
  145 + private List<OaOfficialDocReceiveRespVO> buildOfficialDocReceiveRespVOList(List<OaOfficialDocReceiveDO> receives) {
  146 + if (CollUtil.isEmpty(receives)) {
  147 + return Collections.emptyList();
  148 + }
  149 + // 1.1 批量查询关联发文,手工收文无需查询
  150 + Map<Long, OaOfficialDocSendDO> sendMap = officialDocSendService.getOfficialDocSendMap(
  151 + convertSet(receives, OaOfficialDocReceiveDO::getSendId));
  152 + // 1.2 批量查询主办人及签发人
  153 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertSetByFlatMap(receives, receive -> {
  154 + OaOfficialDocSendDO send = sendMap.get(receive.getSendId());
  155 + return Stream.of(receive.getHandlerUserId(), send != null ? send.getSignerUserId() : null);
  156 + }));
  157 + // 1.3 批量查询收发文部门
  158 + Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSetByFlatMap(receives, receive -> {
  159 + OaOfficialDocSendDO send = sendMap.get(receive.getSendId());
  160 + return Stream.of(receive.getReceiveDeptId(), send != null ? send.getSendDeptId() : null);
  161 + }));
  162 + // TODO DONE @AI:移除重复解释实现方式的注释,OA 同类说明同步精简。
  163 + // 2. 转换并补充展示字段
  164 + return convertList(receives, receive -> {
  165 + OaOfficialDocReceiveRespVO respVO = BeanUtils.toBean(receive, OaOfficialDocReceiveRespVO.class);
  166 + MapUtils.findAndThen(userMap, receive.getHandlerUserId(), user -> respVO.setHandlerName(user.getNickname()));
  167 + MapUtils.findAndThen(deptMap, receive.getReceiveDeptId(), dept -> respVO.setReceiveDeptName(dept.getName()));
  168 + MapUtils.findAndThen(sendMap, receive.getSendId(), send -> {
  169 + MapUtils.findAndThen(deptMap, send.getSendDeptId(), dept -> respVO.setSendDeptName(dept.getName()));
  170 + MapUtils.findAndThen(userMap, send.getSignerUserId(), user -> respVO.setSignerName(user.getNickname()));
  171 + respVO.setIssueTime(send.getIssueTime()).setDisclosureType(send.getDisclosureType());
  172 + });
  173 + return respVO;
  174 + });
  175 + }
  176 +
  177 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/officialdoc/OaOfficialDocSendController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.officialdoc;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  7 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  8 +import cn.iocoder.yudao.module.oa.controller.admin.officialdoc.vo.send.OaOfficialDocSendPageReqVO;
  9 +import cn.iocoder.yudao.module.oa.controller.admin.officialdoc.vo.send.OaOfficialDocSendRespVO;
  10 +import cn.iocoder.yudao.module.oa.controller.admin.officialdoc.vo.send.OaOfficialDocSendSaveReqVO;
  11 +import cn.iocoder.yudao.module.oa.dal.dataobject.officialdoc.OaOfficialDocSendDO;
  12 +import cn.iocoder.yudao.module.oa.service.officialdoc.OaOfficialDocSendService;
  13 +import cn.iocoder.yudao.module.system.api.dept.DeptApi;
  14 +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
  15 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  16 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  17 +import io.swagger.v3.oas.annotations.Operation;
  18 +import io.swagger.v3.oas.annotations.Parameter;
  19 +import io.swagger.v3.oas.annotations.tags.Tag;
  20 +import jakarta.annotation.Resource;
  21 +import jakarta.validation.Valid;
  22 +import org.springframework.security.access.prepost.PreAuthorize;
  23 +import org.springframework.validation.annotation.Validated;
  24 +import org.springframework.web.bind.annotation.*;
  25 +
  26 +import java.util.Collections;
  27 +import java.util.List;
  28 +import java.util.Map;
  29 +import java.util.Set;
  30 +
  31 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  32 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  33 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  34 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  35 +
  36 +@Tag(name = "管理后台 - 公文发文")
  37 +@RestController
  38 +@RequestMapping("/oa/officialdoc-send")
  39 +@Validated
  40 +public class OaOfficialDocSendController {
  41 +
  42 + @Resource
  43 + private OaOfficialDocSendService officialDocSendService;
  44 +
  45 + @Resource
  46 + private AdminUserApi adminUserApi;
  47 + @Resource
  48 + private DeptApi deptApi;
  49 +
  50 + @PostMapping("/create")
  51 + @Operation(summary = "创建公文发文")
  52 + @PreAuthorize("@ss.hasPermission('oa:officialdoc-send:create')")
  53 + public CommonResult<Long> createOfficialDocSend(@Valid @RequestBody OaOfficialDocSendSaveReqVO reqVO) {
  54 + return success(officialDocSendService.createOfficialDocSend(reqVO, getLoginUserId()));
  55 + }
  56 +
  57 + @PutMapping("/update")
  58 + @Operation(summary = "更新公文发文")
  59 + @PreAuthorize("@ss.hasPermission('oa:officialdoc-send:update')")
  60 + public CommonResult<Boolean> updateOfficialDocSend(@Valid @RequestBody OaOfficialDocSendSaveReqVO reqVO) {
  61 + officialDocSendService.updateOfficialDocSend(reqVO, getLoginUserId());
  62 + return success(true);
  63 + }
  64 +
  65 + @DeleteMapping("/delete")
  66 + @Operation(summary = "删除公文发文")
  67 + @Parameter(name = "id", description = "发文编号", required = true, example = "1024")
  68 + @PreAuthorize("@ss.hasPermission('oa:officialdoc-send:delete')")
  69 + public CommonResult<Boolean> deleteOfficialDocSend(@RequestParam("id") Long id) {
  70 + officialDocSendService.deleteOfficialDocSend(id, getLoginUserId());
  71 + return success(true);
  72 + }
  73 +
  74 + @PostMapping("/submit")
  75 + @Operation(summary = "提交公文发文审批")
  76 + @Parameter(name = "id", description = "发文编号", required = true, example = "1024")
  77 + @PreAuthorize("@ss.hasPermission('oa:officialdoc-send:update')")
  78 + public CommonResult<Boolean> submitOfficialDocSend(@RequestParam("id") Long id) {
  79 + officialDocSendService.submitOfficialDocSend(id, getLoginUserId());
  80 + return success(true);
  81 + }
  82 +
  83 + @PutMapping("/cancel")
  84 + @Operation(summary = "撤销公文发文审批")
  85 + @Parameter(name = "id", description = "发文编号", required = true, example = "1024")
  86 + @PreAuthorize("@ss.hasPermission('oa:officialdoc-send:update')")
  87 + public CommonResult<Boolean> cancelOfficialDocSend(@RequestParam("id") Long id) {
  88 + officialDocSendService.cancelOfficialDocSend(id, getLoginUserId());
  89 + return success(true);
  90 + }
  91 +
  92 + @GetMapping("/page")
  93 + @Operation(summary = "获得公文发文分页列表")
  94 + @PreAuthorize("@ss.hasPermission('oa:officialdoc-send:query')")
  95 + public CommonResult<PageResult<OaOfficialDocSendRespVO>> getOfficialDocSendPage(
  96 + @Valid OaOfficialDocSendPageReqVO pageReqVO) {
  97 + PageResult<OaOfficialDocSendDO> pageResult = officialDocSendService.getOfficialDocSendPage(pageReqVO, getLoginUserId());
  98 + return success(new PageResult<>(buildOfficialDocSendRespVOList(pageResult.getList()), pageResult.getTotal()));
  99 + }
  100 +
  101 + @GetMapping("/get")
  102 + @Operation(summary = "获得公文发文详情")
  103 + @Parameter(name = "id", description = "发文编号", required = true, example = "1024")
  104 + @PreAuthorize("@ss.hasAnyPermissions('oa:officialdoc-send:query', 'oa:officialdoc-receive:query')")
  105 + public CommonResult<OaOfficialDocSendRespVO> getOfficialDocSend(@RequestParam("id") Long id) {
  106 + OaOfficialDocSendDO send = officialDocSendService.getOfficialDocSend(id);
  107 + return success(buildOfficialDocSendRespVO(send));
  108 + }
  109 +
  110 + // ==================== 拼接 VO ====================
  111 +
  112 + /**
  113 + * 拼接公文发文详情
  114 + *
  115 + * @param send 公文发文
  116 + * @return 公文发文响应
  117 + */
  118 + private OaOfficialDocSendRespVO buildOfficialDocSendRespVO(OaOfficialDocSendDO send) {
  119 + if (send == null) {
  120 + return null;
  121 + }
  122 + return CollUtil.getFirst(buildOfficialDocSendRespVOList(Collections.singletonList(send)));
  123 + }
  124 +
  125 + /**
  126 + * 拼接公文发文的用户、部门名称
  127 + *
  128 + * @param sends 公文发文列表
  129 + * @return 公文发文响应列表
  130 + */
  131 + private List<OaOfficialDocSendRespVO> buildOfficialDocSendRespVOList(List<OaOfficialDocSendDO> sends) {
  132 + if (CollUtil.isEmpty(sends)) {
  133 + return Collections.emptyList();
  134 + }
  135 + // 1.1 收集发文、主送和抄送部门编号
  136 + Set<Long> deptIds = convertSet(sends, OaOfficialDocSendDO::getSendDeptId);
  137 + sends.forEach(send -> {
  138 + if (CollUtil.isNotEmpty(send.getMainDeptIds())) {
  139 + deptIds.addAll(send.getMainDeptIds());
  140 + }
  141 + if (CollUtil.isNotEmpty(send.getCopyDeptIds())) {
  142 + deptIds.addAll(send.getCopyDeptIds());
  143 + }
  144 + });
  145 + // 1.2 批量查询签发人和部门
  146 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertSet(sends, OaOfficialDocSendDO::getSignerUserId));
  147 + Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(deptIds);
  148 +
  149 + // 2. 转换并补充展示字段
  150 + return convertList(sends, send -> {
  151 + OaOfficialDocSendRespVO respVO = BeanUtils.toBean(send, OaOfficialDocSendRespVO.class);
  152 + MapUtils.findAndThen(userMap, send.getSignerUserId(), user -> respVO.setSignerName(user.getNickname()));
  153 + MapUtils.findAndThen(deptMap, send.getSendDeptId(), dept -> respVO.setSendDeptName(dept.getName()));
  154 + respVO.setMainDeptNames(convertList(send.getMainDeptIds(), id -> {
  155 + DeptRespDTO dept = deptMap.get(id);
  156 + return dept != null ? dept.getName() : null;
  157 + }));
  158 + respVO.setCopyDeptNames(convertList(send.getCopyDeptIds(), id -> {
  159 + DeptRespDTO dept = deptMap.get(id);
  160 + return dept != null ? dept.getName() : null;
  161 + }));
  162 + return respVO;
  163 + });
  164 + }
  165 +
  166 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/officialdoc/OaOfficialDocTemplateController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.officialdoc;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  7 +import cn.iocoder.yudao.module.oa.controller.admin.officialdoc.vo.template.OaOfficialDocTemplatePageReqVO;
  8 +import cn.iocoder.yudao.module.oa.controller.admin.officialdoc.vo.template.OaOfficialDocTemplateRespVO;
  9 +import cn.iocoder.yudao.module.oa.controller.admin.officialdoc.vo.template.OaOfficialDocTemplateSaveReqVO;
  10 +import cn.iocoder.yudao.module.oa.dal.dataobject.officialdoc.OaOfficialDocTemplateDO;
  11 +import cn.iocoder.yudao.module.oa.service.officialdoc.OaOfficialDocTemplateService;
  12 +import io.swagger.v3.oas.annotations.Operation;
  13 +import io.swagger.v3.oas.annotations.Parameter;
  14 +import io.swagger.v3.oas.annotations.tags.Tag;
  15 +import jakarta.annotation.Resource;
  16 +import jakarta.validation.Valid;
  17 +import org.springframework.security.access.prepost.PreAuthorize;
  18 +import org.springframework.validation.annotation.Validated;
  19 +import org.springframework.web.bind.annotation.*;
  20 +
  21 +import java.util.List;
  22 +
  23 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  24 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  25 +
  26 +@Tag(name = "管理后台 - 套红模板")
  27 +@RestController
  28 +@RequestMapping("/oa/officialdoc-template")
  29 +@Validated
  30 +public class OaOfficialDocTemplateController {
  31 +
  32 + @Resource
  33 + private OaOfficialDocTemplateService officialDocTemplateService;
  34 +
  35 + @PostMapping("/create")
  36 + @Operation(summary = "创建套红模板")
  37 + @PreAuthorize("@ss.hasPermission('oa:officialdoc-template:create')")
  38 + public CommonResult<Long> createOfficialDocTemplate(@Valid @RequestBody OaOfficialDocTemplateSaveReqVO reqVO) {
  39 + return success(officialDocTemplateService.createOfficialDocTemplate(reqVO));
  40 + }
  41 +
  42 + @PutMapping("/update")
  43 + @Operation(summary = "更新套红模板")
  44 + @PreAuthorize("@ss.hasPermission('oa:officialdoc-template:update')")
  45 + public CommonResult<Boolean> updateOfficialDocTemplate(@Valid @RequestBody OaOfficialDocTemplateSaveReqVO reqVO) {
  46 + officialDocTemplateService.updateOfficialDocTemplate(reqVO);
  47 + return success(true);
  48 + }
  49 +
  50 + @DeleteMapping("/delete")
  51 + @Operation(summary = "删除套红模板")
  52 + @Parameter(name = "id", description = "模板编号", required = true, example = "1024")
  53 + @PreAuthorize("@ss.hasPermission('oa:officialdoc-template:delete')")
  54 + public CommonResult<Boolean> deleteOfficialDocTemplate(@RequestParam("id") Long id) {
  55 + officialDocTemplateService.deleteOfficialDocTemplate(id);
  56 + return success(true);
  57 + }
  58 +
  59 + @GetMapping("/page")
  60 + @Operation(summary = "获得套红模板分页列表")
  61 + @PreAuthorize("@ss.hasPermission('oa:officialdoc-template:query')")
  62 + public CommonResult<PageResult<OaOfficialDocTemplateRespVO>> getOfficialDocTemplatePage(
  63 + @Valid OaOfficialDocTemplatePageReqVO pageReqVO) {
  64 + PageResult<OaOfficialDocTemplateDO> pageResult = officialDocTemplateService.getOfficialDocTemplatePage(pageReqVO);
  65 + return success(BeanUtils.toBean(pageResult, OaOfficialDocTemplateRespVO.class));
  66 + }
  67 +
  68 + @GetMapping("/simple-list")
  69 + @Operation(summary = "获得套红模板精简列表")
  70 + public CommonResult<List<OaOfficialDocTemplateRespVO>> getSimpleOfficialDocTemplateList() {
  71 + List<OaOfficialDocTemplateDO> templates = officialDocTemplateService.getOfficialDocTemplateList(CommonStatusEnum.ENABLE.getStatus());
  72 + return success(convertList(templates, template -> new OaOfficialDocTemplateRespVO()
  73 + .setId(template.getId()).setName(template.getName())));
  74 + }
  75 +
  76 + @GetMapping("/get")
  77 + @Operation(summary = "获得套红模板详情")
  78 + @Parameter(name = "id", description = "模板编号", required = true, example = "1024")
  79 + @PreAuthorize("@ss.hasPermission('oa:officialdoc-template:query')")
  80 + public CommonResult<OaOfficialDocTemplateRespVO> getOfficialDocTemplate(@RequestParam("id") Long id) {
  81 + OaOfficialDocTemplateDO template = officialDocTemplateService.getOfficialDocTemplate(id);
  82 + return success(BeanUtils.toBean(template, OaOfficialDocTemplateRespVO.class));
  83 + }
  84 +
  85 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/officialdoc/vo/receive/OaOfficialDocReceivePageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.officialdoc.vo.receive;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +
  8 +@Schema(description = "管理后台 - 公文收文分页 Request VO")
  9 +@Data
  10 +@EqualsAndHashCode(callSuper = true)
  11 +public class OaOfficialDocReceivePageReqVO extends PageParam {
  12 +
  13 + @Schema(description = "公文标题", example = "关于开展办公用品盘点的通知")
  14 + private String title;
  15 +
  16 + @Schema(description = "来文字号", example = "芋办〔2026〕1号")
  17 + private String documentNo;
  18 +
  19 + @Schema(description = "流程状态", example = "-1")
  20 + private Integer status;
  21 +
  22 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/officialdoc/vo/receive/OaOfficialDocReceiveRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.officialdoc.vo.receive;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import java.time.LocalDateTime;
  6 +import java.util.List;
  7 +
  8 +@Schema(description = "管理后台 - 公文收文 Response VO")
  9 +@Data
  10 +public class OaOfficialDocReceiveRespVO {
  11 +
  12 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  13 + private Long id;
  14 +
  15 + @Schema(description = "来源发文编号", example = "1024")
  16 + private Long sendId;
  17 +
  18 + @Schema(description = "发文部门", example = "行政部")
  19 + private String sendDeptName;
  20 +
  21 + @Schema(description = "发文日期", type = "integer", format = "int64", example = "1789347600000")
  22 + private LocalDateTime issueTime;
  23 +
  24 + @Schema(description = "签发人", example = "张三")
  25 + private String signerName;
  26 +
  27 + @Schema(description = "公开类别", example = "0")
  28 + private Integer disclosureType;
  29 +
  30 + @Schema(description = "收文类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  31 + private Integer receiveType;
  32 +
  33 + @Schema(description = "收文时间", type = "integer", format = "int64", example = "1789347600000")
  34 + private LocalDateTime receiveTime;
  35 +
  36 + @Schema(description = "收文部门编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  37 + private Long receiveDeptId;
  38 +
  39 + @Schema(description = "主办人用户编号", example = "1024")
  40 + private Long handlerUserId;
  41 +
  42 + @Schema(description = "领导批示", example = "请行政部牵头办理")
  43 + private String instruction;
  44 +
  45 + @Schema(description = "办理结果", example = "已完成盘点并归档")
  46 + private String result;
  47 +
  48 + @Schema(description = "办理期限", type = "integer", format = "int64", example = "1789347600000")
  49 + private LocalDateTime deadlineTime;
  50 +
  51 + @Schema(description = "内容摘要", example = "本周已完成办公用品盘点")
  52 + private String summary;
  53 +
  54 + @Schema(description = "备注", example = "请提前联系行政部确认")
  55 + private String remark;
  56 +
  57 + @Schema(description = "附件地址列表", example = "[\"https://example.com/file.pdf\"]")
  58 + private List<String> fileUrls;
  59 +
  60 + @Schema(description = "单据编号", example = "CC202609140001")
  61 + private String no;
  62 +
  63 + @Schema(description = "公文标题", example = "关于开展办公用品盘点的通知")
  64 + private String title;
  65 +
  66 + @Schema(description = "来文字号", example = "芋办〔2026〕1号")
  67 + private String documentNo;
  68 +
  69 + @Schema(description = "密级", example = "0")
  70 + private Integer secrecyLevel;
  71 +
  72 + @Schema(description = "紧急程度", example = "0")
  73 + private Integer urgencyLevel;
  74 +
  75 + @Schema(description = "正式公文地址", example = "https://example.com/file.pdf")
  76 + private String formalFileUrl;
  77 +
  78 + @Schema(description = "收文部门", example = "行政部")
  79 + private String receiveDeptName;
  80 +
  81 + @Schema(description = "主办人", example = "张三")
  82 + private String handlerName;
  83 +
  84 + @Schema(description = "审批状态", example = "-1")
  85 + private Integer status;
  86 +
  87 + @Schema(description = "办理状态", example = "0")
  88 + private Integer handleStatus;
  89 +
  90 + @Schema(description = "流程实例编号", example = "1024")
  91 + private String processInstanceId;
  92 +
  93 + @Schema(description = "创建人编号", example = "1024")
  94 + private String creator;
  95 +
  96 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, type = "integer", format = "int64", example = "1789347600000")
  97 + private LocalDateTime createTime;
  98 +
  99 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/officialdoc/vo/receive/OaOfficialDocReceiveSaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.officialdoc.vo.receive;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import java.time.LocalDateTime;
  6 +import java.util.List;
  7 +import jakarta.validation.constraints.*;
  8 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  9 +import cn.iocoder.yudao.framework.dict.validation.InDict;
  10 +import cn.iocoder.yudao.module.oa.enums.DictTypeConstants;
  11 +import cn.iocoder.yudao.module.oa.enums.officialdoc.OaOfficialDocReceiveTypeEnum;
  12 +
  13 +@Schema(description = "管理后台 - 公文收文新增/修改 Request VO")
  14 +@Data
  15 +public class OaOfficialDocReceiveSaveReqVO {
  16 +
  17 + @Schema(description = "编号", example = "1024")
  18 + private Long id;
  19 +
  20 + @Schema(description = "公文标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "关于开展办公用品盘点的通知")
  21 + @NotBlank(message = "公文标题不能为空")
  22 + @Size(max = 255, message = "公文标题长度不能超过 {max} 个字符")
  23 + private String title;
  24 +
  25 + @Schema(description = "来文字号", example = "芋办〔2026〕1号")
  26 + @Size(max = 64, message = "来文字号长度不能超过 {max} 个字符")
  27 + private String documentNo;
  28 +
  29 + @Schema(description = "密级", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  30 + @NotNull(message = "密级不能为空")
  31 + @InDict(type = DictTypeConstants.OFFICIAL_DOC_SECRET_LEVEL, message = "密级必须是 {value}")
  32 + private Integer secrecyLevel;
  33 +
  34 + @Schema(description = "紧急程度", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  35 + @NotNull(message = "紧急程度不能为空")
  36 + @InDict(type = DictTypeConstants.OFFICIAL_DOC_URGENCY_LEVEL, message = "紧急程度必须是 {value}")
  37 + private Integer urgencyLevel;
  38 +
  39 + @Schema(description = "收文类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  40 + @NotNull(message = "收文类型不能为空")
  41 + @InEnum(value = OaOfficialDocReceiveTypeEnum.class, message = "收文类型必须是 {value}")
  42 + private Integer receiveType;
  43 +
  44 + @Schema(description = "收文时间", requiredMode = Schema.RequiredMode.REQUIRED, type = "integer", format = "int64", example = "1789347600000")
  45 + @NotNull(message = "收文时间不能为空")
  46 + private LocalDateTime receiveTime;
  47 +
  48 + @Schema(description = "收文部门编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  49 + @NotNull(message = "收文部门不能为空")
  50 + private Long receiveDeptId;
  51 +
  52 + @Schema(description = "主办人用户编号", example = "1024")
  53 + private Long handlerUserId;
  54 +
  55 + @Schema(description = "领导批示", example = "请行政部牵头办理")
  56 + @Size(max = 2000, message = "领导批示长度不能超过 {max} 个字符")
  57 + private String instruction;
  58 +
  59 + @Schema(description = "办理结果", example = "已完成盘点并归档")
  60 + @Size(max = 2000, message = "办理结果长度不能超过 {max} 个字符")
  61 + private String result;
  62 +
  63 + @Schema(description = "办理期限", type = "integer", format = "int64", example = "1789347600000")
  64 + private LocalDateTime deadlineTime;
  65 +
  66 + @Schema(description = "内容摘要", example = "本周已完成办公用品盘点")
  67 + private String summary;
  68 +
  69 + @Schema(description = "备注", example = "请提前联系行政部确认")
  70 + @Size(max = 500, message = "备注长度不能超过 {max} 个字符")
  71 + private String remark;
  72 +
  73 + @Schema(description = "附件地址列表", example = "[\"https://example.com/file.pdf\"]")
  74 + private List<@NotBlank(message = "附件地址不能为空") String> fileUrls;
  75 +
  76 + @Schema(description = "正式公文地址", example = "https://example.com/file.pdf")
  77 + @Size(max = 2048, message = "正式公文地址长度不能超过 {max} 个字符")
  78 + private String formalFileUrl;
  79 +
  80 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/officialdoc/vo/send/OaOfficialDocSendPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.officialdoc.vo.send;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +
  8 +@Schema(description = "管理后台 - 公文发文分页 Request VO")
  9 +@Data
  10 +@EqualsAndHashCode(callSuper = true)
  11 +public class OaOfficialDocSendPageReqVO extends PageParam {
  12 +
  13 + @Schema(description = "公文标题", example = "关于开展办公用品盘点的通知")
  14 + private String title;
  15 +
  16 + @Schema(description = "发文字号", example = "芋办〔2026〕1号")
  17 + private String documentNo;
  18 +
  19 + @Schema(description = "流程状态", example = "-1")
  20 + private Integer status;
  21 +
  22 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/officialdoc/vo/send/OaOfficialDocSendRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.officialdoc.vo.send;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import java.time.LocalDateTime;
  6 +import java.util.List;
  7 +
  8 +@Schema(description = "管理后台 - 公文发文 Response VO")
  9 +@Data
  10 +public class OaOfficialDocSendRespVO {
  11 +
  12 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  13 + private Long id;
  14 +
  15 + @Schema(description = "套红模板编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  16 + private Long templateId;
  17 +
  18 + @Schema(description = "公文标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "关于开展办公用品盘点的通知")
  19 + private String title;
  20 +
  21 + @Schema(description = "字号", example = "芋办")
  22 + private String noPrefix;
  23 +
  24 + @Schema(description = "年份", example = "2026")
  25 + private Integer year;
  26 +
  27 + @Schema(description = "第几号文", example = "1")
  28 + private Integer sequence;
  29 +
  30 + @Schema(description = "密级", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  31 + private Integer secrecyLevel;
  32 +
  33 + @Schema(description = "紧急程度", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  34 + private Integer urgencyLevel;
  35 +
  36 + @Schema(description = "公开类别", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  37 + private Integer disclosureType;
  38 +
  39 + @Schema(description = "发文日期", requiredMode = Schema.RequiredMode.REQUIRED, type = "integer", format = "int64", example = "1789347600000")
  40 + private LocalDateTime issueTime;
  41 +
  42 + @Schema(description = "发文部门编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  43 + private Long sendDeptId;
  44 +
  45 + @Schema(description = "主送部门编号列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 2]")
  46 + private List<Long> mainDeptIds;
  47 +
  48 + @Schema(description = "抄送部门编号列表", example = "[1, 2]")
  49 + private List<Long> copyDeptIds;
  50 +
  51 + @Schema(description = "公文正文", example = "请各部门于本周五前完成办公用品盘点并提交结果。")
  52 + private String content;
  53 +
  54 + @Schema(description = "附件地址列表", example = "[\"https://example.com/file.pdf\"]")
  55 + private List<String> fileUrls;
  56 +
  57 + @Schema(description = "正式公文地址", example = "https://example.com/file.pdf")
  58 + private String formalFileUrl;
  59 +
  60 + @Schema(description = "附注", example = "请按要求办理并反馈结果")
  61 + private String remark;
  62 +
  63 + @Schema(description = "单据编号", example = "CC202609140001")
  64 + private String no;
  65 +
  66 + @Schema(description = "公文文号", example = "芋办〔2026〕1号")
  67 + private String documentNo;
  68 +
  69 + @Schema(description = "签发人编号", example = "1024")
  70 + private Long signerUserId;
  71 +
  72 + @Schema(description = "签发人", example = "张三")
  73 + private String signerName;
  74 +
  75 + @Schema(description = "发文部门", example = "行政部")
  76 + private String sendDeptName;
  77 +
  78 + @Schema(description = "主送部门", example = "[\"行政部\"]")
  79 + private List<String> mainDeptNames;
  80 +
  81 + @Schema(description = "抄送部门", example = "[\"财务部\"]")
  82 + private List<String> copyDeptNames;
  83 +
  84 + @Schema(description = "审批状态", example = "-1")
  85 + private Integer status;
  86 +
  87 + @Schema(description = "流程实例编号", example = "1024")
  88 + private String processInstanceId;
  89 +
  90 + @Schema(description = "创建人编号", example = "1024")
  91 + private String creator;
  92 +
  93 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, type = "integer", format = "int64", example = "1789347600000")
  94 + private LocalDateTime createTime;
  95 +
  96 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/officialdoc/vo/send/OaOfficialDocSendSaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.officialdoc.vo.send;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import java.time.LocalDateTime;
  6 +import java.util.List;
  7 +import jakarta.validation.constraints.*;
  8 +import cn.iocoder.yudao.framework.dict.validation.InDict;
  9 +import cn.iocoder.yudao.module.oa.enums.DictTypeConstants;
  10 +
  11 +@Schema(description = "管理后台 - 公文发文新增/修改 Request VO")
  12 +@Data
  13 +public class OaOfficialDocSendSaveReqVO {
  14 +
  15 + @Schema(description = "编号", example = "1024")
  16 + private Long id;
  17 +
  18 + @Schema(description = "套红模板编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  19 + @NotNull(message = "套红模板不能为空")
  20 + private Long templateId;
  21 +
  22 + @Schema(description = "公文标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "关于开展办公用品盘点的通知")
  23 + @NotBlank(message = "标题不能为空")
  24 + @Size(max = 255, message = "公文标题长度不能超过 {max} 个字符")
  25 + private String title;
  26 +
  27 + @Schema(description = "字号", example = "芋办")
  28 + @Size(max = 64, message = "字号长度不能超过 {max} 个字符")
  29 + private String noPrefix;
  30 +
  31 + @Schema(description = "年份", example = "2026")
  32 + @Min(value = 1, message = "年份不能小于 {value}")
  33 + @Max(value = 9999, message = "年份不能大于 {value}")
  34 + private Integer year;
  35 +
  36 + @Schema(description = "第几号文", example = "1")
  37 + @Min(value = 1, message = "第几号文不能小于 {value}")
  38 + private Integer sequence;
  39 +
  40 + @Schema(description = "密级", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  41 + @NotNull(message = "密级不能为空")
  42 + @InDict(type = DictTypeConstants.OFFICIAL_DOC_SECRET_LEVEL, message = "密级必须是 {value}")
  43 + private Integer secrecyLevel;
  44 +
  45 + @Schema(description = "紧急程度", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  46 + @NotNull(message = "紧急程度不能为空")
  47 + @InDict(type = DictTypeConstants.OFFICIAL_DOC_URGENCY_LEVEL, message = "紧急程度必须是 {value}")
  48 + private Integer urgencyLevel;
  49 +
  50 + @Schema(description = "公开类别", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  51 + @NotNull(message = "公开类别不能为空")
  52 + @InDict(type = DictTypeConstants.OFFICIAL_DOC_PUBLIC_CATEGORY, message = "公开类别必须是 {value}")
  53 + private Integer disclosureType;
  54 +
  55 + @Schema(description = "发文日期", requiredMode = Schema.RequiredMode.REQUIRED, type = "integer", format = "int64", example = "1789347600000")
  56 + @NotNull(message = "发文日期不能为空")
  57 + private LocalDateTime issueTime;
  58 +
  59 + @Schema(description = "发文部门编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  60 + @NotNull(message = "发文部门不能为空")
  61 + private Long sendDeptId;
  62 +
  63 + @Schema(description = "主送部门编号列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 2]")
  64 + @NotEmpty(message = "主送部门不能为空")
  65 + private List<@NotNull(message = "主送部门编号不能为空") Long> mainDeptIds;
  66 +
  67 + @Schema(description = "抄送部门编号列表", example = "[1, 2]")
  68 + private List<@NotNull(message = "抄送部门编号不能为空") Long> copyDeptIds;
  69 +
  70 + @Schema(description = "公文正文", example = "请各部门于本周五前完成办公用品盘点并提交结果。")
  71 + private String content;
  72 +
  73 + @Schema(description = "附件地址列表", example = "[\"https://example.com/file.pdf\"]")
  74 + private List<@NotBlank(message = "附件地址不能为空") String> fileUrls;
  75 +
  76 + @Schema(description = "正式公文地址", example = "https://example.com/file.pdf")
  77 + @Size(max = 2048, message = "正式公文地址长度不能超过 {max} 个字符")
  78 + private String formalFileUrl;
  79 +
  80 + @Schema(description = "附注", example = "请按要求办理并反馈结果")
  81 + @Size(max = 500, message = "附注长度不能超过 {max} 个字符")
  82 + private String remark;
  83 +
  84 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/officialdoc/vo/template/OaOfficialDocTemplatePageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.officialdoc.vo.template;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +
  8 +@Schema(description = "管理后台 - 套红模板分页 Request VO")
  9 +@Data
  10 +@EqualsAndHashCode(callSuper = true)
  11 +public class OaOfficialDocTemplatePageReqVO extends PageParam {
  12 +
  13 + @Schema(description = "模板名称", example = "公司行政发文模板")
  14 + private String name;
  15 +
  16 + @Schema(description = "状态", example = "0")
  17 + private Integer status;
  18 +
  19 +}
... ...
yudao-module-oa/src/main/java/cn/iocoder/yudao/module/oa/controller/admin/officialdoc/vo/template/OaOfficialDocTemplateRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.oa.controller.admin.officialdoc.vo.template;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +import java.time.LocalDateTime;
  6 +import java.util.List;
  7 +
  8 +@Schema(description = "管理后台 - 套红模板 Response VO")
  9 +@Data
  10 +public class OaOfficialDocTemplateRespVO {
  11 +
  12 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  13 + private Long id;
  14 +
  15 + @Schema(description = "模板名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "公司行政发文模板")
  16 + private String name;
  17 +
  18 + @Schema(description = "红头名称", example = "芋道科技有限公司文件")
  19 + private String authorityName;
  20 +
  21 + @Schema(description = "红头字号", example = "32")
  22 + private Integer fontSize;
  23 +
  24 + @Schema(description = "发文字号前缀", example = "芋办")
  25 + private String noPrefix;
  26 +
  27 + @Schema(description = "印章图片地址", example = "https://example.com/image.png")
  28 + private String sealPicUrl;
  29 +
  30 + @Schema(description = "分隔线类型", example = "0")
  31 + private Integer separatorType;
  32 +
  33 + @Schema(description = "状态", example = "0")
  34 + private Integer status;
  35 +
  36 + @Schema(description = "显示顺序", example = "1")
  37 + private Integer sort;
  38 +
  39 + @Schema(description = "备注", example = "请提前联系行政部确认")
  40 + private String remark;
  41 +
  42 + @Schema(description = "创建人编号", example = "1024")
  43 + private String creator;
  44 +
  45 + @Schema(description = "创建时间", type = "integer", format = "int64", example = "1789347600000")
  46 + private LocalDateTime createTime;
  47 +
  48 +}
... ...