Commit 09cf3017e19fcf8dd17ee0993f2cc989f27a9fd9
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,6 +10,8 @@ import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils; | ||
| 10 | import lombok.AllArgsConstructor; | 10 | import lombok.AllArgsConstructor; |
| 11 | import lombok.Getter; | 11 | import lombok.Getter; |
| 12 | 12 | ||
| 13 | +import java.math.BigDecimal; | ||
| 14 | +import java.math.RoundingMode; | ||
| 13 | import java.sql.Timestamp; | 15 | import java.sql.Timestamp; |
| 14 | import java.time.*; | 16 | import java.time.*; |
| 15 | import java.time.format.DateTimeFormatter; | 17 | import java.time.format.DateTimeFormatter; |
| @@ -33,6 +35,54 @@ import static cn.hutool.core.date.DatePattern.*; | @@ -33,6 +35,54 @@ import static cn.hutool.core.date.DatePattern.*; | ||
| 33 | public class LocalDateTimeUtils { | 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 | * 空的 LocalDateTime 对象,主要用于 DB 唯一索引的默认值 | 86 | * 空的 LocalDateTime 对象,主要用于 DB 唯一索引的默认值 |
| 37 | */ | 87 | */ |
| 38 | public static LocalDateTime EMPTY = buildTime(1970, 1, 1); | 88 | public static LocalDateTime EMPTY = buildTime(1970, 1, 1); |
| @@ -544,6 +594,16 @@ public class LocalDateTimeUtils { | @@ -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 | * @param beginDate 开始日期 | 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,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 | * @param <T> 数据类型 | 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 | if (CollUtil.isEmpty(list)) { | 39 | if (CollUtil.isEmpty(list)) { |
| 40 | return PageResult.empty(); | 40 | return PageResult.empty(); |
| 41 | } | 41 | } |
| @@ -43,8 +43,9 @@ public class PageUtils { | @@ -43,8 +43,9 @@ public class PageUtils { | ||
| 43 | if (PageParam.PAGE_SIZE_NONE.equals(pageParam.getPageSize())) { | 43 | if (PageParam.PAGE_SIZE_NONE.equals(pageParam.getPageSize())) { |
| 44 | return new PageResult<>(list, total); | 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 | return new PageResult<>(list.subList(fromIndex, toIndex), total); | 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,6 +4,7 @@ import cn.hutool.core.collection.CollUtil; | ||
| 4 | import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.TimeRange; | 4 | import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.TimeRange; |
| 5 | import org.junit.jupiter.api.Test; | 5 | import org.junit.jupiter.api.Test; |
| 6 | 6 | ||
| 7 | +import java.math.BigDecimal; | ||
| 7 | import java.time.LocalDate; | 8 | import java.time.LocalDate; |
| 8 | import java.time.LocalDateTime; | 9 | import java.time.LocalDateTime; |
| 9 | import java.time.LocalTime; | 10 | import java.time.LocalTime; |
| @@ -26,6 +27,56 @@ import static org.junit.jupiter.api.Assertions.assertTrue; | @@ -26,6 +27,56 @@ import static org.junit.jupiter.api.Assertions.assertTrue; | ||
| 26 | public class LocalDateTimeUtilsTest { | 27 | public class LocalDateTimeUtilsTest { |
| 27 | 28 | ||
| 28 | @Test | 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 | public void testDateCompare() { | 80 | public void testDateCompare() { |
| 30 | // 准备参数 | 81 | // 准备参数 |
| 31 | LocalDate firstDate = LocalDate.of(2026, 7, 23); | 82 | LocalDate firstDate = LocalDate.of(2026, 7, 23); |
| @@ -99,6 +150,10 @@ public class LocalDateTimeUtilsTest { | @@ -99,6 +150,10 @@ public class LocalDateTimeUtilsTest { | ||
| 99 | assertNull(LocalDateTimeUtils.getDayBeginTime((LocalDateTime) null)); | 150 | assertNull(LocalDateTimeUtils.getDayBeginTime((LocalDateTime) null)); |
| 100 | assertEquals(LocalDateTime.of(date, LocalTime.MAX), | 151 | assertEquals(LocalDateTime.of(date, LocalTime.MAX), |
| 101 | LocalDateTimeUtils.getDayEndTime(date)); | 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 | @Test | 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,6 +5,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam; | ||
| 5 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 5 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 6 | import cn.iocoder.yudao.framework.common.pojo.SortablePageParam; | 6 | import cn.iocoder.yudao.framework.common.pojo.SortablePageParam; |
| 7 | import cn.iocoder.yudao.framework.common.pojo.SortingField; | 7 | import cn.iocoder.yudao.framework.common.pojo.SortingField; |
| 8 | +import cn.iocoder.yudao.framework.common.util.object.PageUtils; | ||
| 8 | import cn.iocoder.yudao.framework.mybatis.core.util.JdbcUtils; | 9 | import cn.iocoder.yudao.framework.mybatis.core.util.JdbcUtils; |
| 9 | import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils; | 10 | import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils; |
| 10 | import com.baomidou.mybatisplus.annotation.DbType; | 11 | import com.baomidou.mybatisplus.annotation.DbType; |
| @@ -44,7 +45,7 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> { | @@ -44,7 +45,7 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> { | ||
| 44 | if (PageParam.PAGE_SIZE_NONE.equals(pageParam.getPageSize())) { | 45 | if (PageParam.PAGE_SIZE_NONE.equals(pageParam.getPageSize())) { |
| 45 | MyBatisUtils.addOrder(queryWrapper, sortingFields); | 46 | MyBatisUtils.addOrder(queryWrapper, sortingFields); |
| 46 | List<T> list = selectList(queryWrapper); | 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 | // MyBatis Plus 查询 | 51 | // MyBatis Plus 查询 |
| @@ -58,7 +59,7 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> { | @@ -58,7 +59,7 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> { | ||
| 58 | // 特殊:不分页,直接查询全部 | 59 | // 特殊:不分页,直接查询全部 |
| 59 | if (PageParam.PAGE_SIZE_NONE.equals(pageParam.getPageSize())) { | 60 | if (PageParam.PAGE_SIZE_NONE.equals(pageParam.getPageSize())) { |
| 60 | List<D> list = selectJoinList(clazz, lambdaWrapper); | 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 | // MyBatis Plus Join 查询 | 65 | // MyBatis Plus Join 查询 |
| @@ -81,7 +82,7 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> { | @@ -81,7 +82,7 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> { | ||
| 81 | // 特殊:不分页,直接查询全部 | 82 | // 特殊:不分页,直接查询全部 |
| 82 | if (PageParam.PAGE_SIZE_NONE.equals(pageParam.getPageSize())) { | 83 | if (PageParam.PAGE_SIZE_NONE.equals(pageParam.getPageSize())) { |
| 83 | List<D> list = selectJoinList(clazz, lambdaWrapper); | 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 | // MyBatis Plus Join 查询 | 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,7 +135,7 @@ public class HrmAttendanceStatisticsServiceImpl implements HrmAttendanceStatisti | ||
| 135 | buildMonthSummaryList(reqVO.getYear(), reqVO.getMonth(), employeeList); | 135 | buildMonthSummaryList(reqVO.getYear(), reqVO.getMonth(), employeeList); |
| 136 | List<HrmAttendanceMonthRecordRespVO> filteredMonthSummaryList = filterList(monthSummaryList, | 136 | List<HrmAttendanceMonthRecordRespVO> filteredMonthSummaryList = filterList(monthSummaryList, |
| 137 | record -> Objects.equals(record.getFullAttendance(), reqVO.getFullAttendance())); | 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 | @Override | 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 | +} |